Full stack Bitcoin software

Crypto

Architecture of libbitcoin-system: chain, config, crypto, hash, machine, math, radix, stream, streamers, unicode and wallet.

The cryptography, from AES to secp256k1, is implemented in the library itself. It follows the design of the hash module: class templates over specifications, written once over the f operators, so that the same source compiles to scalar code or to vector code over the extensions the build targets.

Paths are relative to include/bitcoin/system/ in libbitcoin-system, at master 0cd57fcf (2026-10-27).

1. Algorithms#

Group Algorithms Location
Ciphers AES-128 and AES-256 (CTR, CBC decryption), ChaCha20, FSChaCha20 (BIP324), Salsa20 crypto/aes/, crypto/
Authentication Poly1305, GHASH crypto/poly1305.hpp, crypto/aes/ghash.hpp
Authenticated encryption AES-GCM, ChaCha20-Poly1305, FSChaCha20-Poly1305, XSalsa20-Poly1305 crypto/
Elliptic curves secp256k1 (ECDSA, Schnorr, ElligatorSwift, batch verification, silent payment scanning), secp256r1, secp384r1, X25519 crypto/secp256k1/, crypto/nist/, crypto/x25519.hpp
Signature parsing DER and lax DER (BIP66) crypto/der_parser.hpp
Secret sharing Shamir over GF(256), RS1024 checksum (SLIP-0039) crypto/shamir/
Entropy maybe_random (std::random_device) crypto/maybe_random.hpp

The template families are instantiated by specification (crypto/algorithms.hpp):

// FIPS 197 (AES)
using aes128     = aes::algorithm<aes::k128>;
using aes256     = aes::algorithm<aes::k256>;

// SEC 1, RFC 6979 (NIST curves)
using secp256r1  = nist::algorithm<nist::p256>;
using secp384r1  = nist::algorithm<nist::p384>;

// SLIP-0039 (Shamir)
using shamir128  = shamir::algorithm<shamir::k128>;
using shamir256  = shamir::algorithm<shamir::k256>;

2. Native, vector and constant time#

Each cipher has a native path where the build targets the dedicated instructions, and otherwise a portable path written over the f operators and extended words, as the hash functions are. The selection is at compile time, by the same have_* flags and if constexpr (see Hash).

Algorithm Native Portable
AES AES-NI, VAES (x86-64), Crypto extension (ARM) Constant-time bitsliced blocks, vectorized across 64 bit lanes
GHASH PCLMULQDQ (x86-64), PMULL (ARM) Constant-time carryless multiplication
ChaCha20 One block per 32 bit lane of an extended word
secp256k1 field One field element per 64 bit lane of an extended word, with 52 bit fused multiply-add (IFMA) where available

The AES template takes the same Native and Vector parameters as the SHA template (crypto/aes/algorithm.hpp):

static constexpr auto use_aes = Native && bc::have_aes && bc::have_128;
static constexpr auto use_vaes = use_aes && bc::have_vaes;
static constexpr auto use_128 = Vector && bc::have_128;
static constexpr auto use_256 = Vector && bc::have_256;
static constexpr auto use_512 = Vector && bc::have_512;

Secret key operations are constant time: the bitsliced AES, the GHASH multiplication, signing and key derivation on the NIST curves, and field and scalar inversion on secp256k1 by fixed exponent.

Constant time is tested only in part. The secp256k1 secret key operations (key derivation, secret and point tweaks, ECDSA and Schnorr signing, and ElligatorSwift key creation and exchange) are tested under Valgrind memcheck (test/crypto/secp256k1/ctime.cpp): each secret is marked undefined, so a branch or memory access that depends on it is reported as an error. These tests run in one CI build, a GCC debug build with the --with-valgrind option, so they cover neither optimized builds, which may introduce secret-dependent branches, nor other compilers or platforms. The other constant time code, including AES, GHASH and the NIST curves, is constant time by construction but not tested for it.

3. secp256k1#

secp256k1::algorithm (crypto/secp256k1/algorithm.hpp) implements the curve's arithmetic in the library, using the 5x52 field representation of bitcoin-core/secp256k1. Its field operations are templates over the word type, integral or extended, so the same arithmetic computes one element or one element per 64 bit lane:

/// secp256k1 arithmetic over integral or extended integral words.
/// Extended words compute one element per 64 bit lane.

On it are built ECDSA and BIP340 Schnorr signing and verification, ElligatorSwift encoding (BIP324), multi-scalar multiplication (impl/crypto/secp256k1/algorithm_pippenger.ipp), and Bernstein-Yang safegcd inversion. The generator tables used for multiplication by the generator are constexpr, computed by the compiler (crypto/secp256k1/tables.hpp).

The batch types (crypto/secp256k1/batch/) verify many ECDSA or Schnorr signatures at once, and scan for silent payments (BIP352), over the lanes of the extended words. Script execution feeds them through signature capture.

4. Other curves#

nist::algorithm implements ECDSA over the short Weierstrass curves with a = -3, secp256r1 and secp384r1, with deterministic nonces (RFC 6979), for the X.509 certificate support (x509/). X25519 implements the Diffie-Hellman function over Curve25519 (RFC 7748).

5. Entropy#

maybe_random draws on std::random_device, which may or may not obtain its values from the operating system or hardware, and whose values may or may not be random. The standard permits a deterministic implementation, and there has been one in wide use: MinGW's seeded a Mersenne Twister with a fixed value, so every run produced the same sequence. This was observed at least as early as 2013, and was not fixed until years later (GCC bug 85494). Since the quality of the source cannot be known, it is named for what it is, and is used only for ephemeral values such as network session keys and address selection, never for wallet seeds. The wallet takes all entropy from its caller.

maybe_random was formerly pseudo_random. Both names state that the output is not known to be random, but some took pseudo_random to mean truly random, and used it for seeds despite explicit warnings against that. It has therefore been renamed, and the bx seed command that exposed it has been removed (see CVE-2023-39910).

Copyright © 2011-2026 libbitcoin developers