UltrafastSecp256k1 API Reference
July 7, 2026 · View on GitHub
Complete API documentation for CPU, CUDA, and WASM implementations.
Table of Contents
- CPU API
- Address Generation API
- Multi-Chain Coins API
- Unified Wallet API
- BIP-39 Mnemonic Seed Phrases
- Message Signing API
- Zero-Knowledge Proof API
- CUDA API
- WASM API
- C ABI (ufsecp)
- Context Lifecycle
- Private Key Operations
- Public Key Operations
- ECDSA
- Schnorr / BIP-340
- ECDH
- Hashing
- Addresses and WIF
- BIP-32 HD Keys
- Taproot / BIP-341
- BIP-39 Mnemonics
- Batch Verification
- Libbitcoin Bridge
- Multi-Scalar Multiplication
- MuSig2 / BIP-327
- FROST Threshold Signatures
- Adaptor Signatures
- Pedersen Commitments
- Zero-Knowledge Proofs
- Multi-Coin Wallet
- Ethereum
- Performance Tips
- Examples
CPU API
Namespace: secp256k1::fast
Headers:
#include <secp256k1/field.hpp>
#include <secp256k1/scalar.hpp>
#include <secp256k1/point.hpp>
FieldElement
256-bit field element for secp256k1 curve (mod p where p = - - 977).
Construction
// Zero element
FieldElement a = FieldElement::zero();
// One element
FieldElement b = FieldElement::one();
// From 64-bit integer
FieldElement c = FieldElement::from_uint64(12345);
// From 4 x 64-bit limbs (little-endian, RECOMMENDED for binary I/O)
std::array<uint64_t, 4> limbs = {0x123, 0x456, 0x789, 0xABC};
FieldElement d = FieldElement::from_limbs(limbs);
// From 32 bytes (big-endian, for hex/test vectors only)
std::array<uint8_t, 32> bytes = {...};
FieldElement e = FieldElement::from_bytes(bytes);
// From hex string (developer-friendly)
FieldElement f = FieldElement::from_hex(
"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"
);
Arithmetic Operations
FieldElement a, b;
// Basic arithmetic (immutable, returns new object)
FieldElement sum = a + b;
FieldElement diff = a - b;
FieldElement prod = a * b;
FieldElement sq = a.square();
FieldElement inv = a.inverse();
// In-place arithmetic (mutable, ~10-15% faster)
a += b;
a -= b;
a *= b;
a.square_inplace(); // a = a^2
a.inverse_inplace(); // a = a^-¹
Serialization
FieldElement a;
// To bytes (big-endian)
std::array<uint8_t, 32> bytes = a.to_bytes();
// To bytes into existing buffer (no allocation)
uint8_t buffer[32];
a.to_bytes_into(buffer);
// To hex string
std::string hex = a.to_hex();
// Access raw limbs (little-endian)
const auto& limbs = a.limbs(); // std::array<uint64_t, 4>
Comparison
FieldElement a, b;
if (a == b) { ... }
if (a != b) { ... }
Scalar
256-bit scalar for secp256k1 curve (mod n where n is the group order).
Construction
// Zero
Scalar a = Scalar::zero();
// One
Scalar b = Scalar::one();
// From 64-bit integer
Scalar c = Scalar::from_uint64(12345);
// From limbs (little-endian)
std::array<uint64_t, 4> limbs = {...};
Scalar d = Scalar::from_limbs(limbs);
// From bytes (big-endian)
std::array<uint8_t, 32> bytes = {...};
Scalar e = Scalar::from_bytes(bytes);
// From hex string
Scalar f = Scalar::from_hex(
"E9873D79C6D87DC0FB6A5778633389F4453213303DA61F20BD67FC233AA33262"
);
Arithmetic Operations
Scalar a, b;
// Basic arithmetic
Scalar sum = a + b;
Scalar diff = a - b;
Scalar prod = a * b;
// In-place
a += b;
a -= b;
a *= b;
Utility Methods
Scalar s;
// Check if zero
bool isZero = s.is_zero();
// Get specific bit
uint8_t bit = s.bit(index); // 0 or 1
// NAF encoding (Non-Adjacent Form)
std::vector<int8_t> naf = s.to_naf();
// wNAF encoding (width-w NAF)
std::vector<int8_t> wnaf = s.to_wnaf(4); // width = 4
Point
Elliptic curve point on secp256k1 (internally Jacobian coordinates).
Construction
// Generator point G
Point G = Point::generator();
// Point at infinity (identity)
Point inf = Point::infinity();
// From affine coordinates
FieldElement x = FieldElement::from_hex("...");
FieldElement y = FieldElement::from_hex("...");
Point p = Point::from_affine(x, y);
// From hex strings (developer-friendly)
Point p2 = Point::from_hex(
"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"
);
Point Operations
Point p, q;
Scalar k;
// Addition and doubling
Point sum = p.add(q); // p + q
Point doubled = p.dbl(); // 2p
// Scalar multiplication
Point result = p.scalar_mul(k); // k * p
// Negation
Point neg = p.negate(); // -p
Optimized Scalar Multiplication
// For fixed K x variable Q pattern (same K, different Q points):
Scalar K = Scalar::from_hex("...");
KPlan plan = KPlan::from_scalar(K); // Precompute once
// Then for each Q:
Point Q1 = Point::from_hex("...", "...");
Point Q2 = Point::from_hex("...", "...");
Point R1 = Q1.scalar_mul_with_plan(plan); // Fastest!
Point R2 = Q2.scalar_mul_with_plan(plan);
In-Place Operations (Fastest)
Point p;
// Increment/decrement by generator
p.next_inplace(); // p += G
p.prev_inplace(); // p -= G
// In-place arithmetic
p.add_inplace(q); // p += q
p.sub_inplace(q); // p -= q
p.dbl_inplace(); // p = 2p
p.negate_inplace(); // p = -p
// Mixed addition (when q is affine, z=1)
FieldElement qx, qy;
p.add_mixed_inplace(qx, qy); // Branchless, ~12% faster
Serialization
Point p;
// Get affine coordinates
FieldElement x = p.x();
FieldElement y = p.y();
// Compressed format (33 bytes: 0x02/0x03 + x)
std::array<uint8_t, 33> compressed = p.to_compressed();
// Uncompressed format (65 bytes: 0x04 + x + y)
std::array<uint8_t, 65> uncompressed = p.to_uncompressed();
// Split x-coordinate for database lookups
std::array<uint8_t, 16> first_half = p.x_first_half();
std::array<uint8_t, 16> second_half = p.x_second_half();
Properties
Point p;
// Check if point at infinity
bool isInf = p.is_infinity();
// Direct Jacobian coordinate access
const FieldElement& X = p.X(); // Jacobian X
const FieldElement& Y = p.Y(); // Jacobian Y
const FieldElement& Z = p.z(); // Jacobian Z
Utility Functions
Self-Test
#include <secp256k1/point.hpp>
// Run correctness tests
bool passed = secp256k1::fast::Selftest(true); // verbose=true
if (!passed) {
std::cerr << "Self-test failed!" << std::endl;
}
ECDSA (RFC 6979)
Namespace: secp256k1
Header:
#include <secp256k1/ecdsa.hpp>
ECDSASignature
struct ECDSASignature {
fast::Scalar r;
fast::Scalar s;
// DER encoding (variable length, max 72 bytes)
std::pair<std::array<uint8_t, 72>, std::size_t> to_der() const;
// Compact 64-byte encoding: r (32 bytes) || s (32 bytes)
std::array<uint8_t, 64> to_compact() const;
// Decode from compact
static ECDSASignature from_compact(const std::array<uint8_t, 64>& data);
// Normalize to low-S (BIP-62)
ECDSASignature normalize() const;
// Check low-S
bool is_low_s() const;
};
Signing
// Sign a 32-byte message hash with RFC 6979 deterministic nonce.
// Returns normalized (low-S) signature. Returns {0,0} on failure.
ECDSASignature ecdsa_sign(
const std::array<uint8_t, 32>& msg_hash,
const fast::Scalar& private_key
);
Verification
// Verify ECDSA signature. Accepts both low-S and high-S.
bool ecdsa_verify(
const std::array<uint8_t, 32>& msg_hash,
const fast::Point& public_key,
const ECDSASignature& sig
);
RFC 6979 Nonce
// Deterministic nonce generation per RFC 6979.
fast::Scalar rfc6979_nonce(
const fast::Scalar& private_key,
const std::array<uint8_t, 32>& msg_hash
);
Example
#include <secp256k1/ecdsa.hpp>
#include <secp256k1/sha256.hpp>
#include <secp256k1/point.hpp>
using namespace secp256k1;
auto msg_hash = SHA256::hash("Hello ECDSA", 11);
fast::Scalar sk = fast::Scalar::from_hex("...");
fast::Point pk = fast::Point::generator().scalar_mul(sk);
// Sign
auto sig = ecdsa_sign(msg_hash, sk);
// Verify
bool ok = ecdsa_verify(msg_hash, pk, sig);
// Compact encoding (64 bytes)
auto compact = sig.to_compact();
auto recovered = ECDSASignature::from_compact(compact);
Schnorr (BIP-340)
Namespace: secp256k1
Header:
#include <secp256k1/schnorr.hpp>
SchnorrSignature
struct SchnorrSignature {
std::array<uint8_t, 32> r; // R.x (nonce point x-coordinate)
fast::Scalar s; // scalar s
// 64-byte encoding: r (32) || s (32)
std::array<uint8_t, 64> to_bytes() const;
static SchnorrSignature from_bytes(const std::array<uint8_t, 64>& data);
};
Signing
// BIP-340 Schnorr sign.
// aux_rand: 32 bytes of auxiliary randomness (use zeros for deterministic).
SchnorrSignature schnorr_sign(
const fast::Scalar& private_key,
const std::array<uint8_t, 32>& msg,
const std::array<uint8_t, 32>& aux_rand
);
Verification
// BIP-340 Schnorr verify with x-only public key.
bool schnorr_verify(
const std::array<uint8_t, 32>& pubkey_x,
const std::array<uint8_t, 32>& msg,
const SchnorrSignature& sig
);
Utilities
// X-only public key (BIP-340: negate if Y is odd)
std::array<uint8_t, 32> schnorr_pubkey(const fast::Scalar& private_key);
// Tagged hash: H_tag(msg) = SHA256(SHA256(tag) || SHA256(tag) || msg)
std::array<uint8_t, 32> tagged_hash(
const char* tag, const void* data, std::size_t len
);
Example
#include <secp256k1/schnorr.hpp>
using namespace secp256k1;
fast::Scalar sk = fast::Scalar::from_hex("...");
auto pk_x = schnorr_pubkey(sk);
std::array<uint8_t, 32> msg = { /* message hash */ };
std::array<uint8_t, 32> aux = {}; // zeros for deterministic
auto sig = schnorr_sign(sk, msg, aux);
bool ok = schnorr_verify(pk_x, msg, sig);
Batch Verification
Namespace: secp256k1
Header:
#include <secp256k1/batch_verify.hpp>
Canonical libbitcoin C++ surface (ufsecp::lbtc::*)
The canonical bridge-free libbitcoin integration is the header-only target secp256k1::fastsecp256k1_libbitcoin.
Build with -DSECP256K1_BUILD_LIBBITCOIN=ON (optionally -DSECP256K1_BUILD_LIBBITCOIN_TESTS=ON).
Package target: secp256k1::fastsecp256k1_libbitcoin (INTERFACE, carries C++20 + HAVE_ULTRAFAST + engine).
Header: <ufsecp/libbitcoin.hpp> — namespace ufsecp::lbtc::*, all stateless inline functions.
Byte layouts (match libbitcoin/libsecp256k1 exactly):
| Type | Format |
|---|---|
| pubkey | 33-byte compressed (0x02/0x03 || X big-endian) |
| hash | 32-byte message hash |
| ecdsa sig | 64-byte secp256k1_ecdsa_signature opaque (r limbs LE || s limbs LE) |
| schnorr sig | 64-byte BIP-340 (R.x big-endian || s big-endian) |
| xonly | 32-byte x-only pubkey |
CT guarantees: Secret-bearing operations (sign, key create, seckey tweak) use secp256k1::ct::* primitives — constant-time, no data-dependent branches. Verify/recover/combine operations are variable-time (all inputs public). See docs/LIBBITCOIN_INTEGRATION.md for the full CT/VT matrix.
Fail-closed semantics: On any failure (invalid input, parsing error, signing error) every output buffer is zeroed and false is returned. Batch APIs write 1/0 per row to out_results; all-valid batches fill out_results with 1. count == 0 returns true. Invalid/nullptr row pointers return false.
Threading: max_threads == 0 = auto, max_threads == 1 = serial bounded chunks, N = cap.
Batch APIs:
ecdsa_verify_batch/ecdsa_verify_columns— rows[hash32|pub33|sig64]@ stride, or parallel spansschnorr_verify_batch/schnorr_verify_columns— rows[msg32|xonly32|sig64]@ stride, or parallel spans
Public-data batch ops (validate / commitment / hashing): all [[nodiscard]] inline bool in ufsecp::lbtc, all variable-time / public-data (no secret is ever touched). Each is ONE surface — internal GPU acceleration via the EXISTING GpuBackend virtuals + deterministic CPU fallback, no CPU/GPU split, no GPU status code, no caller chunking, no C ABI.
// Validate N x-only pubkeys. out[i]=1 iff valid BIP-340 x-coordinate (x<p, even-y lift_x).
bool xonly_validate_batch(const uint8_t* keys32, size_t count,
uint8_t* out_results, size_t max_threads = 0) noexcept;
// Validate N compressed pubkeys. out[i]=1 iff prefix∈{0x02,0x03}, x<p, on curve.
bool pubkey_validate_batch(const uint8_t* pubkeys33, size_t count,
uint8_t* out_results, size_t max_threads = 0) noexcept;
// Verify N RAW taproot tweak commitments (tweak = precomputed scalar, NOT H_TapTweak).
// out[i]=1 iff x(lift_x_even(internal_i) + tweak_i*G)==tweaked_x_i AND y-parity==parity[i].
bool taproot_commitment_verify_batch(const uint8_t* internal_x32, const uint8_t* tweak32,
const uint8_t* tweaked_x32, const uint8_t* parity,
size_t count, uint8_t* out_results,
size_t max_threads = 0) noexcept;
// BIP-340 tagged hash over fixed-length msgs; tag_hash32 = SHA256(tag) shared across rows.
// out32[i] = SHA256(tag_hash32 || tag_hash32 || msgs[i]).
bool tagged_hash_batch(const uint8_t* tag_hash32, const uint8_t* msgs,
size_t msg_len, size_t count, uint8_t* out32,
size_t max_threads = 0) noexcept;
bool tagged_hash_batch(const char* tag, size_t tag_len, const uint8_t* msgs,
size_t msg_len, size_t count, uint8_t* out32,
size_t max_threads = 0) noexcept; // computes tag_hash32 once
// BIP-340 tagged hash over per-item variable-length msgs (TapLeaf scripts).
// CPU covers ALL lengths (no device-side 256-byte cap).
bool tagged_hash_var_batch(const uint8_t* tag_hash32, const uint8_t* msgs,
const uint32_t* msg_lens, size_t stride, size_t count,
uint8_t* out32, size_t max_threads = 0) noexcept;
// Bitcoin HASH256 (double SHA-256) over fixed-length inputs.
// out32[i] = SHA256(SHA256(inputs[i])).
bool hash256_batch(const uint8_t* inputs, size_t input_len, size_t count,
uint8_t* out32, size_t max_threads = 0) noexcept;
// Bitcoin HASH256 (double SHA-256) over per-item variable-length inputs
// (e.g. CPU-pre-serialized transaction bytes for a future txid/wtxid batch
// wrapper). No tag prefix, no transaction parsing on GPU — the caller
// serializes on the CPU; row i = inputs[i*stride .. i*stride+input_lens[i]).
// out32[i] = SHA256(SHA256(row_i)). Every input_lens[i] is validated against
// stride per-row, host-side, before GPU dispatch.
bool hash256_var_batch(const uint8_t* inputs, const uint32_t* input_lens,
size_t stride, size_t count, uint8_t* out32,
size_t max_threads = 0) noexcept;
hash256_var_batch's max_threads parameter is accepted for signature
parity with the other batch ops but is currently unused by the CPU fallback
(single-pass per-row hashing; the GPU hook, when installed, does its own
internal parallelism) — reserved for a future threaded CPU fallback.
Fail-closed contract — validate ops: count==0→true (out untouched); null ptr or count×elem overflow → zero out_results (if non-null) and false; operational GPU failure declines → CPU overwrites every row (never all-zero). Hash ops never pre-zero out32 (zero = wrong/consensus-invalid hash): count==0→true; null ptr, msg_len/input_len==0, stride < msg_lens[i], or overflow → false without touching out32; operational GPU failure declines → CPU writes the correct hash for every row. hash256_var_batch additionally rejects any individual input_lens[i]==0 or input_lens[i]>stride (per-row bounds check, not just a scalar stride check) before touching out32 or dispatching to GPU.
Consumer CMake example:
find_package(secp256k1-fast REQUIRED COMPONENTS CPU LIBBITCOIN)
target_link_libraries(myapp PRIVATE secp256k1::fastsecp256k1_libbitcoin)
Direct byte-span APIs
These APIs are for C++ consumers that already store libbitcoin/libsecp-compatible rows or column arrays. The engine parses and verifies in bounded chunks, including when max_threads == 1, so callers do not need to marshal full tables into ECDSABatchEntry or SchnorrBatchEntry vectors.
// ECDSA rows: [hash32 | compressed_pubkey33 | opaque_sig64]
bool ecdsa_batch_verify_opaque_rows(const uint8_t* rows,
size_t stride,
size_t count,
uint8_t* out_results = nullptr,
size_t max_threads = 0);
// ECDSA columns: digests[count][32], pubkeys[count][33], sigs[count][64]
bool ecdsa_batch_verify_opaque_columns(const uint8_t* digests32,
const uint8_t* pubkeys33,
const uint8_t* sigs64,
size_t count,
uint8_t* out_results = nullptr,
size_t max_threads = 0);
// Schnorr rows: [msg32 | xonly_pubkey32 | bip340_sig64]
bool schnorr_batch_verify_bip340_rows(const uint8_t* rows,
size_t stride,
size_t count,
uint8_t* out_results = nullptr,
size_t max_threads = 0);
// Schnorr columns: digests[count][32], xonly[count][32], sigs[count][64]
bool schnorr_batch_verify_bip340_columns(const uint8_t* digests32,
const uint8_t* xonly32,
const uint8_t* sigs64,
size_t count,
uint8_t* out_results = nullptr,
size_t max_threads = 0);
max_threads == 0 means auto, max_threads == 1 means serial bounded chunks. count == 0 returns true. If out_results is non-null, all-valid batches fill it with 1; mixed, invalid, or unparsable batches write per-row 1/0 after fallback. Invalid pointers or undersized strides return false and zero out_results when provided.
SHA-256
Namespace: secp256k1
Header:
#include <secp256k1/sha256.hpp>
One-shot Hashing
// SHA-256
SHA256::digest_type SHA256::hash(const void* data, std::size_t len);
// Double-SHA256: SHA256(SHA256(data))
SHA256::digest_type SHA256::hash256(const void* data, std::size_t len);
Streaming API
secp256k1::SHA256 ctx;
ctx.update("part1", 5);
ctx.update("part2", 5);
auto digest = ctx.finalize();
// Reuse
ctx.reset();
ctx.update("new data", 8);
auto digest2 = ctx.finalize();
Example
#include <secp256k1/sha256.hpp>
auto hash = secp256k1::SHA256::hash("Hello, world!", 13);
// hash is std::array<uint8_t, 32>
// Double-SHA256 (Bitcoin's hash)
auto hash256 = secp256k1::SHA256::hash256("tx_data", 7);
Constant-Time Layer
Namespace: secp256k1::fast::ct
Headers:
#include <secp256k1/ct/field.hpp>
#include <secp256k1/ct/scalar.hpp>
#include <secp256k1/ct/point.hpp>
#include <secp256k1/ct/ops.hpp>
The CT layer provides side-channel resistant variants of critical operations:
namespace secp256k1::fast::ct {
// Constant-time field operations
void field_mul(const FieldElement& a, const FieldElement& b, FieldElement& out);
void field_inv(const FieldElement& a, FieldElement& out);
// Constant-time scalar multiplication (branchless double-and-add)
void scalar_mul(const Point& base, const Scalar& k, Point& out);
// Complete addition formula (handles all edge cases without branching)
void point_add_complete(const Point& p, const Point& q, Point& out);
// Constant-time point doubling
void point_dbl(const Point& p, Point& out);
} // namespace secp256k1::fast::ct
[!] CT operations are ~5-7x slower than the fast variants. Use only for private key operations (signing, ECDH).
Address Generation API
Namespace: secp256k1
Header:
#include <secp256k1/address.hpp>
Address Types
All address functions take a Network enum (Mainnet or Testnet) and return a std::string.
P2PKH (Legacy, Base58Check)
// "1..." (mainnet) or "m/n..." (testnet)
std::string address_p2pkh(const fast::Point& pubkey,
Network net = Network::Mainnet);
P2WPKH (Native SegWit v0, Bech32)
// "bc1q..." (mainnet) or "tb1q..." (testnet)
std::string address_p2wpkh(const fast::Point& pubkey,
Network net = Network::Mainnet);
P2TR (Taproot, SegWit v1, Bech32m)
// "bc1p..." (mainnet) or "tb1p..." (testnet)
std::string address_p2tr(const fast::Point& internal_key,
Network net = Network::Mainnet);
// From raw 32-byte x-only output key
std::string address_p2tr_raw(const std::array<uint8_t, 32>& output_key_x,
Network net = Network::Mainnet);
P2SH-P2WPKH (Nested/Wrapped SegWit)
// "3..." (mainnet) -- wraps P2WPKH inside P2SH for backward compatibility (BIP-49)
std::string address_p2sh_p2wpkh(const fast::Point& pubkey,
Network net = Network::Mainnet);
P2SH (Pay-to-Script-Hash)
// "3..." (mainnet) -- from raw 20-byte script hash
std::string address_p2sh(const std::array<uint8_t, 20>& script_hash,
Network net = Network::Mainnet);
P2WSH (Witness Script Hash)
// "bc1q..." 32-byte program (SegWit v0)
std::string address_p2wsh(const std::array<uint8_t, 32>& witness_script_hash,
Network net = Network::Mainnet);
CashAddr (Bitcoin Cash BIP-0185)
// Encode hash160 as CashAddr (type: 0=P2PKH, 1=P2SH)
std::string cashaddr_encode(const std::array<uint8_t, 20>& hash,
const std::string& prefix,
uint8_t type = 0);
// CashAddr P2PKH from public key: "bitcoincash:q..."
std::string address_cashaddr(const fast::Point& pubkey,
const std::string& prefix = "bitcoincash");
Encoding Functions
Base58Check
std::string base58check_encode(const uint8_t* data, size_t len);
std::pair<std::vector<uint8_t>, bool> base58check_decode(const std::string& encoded);
Bech32 / Bech32m (BIP-173 / BIP-350)
std::string bech32_encode(const std::string& hrp,
uint8_t witness_version,
const uint8_t* witness_program,
size_t prog_len);
struct Bech32DecodeResult {
std::string hrp;
int witness_version; // -1 if invalid
std::vector<uint8_t> witness_program;
bool valid;
};
Bech32DecodeResult bech32_decode(const std::string& addr);
HASH160
// RIPEMD160(SHA256(data))
std::array<uint8_t, 20> hash160(const uint8_t* data, size_t len);
WIF (Wallet Import Format)
std::string wif_encode(const fast::Scalar& private_key,
bool compressed = true,
Network net = Network::Mainnet);
struct WIFDecodeResult {
fast::Scalar key;
bool compressed;
Network network;
bool valid;
};
WIFDecodeResult wif_decode(const std::string& wif);
BIP-352 Silent Payments
struct SilentPaymentAddress {
fast::Point scan_pubkey; // B_scan
fast::Point spend_pubkey; // B_spend
std::string encode(Network net = Network::Mainnet) const; // sp1q... or tsp1q...
};
// Generate silent payment address from scan/spend private keys
SilentPaymentAddress silent_payment_address(const fast::Scalar& scan_privkey,
const fast::Scalar& spend_privkey);
// Sender: compute unique output key for recipient
std::pair<fast::Point, fast::Scalar>
silent_payment_create_output(const std::vector<fast::Scalar>& input_privkeys,
const SilentPaymentAddress& recipient,
uint32_t k = 0);
// Receiver: scan transaction to detect addressed outputs (CT reference)
std::vector<std::pair<uint32_t, fast::Scalar>>
silent_payment_scan(const fast::Scalar& scan_privkey,
const fast::Scalar& spend_privkey,
const std::vector<fast::Point>& input_pubkeys,
const std::vector<std::array<uint8_t, 32>>& output_pubkeys);
// ── High-performance batch scanner ──────────────────────────────────────────
// Pre-aggregated transaction (A_eff already computed)
struct ScanTx {
fast::Point a_eff; // scan_sk × this = shared secret
std::vector<std::array<uint8_t, 32>> outputs; // x-only output pubkeys
};
// Raw transaction (input pubkeys not yet aggregated)
struct ScanTxRaw {
std::vector<fast::Point> input_pubkeys; // taproot-eligible inputs
std::array<uint8_t, 36> smallest_outpoint; // for input_hash (BIP-352 §A)
std::vector<std::array<uint8_t, 32>> outputs;
};
// Compute A_eff = input_hash × Σ input_pubkeys
// Uses multi_scalar_mul (Pippenger) for m > 1 inputs.
ScanTx compute_a_eff(const ScanTxRaw& raw);
// Batch scan: one KPlan recode, one GLV decompose, lockstep wNAF loop for all txs.
// Stage 1: batch_scalar_mul_fixed_k (fixed scan_sk, N variable a_eff points).
// Stage 2: hash × t×G comparison for each candidate output.
// Expected throughput: ~206K tx/s (16-core) — Stage 1 bottleneck ~9.1 µs/tx/core.
std::vector<std::pair<uint32_t, fast::Scalar>>
fast_scan_batch(const fast::Scalar& scan_privkey,
const fast::Scalar& spend_privkey,
const std::vector<ScanTx>& txs);
// ── batch_scalar_mul_generator (fixed-base, N scalars) ──────────────────────
// Computes results[i] = scalars[i] × G for all i in [0, n).
// One mutex lock for all N multiplications; thread-local digit scratch.
// Non-GLV path when config.enable_glv=false (default): fill_window_digits_into + accumulate.
// GLV path when enable_glv=true: split_scalar_internal + shamir_windowed_glv.
// ESP32: falls back to per-point scalar_mul_generator. n=1 fast-path skips batch setup.
void batch_scalar_mul_generator(const Scalar* scalars, Point* results, std::size_t n);
// ── KPlan + batch_scalar_mul_fixed_k (low-level) ────────────────────────────
// Precomputed GLV wNAF schedule for a fixed scalar.
// Computed once; reused for all N points in a batch.
struct KPlan { /* opaque */ };
// static method on fast::Point:
// static void batch_scalar_mul_fixed_k(const KPlan& plan,
// const Point* pts, size_t n,
// Point* results);
// One KPlan recode, lockstep wNAF loop, 1 field_inv per 2048-pt chunk.
// For N=1 falls back to scalar_mul_with_plan; non-FE52 builds use per-point fallback.
Multi-Chain Coins API
Namespace: secp256k1::coins
Headers:
#include <secp256k1/coins/coin_params.hpp>
#include <secp256k1/coins/coin_address.hpp>
Coin Parameters
28 coins predefined as constexpr CoinParams objects:
| Coin | Ticker | Encoding | BIP-44 | Chain ID |
|---|---|---|---|---|
| Bitcoin | BTC | Bech32 | 0 | -- |
| Litecoin | LTC | Bech32 | 2 | -- |
| Dogecoin | DOGE | Base58Check | 3 | -- |
| Dash | DASH | Base58Check | 5 | -- |
| Ethereum | ETH | EIP-55 | 60 | 1 |
| Bitcoin Cash | BCH | CashAddr | 145 | -- |
| Bitcoin SV | BSV | Base58Check | 236 | -- |
| Zcash | ZEC | Base58Check | 133 | -- |
| DigiByte | DGB | Bech32 | 20 | -- |
| Namecoin | NMC | Base58Check | 7 | -- |
| Peercoin | PPC | Base58Check | 6 | -- |
| Vertcoin | VTC | Bech32 | 28 | -- |
| Viacoin | VIA | Bech32 | 14 | -- |
| Groestlcoin | GRS | Bech32 | 17 | -- |
| Syscoin | SYS | Bech32 | 57 | -- |
| BNB Smart Chain | BNB | EIP-55 | 60 | 56 |
| Polygon | POL | EIP-55 | 60 | 137 |
| Avalanche | AVAX | EIP-55 | 60 | 43114 |
| Fantom | FTM | EIP-55 | 60 | 250 |
| Arbitrum | ARB | EIP-55 | 60 | 42161 |
| Optimism | OP | EIP-55 | 60 | 10 |
| Ravencoin | RVN | Base58Check | 175 | -- |
| Flux | FLUX | Base58Check | 19167 | -- |
| Qtum | QTUM | Base58Check | 2301 | -- |
| Horizen | ZEN | Base58Check | 121 | -- |
| Bitcoin Gold | BTG | Base58Check | 156 | -- |
| Komodo | KMD | Base58Check | 141 | -- |
| Tron | TRX | TRON_BASE58 | 195 | -- |
Lookup Functions
const CoinParams* find_by_coin_type(uint32_t coin_type); // Returns nullptr if not found
const CoinParams* find_by_ticker(const char* ticker); // Case-sensitive
Iteration
inline constexpr const CoinParams* ALL_COINS[]; // Array of all 28 coins
inline constexpr size_t ALL_COINS_COUNT = 28;
Coin Address Functions
// Default/preferred format for each coin (Bech32 for BTC, EIP-55 for ETH, etc.)
std::string coin_address(const fast::Point& pubkey, const CoinParams& coin,
bool testnet = false);
// Explicit format selection
std::string coin_address_p2pkh(const fast::Point& pubkey, const CoinParams& coin,
bool testnet = false);
std::string coin_address_p2wpkh(const fast::Point& pubkey, const CoinParams& coin,
bool testnet = false);
std::string coin_address_p2tr(const fast::Point& internal_key, const CoinParams& coin,
bool testnet = false);
std::string coin_address_p2sh_p2wpkh(const fast::Point& pubkey, const CoinParams& coin,
bool testnet = false);
std::string coin_address_p2sh(const std::array<uint8_t, 20>& script_hash,
const CoinParams& coin, bool testnet = false);
std::string coin_address_cashaddr(const fast::Point& pubkey, const CoinParams& coin,
bool testnet = false);
// WIF encoding with coin-specific prefix
std::string coin_wif_encode(const fast::Scalar& private_key, const CoinParams& coin,
bool compressed = true, bool testnet = false);
Functions return empty string if the coin does not support the requested format (e.g.,
coin_address_p2wpkhfor Dogecoin).
Coin Key Generation
struct CoinKeyPair {
fast::Scalar private_key;
fast::Point public_key;
std::string address; // Default format for coin
std::string wif; // WIF-encoded key (empty for EVM coins)
};
CoinKeyPair coin_derive(const fast::Scalar& private_key, const CoinParams& coin,
bool testnet = false, const CurveContext* ctx = nullptr);
Unified Wallet API
Namespace: secp256k1::coins::wallet
Header:
#include <secp256k1/coins/wallet.hpp>
Chain-agnostic facade over all address, signing, and recovery operations. Same API regardless of underlying chain.
Wallet Key Management
struct WalletKey {
fast::Scalar priv; // 32-byte private scalar
fast::Point pub; // Public key
};
// Create from raw 32-byte key. Returns (key, success).
std::pair<WalletKey, bool> from_private_key(const uint8_t* priv32);
// Export in chain-appropriate format:
// Bitcoin-family: WIF (Base58Check)
// EVM-family: 0x-prefixed hex
// Tron: raw hex
std::string export_private_key(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
std::string export_public_key_hex(const CoinParams& coin, const WalletKey& key);
Wallet Address Generation
// Default address format for coin
std::string get_address(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
// Explicit format selection
std::string get_address_p2pkh(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
std::string get_address_p2wpkh(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
std::string get_address_p2sh_p2wpkh(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
std::string get_address_p2tr(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
std::string get_address_cashaddr(const CoinParams& coin, const WalletKey& key,
bool testnet = false);
Wallet Signing
struct MessageSignature {
std::array<uint8_t, 32> r;
std::array<uint8_t, 32> s;
int recid; // Recovery ID (0-3)
uint64_t v; // EIP-155 v (EVM) or 27+recid (Bitcoin)
std::array<uint8_t, 65> to_rsv() const; // [r:32][s:32][v:1]
};
// Chain-aware message signing:
// Bitcoin: "\x18Bitcoin Signed Message:\n" prefix -> dSHA256
// Ethereum: "\x19Ethereum Signed Message:\n" prefix -> Keccak-256
MessageSignature sign_message(const CoinParams& coin, const WalletKey& key,
const uint8_t* msg, size_t msg_len);
// Sign raw 32-byte hash (no prefix, no hashing)
MessageSignature sign_hash(const CoinParams& coin, const WalletKey& key,
const uint8_t* hash32);
// Verify signed message against public key
bool verify_message(const CoinParams& coin, const fast::Point& pubkey,
const uint8_t* msg, size_t msg_len,
const MessageSignature& sig);
Wallet Recovery
// Recover public key from message + signature
std::pair<fast::Point, bool>
recover_signer(const CoinParams& coin,
const uint8_t* msg, size_t msg_len,
const MessageSignature& sig);
// Recover address string from message + signature
std::pair<std::string, bool>
recover_address(const CoinParams& coin,
const uint8_t* msg, size_t msg_len,
const MessageSignature& sig);
BIP-39 Mnemonic Seed Phrases
Namespace: secp256k1
Header:
#include <secp256k1/bip39.hpp>
BIP-39 mnemonic code for generating deterministic keys. Converts entropy to human-readable word sequences and derives 512-bit seeds compatible with BIP-32.
Mnemonic Generation
// Generate mnemonic from OS CSPRNG entropy (12 words = 16 bytes)
auto [mnemonic, ok] = secp256k1::bip39_generate(16);
// Generate from explicit entropy (24 words = 32 bytes)
uint8_t entropy[32] = { /* ... */ };
auto [mnemonic24, ok2] = secp256k1::bip39_generate(32, entropy);
std::pair<std::string, bool>
bip39_generate(std::size_t entropy_bytes,
const std::uint8_t* entropy_in = nullptr);
Parameters:
| Parameter | Description |
|---|---|
entropy_bytes | 16 (12 words), 20 (15), 24 (18), 28 (21), or 32 (24 words) |
entropy_in | Optional explicit entropy; nullptr = use OS CSPRNG |
Returns: {mnemonic_string, success}
Mnemonic Validation
bool valid = secp256k1::bip39_validate("abandon abandon ... about");
bool bip39_validate(const std::string& mnemonic);
Validates word count (12/15/18/21/24), word membership in BIP-39 English wordlist, and SHA-256 checksum.
Seed Derivation
auto [seed, ok] = secp256k1::bip39_mnemonic_to_seed(mnemonic, "my passphrase");
// seed is std::array<uint8_t, 64> -- pass to bip32_master_key()
std::pair<std::array<std::uint8_t, 64>, bool>
bip39_mnemonic_to_seed(const std::string& mnemonic,
const std::string& passphrase = "");
Uses PBKDF2-HMAC-SHA512 with 2048 iterations. Salt = "mnemonic" + passphrase.
Mnemonic to Entropy
auto [ent, ok] = secp256k1::bip39_mnemonic_to_entropy("abandon abandon ... about");
// ent.data = raw entropy bytes, ent.length = byte count (16-32)
struct Bip39Entropy {
std::array<std::uint8_t, 32> data{};
std::size_t length = 0;
};
std::pair<Bip39Entropy, bool>
bip39_mnemonic_to_entropy(const std::string& mnemonic);
PBKDF2-HMAC-SHA512
void pbkdf2_hmac_sha512(const std::uint8_t* password, std::size_t password_len,
const std::uint8_t* salt, std::size_t salt_len,
std::uint32_t iterations,
std::uint8_t* output, std::size_t output_len);
Exposed for direct use and testing. Used internally by bip39_mnemonic_to_seed().
Wordlist Access
const char* const* words = secp256k1::bip39_wordlist_english();
// words[0] == "abandon", words[2047] == "zoo"
Returns pointer to the 2048-word sorted English BIP-39 wordlist.
Message Signing API
Namespace: secp256k1::coins
Header:
#include <secp256k1/coins/message_signing.hpp>
Low-level Bitcoin message signing (BIP-137 / Electrum format).
Bitcoin Message Hash
// SHA256(SHA256("\x18Bitcoin Signed Message:\n" + varint(msg_len) + msg))
std::array<uint8_t, 32> bitcoin_message_hash(const uint8_t* msg, size_t msg_len);
Sign / Verify / Recover
RecoverableSignature bitcoin_sign_message(const uint8_t* msg, size_t msg_len,
const fast::Scalar& private_key);
bool bitcoin_verify_message(const uint8_t* msg, size_t msg_len,
const fast::Point& pubkey,
const ECDSASignature& sig);
std::pair<fast::Point, bool>
bitcoin_recover_message(const uint8_t* msg, size_t msg_len,
const ECDSASignature& sig, int recid);
Base64 Encoding
// 65-byte format: 1-byte header (recid + compression flag, range 27-34) + r + s
std::string bitcoin_sig_to_base64(const RecoverableSignature& rsig,
bool compressed = true);
struct BitcoinSigDecodeResult {
ECDSASignature sig;
int recid;
bool compressed;
bool valid;
};
BitcoinSigDecodeResult bitcoin_sig_from_base64(const std::string& base64);
Zero-Knowledge Proof API
Namespace: secp256k1::zk
Header:
#include <secp256k1/zk.hpp>
Knowledge Proof (Schnorr Sigma)
Non-interactive proof of knowledge of discrete log via Fiat-Shamir transform.
KnowledgeProof
struct KnowledgeProof {
std::array<uint8_t, 32> rx; // R.x (nonce point x-coordinate)
fast::Scalar s; // response scalar
std::array<uint8_t, 64> serialize() const;
static bool deserialize(const uint8_t* data64, KnowledgeProof& out);
};
knowledge_prove
// Prove: "I know x such that pubkey = x*G"
// Uses CT layer (constant-time). Nonce hedged with aux_rand.
KnowledgeProof knowledge_prove(
const fast::Scalar& secret,
const fast::Point& pubkey,
const std::array<uint8_t, 32>& msg,
const std::array<uint8_t, 32>& aux_rand);
knowledge_verify
// Verify: s*G == R + e*P (FAST path)
bool knowledge_verify(
const KnowledgeProof& proof,
const fast::Point& pubkey,
const std::array<uint8_t, 32>& msg);
knowledge_prove_base / knowledge_verify_base
Same as above but with an arbitrary base point (not just G):
KnowledgeProof knowledge_prove_base(
const fast::Scalar& secret,
const fast::Point& point, // point = secret * base
const fast::Point& base,
const std::array<uint8_t, 32>& msg,
const std::array<uint8_t, 32>& aux_rand);
bool knowledge_verify_base(
const KnowledgeProof& proof,
const fast::Point& point,
const fast::Point& base,
const std::array<uint8_t, 32>& msg);
DLEQ Proof (Discrete Log Equality)
Proves log_G(P) == log_H(Q) without revealing the secret.
DLEQProof
struct DLEQProof {
fast::Scalar e; // challenge
fast::Scalar s; // response
std::array<uint8_t, 64> serialize() const;
static bool deserialize(const uint8_t* data64, DLEQProof& out);
};
dleq_prove
// Prove: P = secret*G AND Q = secret*H (same secret)
// Uses CT layer. Nonce hedged with aux_rand.
DLEQProof dleq_prove(
const fast::Scalar& secret,
const fast::Point& G,
const fast::Point& H,
const fast::Point& P,
const fast::Point& Q,
const std::array<uint8_t, 32>& aux_rand);
dleq_verify
// Verify: s*G == R1 + e*P AND s*H == R2 + e*Q (FAST path)
bool dleq_verify(
const DLEQProof& proof,
const fast::Point& G,
const fast::Point& H,
const fast::Point& P,
const fast::Point& Q);
Bulletproof Range Proof
Proves committed value is in [0, ) without revealing the value. Based on Bulletproofs (Bunz et al., 2018). Logarithmic proof size.
RangeProof
static constexpr size_t RANGE_PROOF_BITS = 64;
static constexpr size_t RANGE_PROOF_LOG2 = 6;
struct RangeProof {
fast::Point A, S; // vector commitments
fast::Point T1, T2; // polynomial commitments
fast::Scalar tau_x, mu, t_hat; // scalar responses
std::array<fast::Point, 6> L; // inner product argument (log2(64) rounds)
std::array<fast::Point, 6> R;
fast::Scalar a, b; // final inner product scalars
};
range_prove
// Generate Bulletproof range proof for Pedersen commitment C = value*H + blinding*G
// value must be in [0, $2^{64}$). Uses CT layer.
RangeProof range_prove(
uint64_t value,
const fast::Scalar& blinding,
const PedersenCommitment& commitment,
const std::array<uint8_t, 32>& aux_rand);
range_verify
// Verify range proof (MSM-optimized, FAST path)
// Returns true if committed value is in [0, $2^{64}$)
bool range_verify(
const PedersenCommitment& commitment,
const RangeProof& proof);
ZK Batch Operations
batch_range_verify
// Batch-verify multiple range proofs (more efficient than individual verification)
// Returns true only if ALL proofs are valid.
bool batch_range_verify(
const PedersenCommitment* commitments,
const RangeProof* proofs,
size_t count);
batch_commit
// Batch-create Pedersen commitments
void batch_commit(
const fast::Scalar* values,
const fast::Scalar* blindings,
PedersenCommitment* commitments_out,
size_t count);
GeneratorVectors
struct GeneratorVectors {
std::array<fast::Point, 64> G; // nothing-up-my-sleeve generators
std::array<fast::Point, 64> H;
};
// Retrieve cached generator vectors (computed once on first call)
const GeneratorVectors& get_generator_vectors();
ZK Performance Summary
| Operation | Time | Throughput | Layer |
|---|---|---|---|
| Pedersen Commit | 33.0 us | 30.3K op/s | FAST |
| Knowledge Prove | 20.3 us | 49.3K op/s | CT |
| Knowledge Verify | 21.8 us | 46.0K op/s | FAST |
| DLEQ Prove | 40.0 us | 25.0K op/s | CT |
| DLEQ Verify | 56.4 us | 17.7K op/s | FAST |
| Range Prove (64b) | 13,467 us | 74 op/s | CT |
| Range Verify (64b) | 2,634 us | 380 op/s | FAST |
Measured on i7-14400F, 11 passes, pinned core, median.
SNARK Witness Generation
Foreign-field witness generators that decompose secp256k1 signature verification
into 5×52-bit limbs suitable for PLONK / Halo2 / Circom circuit wiring.
All fields are split into ForeignFieldLimbs (five uint64_t values, each ≤ ).
EcdsaSnarkWitness
Decomposes an ECDSA signature (r, s) on message msg under pubkey into
circuit-ready witness data: s_inv, u1 = msg·s⁻¹, u2 = r·s⁻¹, recovered
R point, and the full public key coordinates.
struct EcdsaSnarkWitness {
// --- public inputs (circuit "instance") ---
ForeignFieldLimbs msg; // H(m) mod n
ForeignFieldLimbs sig_r; // r
ForeignFieldLimbs pub_x; // pubkey.x (field element)
ForeignFieldLimbs pub_y; // pubkey.y (field element)
// --- private witness ---
ForeignFieldLimbs sig_s; // s
ForeignFieldLimbs s_inv; // s⁻¹ mod n
ForeignFieldLimbs u1; // msg·s⁻¹ mod n
ForeignFieldLimbs u2; // r·s⁻¹ mod n
ForeignFieldLimbs r_x; // R.x (recovered nonce point)
ForeignFieldLimbs r_y; // R.y
// canonical 32-byte encodings
std::array<uint8_t, 32> bytes_s_inv;
std::array<uint8_t, 32> bytes_u1;
std::array<uint8_t, 32> bytes_u2;
std::array<uint8_t, 32> bytes_r_x;
std::array<uint8_t, 32> bytes_r_y;
std::array<uint8_t, 32> bytes_pub_y;
bool valid; // false if signature fails verification
};
EcdsaSnarkWitness ecdsa_snark_witness(
const std::array<uint8_t, 32>& msg32,
const fast::Point& pubkey,
const fast::Scalar& sig_r,
const fast::Scalar& sig_s);
SchnorrSnarkWitness
Decomposes a BIP-340 Schnorr signature (R.x, s) on message msg under
pubkey_x (x-only) into circuit-ready witness data. Lifts both R and
pubkey to full affine points (even Y via lift_x_even), computes the
BIP-340 challenge e = H("BIP0340/challenge" || R.x || P.x || msg), and
verifies s·G = R + e·P.
struct SchnorrSnarkWitness {
// --- public inputs ---
ForeignFieldLimbs msg; // 32-byte message
ForeignFieldLimbs sig_r; // R.x (from sig64[0:32])
ForeignFieldLimbs sig_s; // s (from sig64[32:64])
ForeignFieldLimbs pub_x; // x-only pubkey
// --- private witness ---
ForeignFieldLimbs r_y; // lifted R.y (even)
ForeignFieldLimbs pub_y; // lifted P.y (even)
ForeignFieldLimbs e; // BIP-340 challenge scalar
// canonical 32-byte encodings
std::array<uint8_t, 32> bytes_r_y;
std::array<uint8_t, 32> bytes_pub_y;
std::array<uint8_t, 32> bytes_e;
bool valid; // false if signature fails BIP-340 verification
};
SchnorrSnarkWitness schnorr_snark_witness(
const std::array<uint8_t, 32>& msg32,
const std::array<uint8_t, 32>& pubkey_x,
const fast::Scalar& sig_r,
const fast::Scalar& sig_s);
CUDA API
Namespace: secp256k1::cuda
Header:
#include <secp256k1.cuh>
CUDA Data Structures
// Field element (4 x 64-bit limbs, little-endian)
struct FieldElement {
uint64_t limbs[4];
};
// Scalar (4 x 64-bit limbs)
struct Scalar {
uint64_t limbs[4];
};
// Jacobian point (X, Y, Z)
struct JacobianPoint {
FieldElement x;
FieldElement y;
FieldElement z;
bool infinity;
};
// Affine point (x, y)
struct AffinePoint {
FieldElement x;
FieldElement y;
};
// 32-bit view for optimized operations (zero-cost conversion)
struct MidFieldElement {
uint32_t limbs[8];
};
CUDA Field Operations
All functions are __device__ and can only be called from GPU kernels.
// Initialization
__device__ void field_set_zero(FieldElement* r);
__device__ void field_set_one(FieldElement* r);
// Comparison
__device__ bool field_is_zero(const FieldElement* a);
__device__ bool field_eq(const FieldElement* a, const FieldElement* b);
// Arithmetic
__device__ void field_add(const FieldElement* a, const FieldElement* b, FieldElement* r);
__device__ void field_sub(const FieldElement* a, const FieldElement* b, FieldElement* r);
__device__ void field_mul(const FieldElement* a, const FieldElement* b, FieldElement* r);
__device__ void field_sqr(const FieldElement* a, FieldElement* r);
__device__ void field_inv(const FieldElement* a, FieldElement* r);
__device__ void field_neg(const FieldElement* a, FieldElement* r);
// Domain conversion (Montgomery mode only)
__device__ void field_to_mont(const FieldElement* a, FieldElement* r);
__device__ void field_from_mont(const FieldElement* a, FieldElement* r);
CUDA Point Operations
// Initialization
__device__ void jacobian_set_infinity(JacobianPoint* p);
__device__ void jacobian_set_generator(JacobianPoint* p);
__device__ bool jacobian_is_infinity(const JacobianPoint* p);
// Point arithmetic
__device__ void jacobian_double(const JacobianPoint* p, JacobianPoint* r);
__device__ void jacobian_add(const JacobianPoint* p, const JacobianPoint* q, JacobianPoint* r);
__device__ void jacobian_add_mixed(const JacobianPoint* p, const AffinePoint* q, JacobianPoint* r);
// Scalar multiplication
__device__ void scalar_mul(const JacobianPoint* p, const Scalar* k, JacobianPoint* r);
__device__ void scalar_mul_generator(const Scalar* k, JacobianPoint* r);
// Conversion
__device__ void jacobian_to_affine(const JacobianPoint* p, AffinePoint* r);
CUDA Batch Operations
#include <batch_inversion.cuh>
// Batch field inversion (Montgomery's trick)
// Inverts n field elements using only 1 modular inversion + 3(n-1) multiplications
__device__ void batch_invert(FieldElement* elements, int n, FieldElement* scratch);
CUDA Hash Operations
#include <hash160.cuh>
// Compute HASH160 = RIPEMD160(SHA256(pubkey))
__device__ void hash160_compressed(const uint8_t pubkey[33], uint8_t hash[20]);
__device__ void hash160_uncompressed(const uint8_t pubkey[65], uint8_t hash[20]);
CUDA Signature Operations
World-first: No other open-source GPU library provides secp256k1 ECDSA + Schnorr sign/verify.
Data Structures
#include <ecdsa.cuh>
#include <schnorr.cuh>
#include <recovery.cuh>
// ECDSA signature (r, s as Scalars)
struct ECDSASignatureGPU {
Scalar r;
Scalar s;
};
// Schnorr BIP-340 signature (32-byte R x-coordinate + Scalar s)
struct SchnorrSignatureGPU {
uint8_t r[32]; // x-coordinate of R point
Scalar s;
};
// Recoverable ECDSA signature
struct RecoverableSignatureGPU {
ECDSASignatureGPU sig;
int recid; // Recovery ID (0-3)
};
Device Functions
// ECDSA Sign (RFC 6979 deterministic nonces, low-S normalization)
// Returns true on success
__device__ bool ecdsa_sign(
const uint8_t msg_hash[32], // 32-byte message hash
const Scalar* privkey, // Private key
ECDSASignatureGPU* sig // Output signature
);
// ECDSA Verify (Shamir's trick + GLV endomorphism)
// Returns true if signature is valid
__device__ bool ecdsa_verify(
const uint8_t msg_hash[32], // 32-byte message hash
const JacobianPoint* pubkey, // Public key (Jacobian)
const ECDSASignatureGPU* sig // Signature to verify
);
// ECDSA Sign with Recovery ID
__device__ bool ecdsa_sign_recoverable(
const uint8_t msg_hash[32],
const Scalar* privkey,
RecoverableSignatureGPU* sig // Output: signature + recid
);
// ECDSA Recover public key from signature
__device__ bool ecdsa_recover(
const uint8_t msg_hash[32],
const RecoverableSignatureGPU* sig,
JacobianPoint* pubkey // Output: recovered public key
);
// Schnorr Sign (BIP-340, tagged hash midstates for performance)
__device__ bool schnorr_sign(
const Scalar* privkey,
const uint8_t msg[32],
const uint8_t aux_rand[32], // Auxiliary randomness
SchnorrSignatureGPU* sig // Output signature
);
// Schnorr Verify (BIP-340, x-only pubkey)
__device__ bool schnorr_verify(
const uint8_t pubkey_x[32], // X-only public key (32 bytes)
const uint8_t msg[32],
const SchnorrSignatureGPU* sig
);
Batch Kernel Wrappers
Host-callable kernel wrappers for batch processing:
// Launch batch ECDSA sign (128 threads/block, 2 blocks/SM)
void ecdsa_sign_batch_kernel<<<blocks, 128>>>(
const uint8_t* msg_hashes, // N x 32 bytes
const Scalar* privkeys, // N scalars
ECDSASignatureGPU* sigs, // N output signatures
int count
);
// Launch batch ECDSA verify
void ecdsa_verify_batch_kernel<<<blocks, 128>>>(
const uint8_t* msg_hashes,
const JacobianPoint* pubkeys,
const ECDSASignatureGPU* sigs,
bool* results, // N output booleans
int count
);
// Launch batch Schnorr sign
void schnorr_sign_batch_kernel<<<blocks, 128>>>(
const Scalar* privkeys,
const uint8_t* msgs,
const uint8_t* aux_rands,
SchnorrSignatureGPU* sigs,
int count
);
// Launch batch Schnorr verify
void schnorr_verify_batch_kernel<<<blocks, 128>>>(
const uint8_t* pubkey_xs,
const uint8_t* msgs,
const SchnorrSignatureGPU* sigs,
bool* results,
int count
);
Performance
| Operation | Time/Op | Throughput |
|---|---|---|
| ECDSA Sign | 204.8 ns | 4.88 M/s |
| ECDSA Verify | 410.1 ns | 2.44 M/s |
| ECDSA Sign + Recid | 311.5 ns | 3.21 M/s |
| Schnorr Sign | 273.4 ns | 3.66 M/s |
| Schnorr Verify | 354.6 ns | 2.82 M/s |
RTX 5060 Ti, kernel-only timing, batch 16K
CUDA CT (Constant-Time) Operations
Namespace: secp256k1::cuda::ct
Header:
#include <ct/ct_sign.cuh> // CT ECDSA/Schnorr sign
#include <ct/ct_zk.cuh> // CT ZK knowledge/DLEQ prove
All CT functions use constant-time scalar multiplication and arithmetic to prevent
side-channel leakage. Proving operations are CT; verification uses the fast path
(public data only -- see zk.cuh).
CT Signing
// CT ECDSA sign (constant-time k*G, k^-1, scalar ops)
__device__ bool ct_ecdsa_sign(
const uint8_t msg_hash[32],
const Scalar* privkey,
ECDSASignatureGPU* sig
);
// CT Schnorr sign (constant-time nonce generation + signing)
__device__ bool ct_schnorr_sign(
const Scalar* privkey,
const uint8_t msg[32],
const uint8_t aux_rand[32],
SchnorrSignatureGPU* sig
);
// CT Schnorr keypair (x-only pubkey extraction)
__device__ void ct_schnorr_keypair_create(
const Scalar* privkey,
SchnorrKeypairGPU* keypair
);
CT Zero-Knowledge Proofs
Data Structures
#include <zk.cuh> // KnowledgeProofGPU, DLEQProofGPU
#include <ct/ct_zk.cuh> // CT proving functions
// Knowledge proof (Schnorr sigma protocol)
struct KnowledgeProofGPU {
uint8_t rx[32]; // R point x-coordinate
Scalar s; // Response scalar
};
// DLEQ proof (discrete log equality)
struct DLEQProofGPU {
Scalar e; // Challenge
Scalar s; // Response
};
Device Functions
// CT Knowledge Proof: proves knowledge of secret s such that P = s * B
// Uses CT scalar_mul for k*B (k is secret), CT scalar arithmetic
__device__ bool ct_knowledge_prove_device(
const Scalar* secret,
const JacobianPoint* pubkey, // P = secret * base
const JacobianPoint* base, // Arbitrary base point B
const uint8_t msg[32],
const uint8_t aux[32], // Auxiliary randomness (XOR hedging)
KnowledgeProofGPU* proof
);
// Convenience: prove for generator G
__device__ bool ct_knowledge_prove_generator_device(
const Scalar* secret,
const JacobianPoint* pubkey, // P = secret * G
const uint8_t msg[32],
const uint8_t aux[32],
KnowledgeProofGPU* proof
);
// CT DLEQ Proof: proves log_G(P) == log_H(Q) without revealing the log
// Two CT scalar_mul operations (k*G, k*H), 6-point challenge hash
__device__ bool ct_dleq_prove_device(
const Scalar* secret,
const JacobianPoint* G, // First base
const JacobianPoint* H, // Second base
const JacobianPoint* P, // P = secret * G
const JacobianPoint* Q, // Q = secret * H
const uint8_t aux[32],
DLEQProofGPU* proof
);
Batch Kernels
// Batch CT knowledge prove (one thread per proof)
__global__ void ct_knowledge_prove_batch_kernel(
const Scalar* secrets,
const JacobianPoint* pubkeys,
const JacobianPoint* bases,
const uint8_t* messages, // N * 32 bytes
const uint8_t* aux_rands, // N * 32 bytes
KnowledgeProofGPU* proofs,
bool* results,
uint32_t count
);
// Batch CT knowledge prove with generator G
__global__ void ct_knowledge_prove_generator_batch_kernel(
const Scalar* secrets,
const JacobianPoint* pubkeys,
const uint8_t* messages,
const uint8_t* aux_rands,
KnowledgeProofGPU* proofs,
bool* results,
uint32_t count
);
// Batch CT DLEQ prove
__global__ void ct_dleq_prove_batch_kernel(
const Scalar* secrets,
const JacobianPoint* G_pts,
const JacobianPoint* H_pts,
const JacobianPoint* P_pts,
const JacobianPoint* Q_pts,
const uint8_t* aux_rands,
DLEQProofGPU* proofs,
bool* results,
uint32_t count
);
Design note: Verification functions (
knowledge_verify_device,dleq_verify_device) live inzk.cuhand use the fast (variable-time) path -- verification inputs are public.
OpenCL ZK Operations
Kernel file: src/opencl/kernels/secp256k1_zk.cl
OpenCL ZK operations use the fast-path scalar multiplication (wNAF-5). OpenCL has no separate CT layer -- all operations are uniform across work-items by design.
// Device functions (called from kernels)
void zk_knowledge_prove_impl(...);
void zk_knowledge_verify_impl(...);
void zk_dleq_prove_impl(...);
void zk_dleq_verify_impl(...);
// Batch kernels
__kernel void zk_knowledge_prove_batch(...);
__kernel void zk_knowledge_verify_batch(...);
__kernel void zk_dleq_prove_batch(...);
__kernel void zk_dleq_verify_batch(...);
Metal ZK Operations
Shader file: src/metal/shaders/secp256k1_zk.h
Kernel file: src/metal/shaders/secp256k1_kernels.metal (Kernels 19-22)
Metal ZK uses branchless affine_select in scalar multiplication (semi-CT).
8x32 limb representation.
// Device functions
bool zk_knowledge_prove(...);
bool zk_knowledge_verify(...);
bool zk_dleq_prove(...);
bool zk_dleq_verify(...);
// Kernels 19-22
kernel void zk_knowledge_prove_batch(...) [[kernel]];
kernel void zk_knowledge_verify_batch(...) [[kernel]];
kernel void zk_dleq_prove_batch(...) [[kernel]];
kernel void zk_dleq_verify_batch(...) [[kernel]];
WASM API
Module: @ultrafastsecp256k1/wasm
Usage:
import { Secp256k1 } from './secp256k1.mjs';
const lib = await Secp256k1.create();
Functions
| Function | Parameters | Returns | Description |
|---|---|---|---|
selftest() | -- | boolean | Run built-in self-test |
version() | -- | string | Library version ("3.0.0") |
pubkeyCreate(seckey) | Uint8Array(32) | {x, y} | Public key from private key |
pointMul(px, py, scalar) | Uint8Array(32) x 3 | {x, y} | Scalar x Point |
pointAdd(px, py, qx, qy) | Uint8Array(32) x 4 | {x, y} | Point addition |
ecdsaSign(msgHash, seckey) | Uint8Array(32) x 2 | Uint8Array(64) | ECDSA sign (r‖s) |
ecdsaVerify(msgHash, pubX, pubY, sig) | Uint8Array(32) x 3 + Uint8Array(64) | boolean | ECDSA verify |
schnorrSign(seckey, msg, aux?) | Uint8Array(32) x 2-3 | Uint8Array(64) | Schnorr BIP-340 sign |
schnorrVerify(pubkeyX, msg, sig) | Uint8Array(32) x 2 + Uint8Array(64) | boolean | Schnorr verify |
schnorrPubkey(seckey) | Uint8Array(32) | Uint8Array(32) | X-only public key |
sha256(data) | Uint8Array | Uint8Array(32) | SHA-256 hash |
C API
For direct C/C++ or custom WASM bindings, see secp256k1_wasm.h.
Example
const lib = await Secp256k1.create();
console.log('v' + lib.version(), lib.selftest() ? 'OK' : 'X');
// ECDSA workflow
const privkey = new Uint8Array(32);
privkey[31] = 1;
const { x, y } = lib.pubkeyCreate(privkey);
const msgHash = lib.sha256(new TextEncoder().encode('Hello'));
const sig = lib.ecdsaSign(msgHash, privkey);
const valid = lib.ecdsaVerify(msgHash, x, y, sig);
See bindings/wasm/README.md for detailed build and usage instructions.
C ABI (ufsecp)
The stable C ABI is defined in include/ufsecp/ufsecp.h. This is the optional
libufsecp package for C callers, bindings, and explicit bridge consumers.
Native C++ integrations and the libsecp256k1-compatible shim should link the
engine target (secp256k1::fast / fastsecp256k1) directly. Top-level install
emits ufsecp.pc only with -DSECP256K1_INSTALL_CABI=ON; the default
secp256k1-fast.pc links the native engine library.
All functions follow these rules:
- Opaque context:
ufsecp_ctx*-- one per thread, or externally synchronised - Every function returns
ufsecp_error_t(0 = OK) - All I/O is
uint8_t[]with fixed sizes -- no internal types leak - Dual-layer CT: signing/nonce/key-tweak always use the CT layer; verify/point-arith use the fast layer. No opt-in flag.
- Caller owns all buffers -- library never allocates on behalf of caller (except
ctx_create/ctx_clone)
Security Properties (updated 2026-06-06)
- Strict private key parsing: All functions accepting a private key use
Scalar::parse_bytes_strict_nonzero(), which rejects keys>= nand== 0.Scalar::from_bytes()(silent mod-n reduction) is never used on secret inputs. - CT pubkey derivation:
ufsecp_pubkey_createand all functions that derive a public key from a private key usect::generator_mul(), not the variable-timePoint::generator().scalar_mul(). No secret value touches the fast scalar-mul path. - Degenerate output detection: All signing functions (
ufsecp_ecdsa_sign,ufsecp_schnorr_sign, their_verifiedand_batchvariants) check forr == 0,s == 0, and all-zero Schnorr R x-coordinate after signing. On degenerate output the output buffer is zeroed andUFSECP_ERR_INTERNALis returned — the zero signature is never serialized as success. - Fail-closed output buffers: Message-signing, FROST, Taproot, BIP39 seed, and BIP144 hash wrappers clear output buffers before processing. On any non-OK return, callers must treat the output as zeroed/invalid and must not reuse a previous value from the same buffer.
- BIP39 C ABI validation:
ufsecp_bip39_to_seedvalidates the mnemonic word list and checksum before PBKDF2. Invalid mnemonics returnUFSECP_ERR_BAD_INPUTand leaveseed64_outall zero. - BIP144 strict parser:
ufsecp_bip144_txidenforces minimal CompactSize encodings and consumes all witness stacks before the 4-byte locktime. Extra bytes between witness data and locktime returnUFSECP_ERR_BAD_INPUT. - Taproot tweak parsing: Taproot tweak scalars are parsed strictly;
t >= nis rejected, whilet == 0remains valid per BIP-341. A final infinity output key or zero tweaked private key returns a non-OK error. - Batch fail-closed: Batch sign functions clear all output slots before processing. A per-slot failure zeroes that slot's output bytes; partial success is not possible.
- Batch count == 0 rejected:
ufsecp_ecdsa_sign_batchandufsecp_schnorr_sign_batchreturnUFSECP_ERR_BAD_INPUTwhencount == 0.
Error Codes
| Code | Name | Value |
|---|---|---|
UFSECP_OK | Success | 0 |
UFSECP_ERR_NULL_ARG | NULL pointer argument | 1 |
UFSECP_ERR_BAD_KEY | Invalid private key | 2 |
UFSECP_ERR_BAD_PUBKEY | Invalid public key | 3 |
UFSECP_ERR_BAD_SIG | Invalid signature | 4 |
UFSECP_ERR_BAD_INPUT | Invalid input data | 5 |
UFSECP_ERR_VERIFY_FAIL | Verification failed | 6 |
UFSECP_ERR_ARITH | Arithmetic error | 7 |
UFSECP_ERR_SELFTEST | Self-test failure | 8 |
UFSECP_ERR_INTERNAL | Internal error | 9 |
UFSECP_ERR_BUF_TOO_SMALL | Output buffer too small | 10 |
Size Constants
| Constant | Value | Description |
|---|---|---|
UFSECP_PRIVKEY_LEN | 32 | Private key |
UFSECP_PUBKEY_COMPRESSED_LEN | 33 | Compressed public key |
UFSECP_PUBKEY_UNCOMPRESSED_LEN | 65 | Uncompressed public key |
UFSECP_PUBKEY_XONLY_LEN | 32 | x-only public key (BIP-340) |
UFSECP_SIG_COMPACT_LEN | 64 | Compact R||S / r||s |
UFSECP_ECDSA_OPAQUE_SIG_LEN | 64 | Copied secp256k1-compatible ECDSA opaque scalar storage |
UFSECP_SIG_DER_MAX_LEN | 72 | Maximum DER-encoded ECDSA sig |
UFSECP_HASH_LEN | 32 | SHA-256 / Keccak-256 digest |
UFSECP_HASH160_LEN | 20 | RIPEMD160(SHA256) digest |
UFSECP_SHARED_SECRET_LEN | 32 | ECDH shared secret |
UFSECP_BIP32_SERIALIZED_LEN | 78 | BIP-32 extended key |
Context Lifecycle
// Create context (runs self-test on first call)
ufsecp_ctx* ctx = NULL;
ufsecp_error_t err = ufsecp_ctx_create(&ctx);
// Clone context (deep copy, for multi-thread use)
ufsecp_ctx* ctx2 = NULL;
ufsecp_ctx_clone(ctx, &ctx2);
// Error inspection
ufsecp_error_t last = ufsecp_last_error(ctx);
const char* msg = ufsecp_last_error_msg(ctx);
// FFI layout assertion
size_t sz = ufsecp_ctx_size();
// Point the engine at your own fixed-base cache directory (replaces config.ini).
// NULL or "" => current working directory. Call before the first ctx_create.
ufsecp_set_cache_dir("/var/lib/myapp/ufsecp_cache");
// Destroy (NULL-safe)
ufsecp_ctx_destroy(ctx);
| Function | Signature | Description |
|---|---|---|
ufsecp_ctx_create | (ufsecp_ctx** ctx_out) -> error_t | Create new context |
ufsecp_ctx_clone | (const ctx*, ufsecp_ctx** ctx_out) -> error_t | Deep copy context |
ufsecp_ctx_destroy | (ctx*) -> void | Free context (NULL-safe) |
ufsecp_last_error | (const ctx*) -> error_t | Last error code |
ufsecp_last_error_msg | (const ctx*) -> const char* | Last error message |
ufsecp_ctx_size | (void) -> size_t | Compiled ctx struct size |
ufsecp_set_cache_dir | (const char* dir|NULL) -> error_t | Set fixed-base cache directory (replaces config.ini); NULL/"" = CWD. Process-global |
ufsecp_context_randomize | (ctx, seed32[32]|NULL) -> error_t | Install scalar blinding (thread-local); NULL clears |
Private Key Operations
| Function | Signature | Description |
|---|---|---|
ufsecp_seckey_verify | (ctx, privkey[32]) -> error_t | Validate private key (non-zero, < n) |
ufsecp_seckey_negate | (ctx, privkey[32]) -> error_t | Negate in-place: key <- -key mod n |
ufsecp_seckey_tweak_add | (ctx, privkey[32], tweak[32]) -> error_t | key <- (key + tweak) mod n |
ufsecp_seckey_tweak_mul | (ctx, privkey[32], tweak[32]) -> error_t | key <- (key * tweak) mod n |
Public Key Operations
| Function | Signature | Description |
|---|---|---|
ufsecp_pubkey_create | (ctx, privkey[32], pubkey33_out[33]) -> error_t | Compressed pubkey from privkey (CT path; rejects key >= n or == 0) |
ufsecp_pubkey_create_uncompressed | (ctx, privkey[32], pubkey65_out[65]) -> error_t | Uncompressed pubkey from privkey (CT path; rejects key >= n or == 0) |
ufsecp_pubkey_parse | (ctx, input, input_len, pubkey33_out[33]) -> error_t | Parse 33 or 65 bytes to compressed |
ufsecp_pubkey_xonly | (ctx, privkey[32], xonly32_out[32]) -> error_t | x-only pubkey (BIP-340) |
ufsecp_pubkey_add | (ctx, a33[33], b33[33], out33[33]) -> error_t | Point addition: out = a + b |
ufsecp_pubkey_negate | (ctx, pubkey33[33], out33[33]) -> error_t | Point negation: out = -P |
ufsecp_pubkey_tweak_add | (ctx, pubkey33[33], tweak[32], out33[33]) -> error_t | out = P + tweak*G |
ufsecp_pubkey_tweak_mul | (ctx, pubkey33[33], tweak[32], out33[33]) -> error_t | out = tweak * P |
ufsecp_pubkey_combine | (ctx, pubkeys, n, out33[33]) -> error_t | Sum N compressed pubkeys |
ECDSA
| Function | Signature | Description |
|---|---|---|
ufsecp_ecdsa_sign | (ctx, msg32[32], privkey[32], sig64_out[64]) -> error_t | Sign (RFC 6979, low-S, CT path; rejects key >= n or == 0; returns UFSECP_ERR_INTERNAL on r==0 or s==0, zeroes output) |
ufsecp_ecdsa_sign_verified | (ctx, msg32[32], privkey[32], sig64_out[64]) -> error_t | Sign + verify (fault resistance; same CT and degenerate-output guarantees as ufsecp_ecdsa_sign) |
ufsecp_ecdsa_sign_batch | (ctx, n, msgs32[], privkeys32[], sigs64_out[]) -> error_t | CPU CT batch sign; rejects n == 0; clears all output slots before processing; per-slot failure zeroes that slot; repeats stable 32/32/64-byte item layout per entry |
ufsecp_ecdsa_verify | (ctx, msg32[32], sig64[64], pubkey33[33]) -> error_t | Verify compact signature |
ufsecp_ecdsa_sig_compact_to_opaque | (ctx, sig64[64], opaque64_out[64]) -> error_t | Convert compact ECDSA r||s to secp256k1-compatible opaque scalar storage; does not normalize |
ufsecp_ecdsa_sig_opaque_to_compact | (ctx, opaque64[64], sig64_out[64]) -> error_t | Convert opaque scalar storage to compact r||s; does not normalize |
ufsecp_ecdsa_sig_normalize_opaque | (ctx, opaque64[64], opaque64_out[64], changed_out*) -> error_t | Low-S normalize opaque storage; input/output may alias; changed_out is optional |
ufsecp_ecdsa_verify_opaque | (ctx, msg32[32], opaque64[64], pubkey33[33]) -> error_t | Verify copied secp256k1-compatible opaque ECDSA storage; normalizes high-S internally before verify |
ufsecp_ecdsa_verify_opaque_batch | (ctx, msgs32[], pubs33[], opaque_sigs64[], n, results_out[]) -> error_t | Per-row opaque ECDSA verify from columns; malformed rows return result 0 without aborting the whole batch |
ufsecp_ecdsa_verify_opaque_rows | (ctx, rows, stride, n, results_out[]) -> error_t | Per-row opaque ECDSA verify from strided rows: `msg32 |
ufsecp_ecdsa_sig_to_der | (ctx, sig64[64], der_out, der_len*) -> error_t | Compact to DER encoding |
ufsecp_ecdsa_sig_from_der | (ctx, der, der_len, sig64_out[64]) -> error_t | DER to compact encoding |
ufsecp_ecdsa_sign_recoverable | (ctx, msg32, privkey, sig64_out, recid_out*) -> error_t | Sign with recovery id (0-3) |
ufsecp_ecdsa_recover | (ctx, msg32, sig64, recid, pubkey33_out[33]) -> error_t | Recover pubkey from recoverable sig |
Opaque ECDSA support is for integrations whose public signature value stores
copied secp256k1_ecdsa_signature scalar bytes. ufsecp_ecdsa_verify remains a
strict compact r||s verifier; the opaque verify APIs parse the scalar storage,
apply low-S normalization, and then verify, matching libsecp256k1's
secp256k1_ecdsa_signature_normalize(...) plus verify path.
Schnorr / BIP-340
| Function | Signature | Description |
|---|---|---|
ufsecp_schnorr_sign | (ctx, msg32, privkey, aux_rand[32], sig64_out) -> error_t | BIP-340 sign (CT path; rejects key >= n or == 0; returns UFSECP_ERR_INTERNAL on s==0 or R x-coord all-zeros, zeroes output; aux_rand=zeros for deterministic) |
ufsecp_schnorr_sign_verified | (ctx, msg32, privkey, aux_rand, sig64_out) -> error_t | Sign + verify (fault resistance; same CT and degenerate-output guarantees as ufsecp_schnorr_sign) |
ufsecp_schnorr_sign_batch | (ctx, n, msgs32[], privkeys32[], aux_rands32[], sigs64_out[]) -> error_t | CPU CT batch sign; rejects n == 0; clears all output slots before processing; per-slot failure zeroes that slot; aux_rands32 is required — NULL is rejected with UFSECP_ERR_NULL_ARG (pass a zero-filled buffer to opt out of hedging) |
ufsecp_schnorr_verify | (ctx, msg32, sig64, pubkey_x[32]) -> error_t | Verify BIP-340 signature |
ECDH
| Function | Signature | Description |
|---|---|---|
ufsecp_ecdh | (ctx, privkey, pubkey33, secret32_out) -> error_t | SHA256(compressed shared point) |
ufsecp_ecdh_xonly | (ctx, privkey, pubkey33, secret32_out) -> error_t | SHA256(x-coordinate) |
ufsecp_ecdh_raw | (ctx, privkey, pubkey33, secret32_out) -> error_t | Raw x-coordinate (no hash) |
Hashing
| Function | Signature | Description |
|---|---|---|
ufsecp_sha256 | (data, len, digest32_out) -> error_t | SHA-256 (HW-accel when available) |
ufsecp_sha512 | (data, len, digest64_out) -> error_t | SHA-512 |
ufsecp_hash160 | (data, len, digest20_out) -> error_t | RIPEMD160(SHA256) |
ufsecp_tagged_hash | (tag, data, len, digest32_out) -> error_t | BIP-340 tagged hash |
Addresses and WIF
| Function | Signature | Description |
|---|---|---|
ufsecp_addr_p2pkh | (ctx, pubkey33, network, addr_out, addr_len*) -> error_t | P2PKH (Base58) |
ufsecp_addr_p2wpkh | (ctx, pubkey33, network, addr_out, addr_len*) -> error_t | P2WPKH (Bech32, SegWit v0) |
ufsecp_addr_p2tr | (ctx, internal_key_x[32], network, addr_out, addr_len*) -> error_t | P2TR (Bech32m, Taproot) |
ufsecp_wif_encode | (ctx, privkey, compressed, network, wif_out, wif_len*) -> error_t | Private key to WIF |
ufsecp_wif_decode | (ctx, wif, privkey32_out, compressed_out*, network_out*) -> error_t | WIF to private key |
Network constants: UFSECP_NET_MAINNET (0), UFSECP_NET_TESTNET (1).
BIP-32 HD Keys
Opaque key type: ufsecp_bip32_key (82 bytes, contains 78-byte serialised key + metadata).
| Function | Signature | Description |
|---|---|---|
ufsecp_bip32_master | (ctx, seed, seed_len, key_out*) -> error_t | Master key from seed (16-64 bytes) |
ufsecp_bip32_derive | (ctx, parent*, index, child_out*) -> error_t | Single child derivation (>=0x80000000 = hardened) |
ufsecp_bip32_derive_path | (ctx, master*, path_str, key_out*) -> error_t | Full path, e.g. "m/44'/0'/0'/0/0" |
ufsecp_bip32_privkey | (ctx, key*, privkey32_out) -> error_t | Extract 32-byte private key |
ufsecp_bip32_pubkey | (ctx, key*, pubkey33_out) -> error_t | Extract 33-byte compressed public key |
Taproot / BIP-341
| Function | Signature | Description |
|---|---|---|
ufsecp_taproot_output_key | (ctx, internal_x[32], merkle_root, output_x_out[32], parity_out*) -> error_t | Derive Taproot output key |
ufsecp_taproot_tweak_seckey | (ctx, privkey[32], merkle_root, tweaked32_out[32]) -> error_t | Tweak privkey for key-path spend |
ufsecp_taproot_verify | (ctx, output_x, parity, internal_x, merkle_root, len) -> error_t | Verify Taproot commitment |
Taproot tweak helpers reject merkle_root == NULL when a positive root length is
specified, reject roots longer than 32 bytes, strict-parse tweak scalars, and
clear output buffers before returning input or internal errors.
BIP-39 Mnemonics
| Function | Signature | Description |
|---|---|---|
ufsecp_bip39_generate | (ctx, entropy_bytes, entropy_in, mnemonic_out, mnemonic_len*) -> error_t | Generate mnemonic (12/15/18/21/24 words) |
ufsecp_bip39_validate | (ctx, mnemonic) -> error_t | Validate mnemonic (checksum + wordlist) |
ufsecp_bip39_to_seed | (ctx, mnemonic, passphrase, seed64_out) -> error_t | Mnemonic to 64-byte seed (PBKDF2) |
ufsecp_bip39_to_entropy | (ctx, mnemonic, entropy_out, entropy_len*) -> error_t | Mnemonic back to raw entropy |
Entropy sizes: 16 (12 words), 20 (15), 24 (18), 28 (21), 32 (24 words). Pass entropy_in=NULL for random.
Batch Verification
| Function | Signature | Description |
|---|---|---|
ufsecp_schnorr_batch_verify | (ctx, entries, n) -> error_t | Verify N Schnorr sigs. Entry: 32 xonly + 32 msg + 64 sig = 128 bytes |
ufsecp_ecdsa_batch_verify | (ctx, entries, n) -> error_t | Verify N ECDSA sigs (single-threaded). Entry: 32 msg + 33 pubkey + 64 sig = 129 bytes |
ufsecp_ecdsa_batch_verify_mt | (ctx, entries, n, max_threads) -> error_t | Same as ufsecp_ecdsa_batch_verify but verifies across CPU threads (engine-owned parallelism). max_threads: 0 = auto (hardware_concurrency, no arbitrary upper cap), 1 = serial. Result identical to serial for any thread count (verify is variable-time over public data) |
ufsecp_schnorr_batch_identify_invalid | (ctx, entries, n, invalid_out, invalid_count*) -> error_t | Find indices of invalid Schnorr sigs |
ufsecp_ecdsa_batch_identify_invalid | (ctx, entries, n, invalid_out, invalid_count*) -> error_t | Find indices of invalid ECDSA sigs |
The C++ engine also provides secp256k1::schnorr_batch_verify_mt(entries, n, max_threads),
the multi-threaded twin of ecdsa_batch_verify_mt (same chunked design, 0=auto/1=serial,
result identical to serial for any thread count).
Standard integration surface (CPU): for drop-in
libsecp256k1compatibility, prefer the shim batch extension incompat/libsecp256k1_shim/include/secp256k1_batch.h(secp256k1_ecdsa_verify_batch[_mt|_results]/secp256k1_schnorrsig_verify_batch[_mt|_results]), which wraps the engine MT path with callermax_threadscontrol and optional per-row results. Every function also takes a trailingconst ufsecp_cancel_token* cancel(shared type inufsecp/ufsecp_cancel.h, same as the libbitcoin bridge; C++ defaultNULL= no cancellation, zero overhead) so a long batch can be aborted from outside; on cancel it returns0(fail-closed). SeeINTEGRATION_MODELS.md. Theufsecp_*batch entries above are the native (non-shim) ABI; thelibbitcoin_bridgebelow is the advanced GPU/zero-copy tier.
Libbitcoin Bridge
The optional compat/libbitcoin_bridge C ABI exposes one controller and two
script-signature batch layouts. Both layouts return identical per-row verdicts
and keep ECDSA and Schnorr batches homogeneous.
| Function | Signature | Description |
|---|---|---|
ufsecp_lbtc_ctrl_create | (ctrl**, backend) -> error_t | Create bridge controller; AUTO binds GPU when available, otherwise CPU |
ufsecp_lbtc_verify_ecdsa_mt / _ecdsa_opaque_mt / _ecdsa_compact_mt / _schnorr_mt | (ctrl, rows, n, key_size, results, invalid_idx, invalid_cap, invalid_count, max_threads, cancel=NULL) | Multi-threaded twins of the packed-row verify entries. max_threads: 0=auto (all cores), 1=serial (identical to the non-mt fn), N=cap. Identical per-row verdict; GPU path unaffected (governs the CPU fallback only); cancellation preserved. _compact_mt's CPU fallback is per-row/serial — prefer _opaque_mt for MT. |
ufsecp_lbtc_verify_ecdsa | (ctrl, rows, n, key_size, results, invalid_idx, invalid_cap, invalid_count, cancel=NULL) | Packed-row ECDSA verify; row = `hash32 |
ufsecp_lbtc_verify_schnorr | (ctrl, rows, n, key_size, results, invalid_idx, invalid_cap, invalid_count, cancel=NULL) | Packed-row Schnorr verify; row = `hash32 |
ufsecp_lbtc_verify_ecdsa_columns | (ctrl, hashes32, pubkeys33, sigs64, n, results, cancel=NULL) | Columnar ECDSA verify; avoids bridge-side row-to-column de-interleave |
ufsecp_lbtc_verify_schnorr_columns | (ctrl, hashes32, pubkeys_x32, sigs64, n, results, cancel=NULL) | Columnar Schnorr verify; avoids bridge-side row-to-column de-interleave |
ufsecp_lbtc_verify_ecdsa_collect | (ctrl, rows, n, key_size, cancel=NULL) | Packed-row collect; valid rows zero the row tail, invalid rows keep it |
ufsecp_lbtc_verify_schnorr_collect | (ctrl, rows, n, key_size, cancel=NULL) | Packed-row Schnorr collect |
ufsecp_lbtc_verify_ecdsa_collect_mt / _schnorr_collect_mt | (ctrl, rows, n, key_size, max_threads, cancel=NULL) | Multi-threaded twins of the collect entries (same verdict/key-cell semantics; max_threads 0=auto, 1=serial, N=cap). |
ufsecp_lbtc_verify_ecdsa_columns_collect | (ctrl, hashes32, pubkeys33, sigs64, n, key_cells, key_size, cancel=NULL) | Columnar collect; valid rows zero key_cells[i], invalid rows keep it |
ufsecp_lbtc_verify_schnorr_columns_collect | (ctrl, hashes32, pubkeys_x32, sigs64, n, key_cells, key_size, cancel=NULL) | Columnar Schnorr collect |
Use the packed-row API when the producer naturally stores [record | key]
tuples. Use the columnar API when the producer already stores
hashes[], pubkeys[], sigs[], and optional key_cells[]; on GPU builds the
bridge forwards these columns directly to the existing GPU C ABI instead of
building temporary columns from rows. The opaque key bytes are correlation
metadata only and are never interpreted by the bridge.
All libbitcoin verify/collect entry points accept a trailing
const ufsecp_cancel_token* cancel argument. NULL preserves the old behavior.
When the token callback returns non-zero, the call returns UFSECP_ERR_CANCELLED;
callers must discard partial results or collect-key state. C++ callers may omit
the argument because the header supplies a default NULL; plain C callers pass
the final argument explicitly.
Multi-Scalar Multiplication
| Function | Signature | Description |
|---|---|---|
ufsecp_shamir_trick | (ctx, a[32], P33[33], b[32], Q33[33], out33[33]) -> error_t | Compute aP + bQ |
ufsecp_multi_scalar_mul | (ctx, scalars, points, n, out33[33]) -> error_t | Compute sum(scalars[i] * points[i]) |
Scalars: 32-byte big-endian, contiguous. Points: 33-byte compressed, contiguous.
MuSig2 / BIP-327
Size constants: UFSECP_MUSIG2_PUBNONCE_LEN (66), UFSECP_MUSIG2_AGGNONCE_LEN (66), UFSECP_MUSIG2_KEYAGG_LEN (165), UFSECP_MUSIG2_SESSION_LEN (165), UFSECP_MUSIG2_SECNONCE_LEN (64).
| Function | Signature | Description |
|---|---|---|
ufsecp_musig2_key_agg | (ctx, pubkeys, n, keyagg_out, agg_pubkey32_out) -> error_t | Aggregate x-only pubkeys |
ufsecp_musig2_nonce_gen | (ctx, privkey, pubkey32, agg_pubkey32, msg32, extra_in, secnonce_out, pubnonce_out) -> error_t | Generate nonce pair |
ufsecp_musig2_nonce_agg | (ctx, pubnonces, n, aggnonce_out) -> error_t | Aggregate public nonces |
ufsecp_musig2_start_sign_session | (ctx, aggnonce, keyagg, msg32, session_out) -> error_t | Start signing session |
ufsecp_musig2_partial_sign | (ctx, secnonce, privkey, keyagg, session, signer_idx, partial_sig32_out) -> error_t | Produce partial signature |
ufsecp_musig2_partial_verify | (ctx, partial_sig32, pubnonce, pubkey32, keyagg, session, signer_idx) -> error_t | Verify partial signature |
ufsecp_musig2_partial_sig_agg | (ctx, partial_sigs, n, session, sig64_out) -> error_t | Aggregate partials to final BIP-340 sig |
FROST Threshold Signatures
Size constants: UFSECP_FROST_SHARE_LEN (36), UFSECP_FROST_KEYPKG_LEN (141), UFSECP_FROST_NONCE_LEN (64), UFSECP_FROST_NONCE_COMMIT_LEN (70).
Input invariants enforced by the current implementation:
- participant IDs are 1-based;
id == 0is rejected during keygen/finalize and yields a zero Lagrange coefficient - signer/commitment/share sender sets must be unique; duplicate signer IDs are treated as invalid and fail closed
- FROST keygen polynomial coefficients are derived from the pair
(participant_id, coeff_index)rather than the old arithmeticid*1000+jscheme, preventing cross-participant coefficient collisions
| Function | Signature | Description |
|---|---|---|
ufsecp_frost_keygen_begin | (ctx, id, threshold, n, seed, commits_out, commits_len*, shares_out, shares_len*) -> error_t | Key generation phase 1 |
ufsecp_frost_keygen_finalize | (ctx, id, all_commits, commits_len, shares, shares_len, t, n, keypkg_out) -> error_t | Key generation phase 2 |
ufsecp_frost_sign_nonce_gen | (ctx, id, nonce_seed, nonce_out, nonce_commit_out) -> error_t | Generate signing nonce |
ufsecp_frost_sign | (ctx, keypkg, nonce, msg32, nonce_commits, n_signers, partial_sig_out[36]) -> error_t | Produce partial signature |
ufsecp_frost_verify_partial | (ctx, partial_sig[36], verification_share33[33], nonce_commits, n_signers, msg32, group_pubkey32) -> error_t | Verify partial signature |
ufsecp_frost_aggregate | (ctx, partial_sigs, n, nonce_commits, n_signers, group_pubkey32, msg32, sig64_out) -> error_t | Aggregate to final Schnorr sig |
Adaptor Signatures
Size constants: UFSECP_SCHNORR_ADAPTOR_SIG_LEN (97), UFSECP_ECDSA_ADAPTOR_SIG_LEN (130).
Current adaptor invariants:
- Schnorr adaptor signatures remain bound to the supplied adaptor point through the challenge computed over
R^ + T - Schnorr adaptor verification rejects pre-signatures whose reconstructed
R = R^ + T_adjhas odd Y parity, matching the BIP-340 even-Y requirement for the final adapted signature - ECDSA adaptor signatures use a multiplicative adapt/extract flow:
adaptdivides out the adaptor secret andextractrecovers the same secret from(pre_sig, sig) - ECDSA adaptor verification is adaptor-point bound; verifying the same pre-signature against the wrong adaptor point fails
Schnorr Adaptor:
| Function | Signature | Description |
|---|---|---|
ufsecp_schnorr_adaptor_sign | (ctx, privkey, msg32, adaptor33, aux_rand, pre_sig_out[97]) -> error_t | Pre-sign |
ufsecp_schnorr_adaptor_verify | (ctx, pre_sig[97], pubkey_x, msg32, adaptor33) -> error_t | Verify pre-signature |
ufsecp_schnorr_adaptor_adapt | (ctx, pre_sig[97], secret[32], sig64_out) -> error_t | Adapt to valid signature |
ufsecp_schnorr_adaptor_extract | (ctx, pre_sig[97], sig64, secret32_out) -> error_t | Extract adaptor secret |
ECDSA Adaptor:
| Function | Signature | Description |
|---|---|---|
ufsecp_ecdsa_adaptor_sign | (ctx, privkey, msg32, adaptor33, pre_sig_out[130]) -> error_t | Pre-sign |
ufsecp_ecdsa_adaptor_verify | (ctx, pre_sig[130], pubkey33, msg32, adaptor33) -> error_t | Verify pre-signature |
ufsecp_ecdsa_adaptor_adapt | (ctx, pre_sig[130], secret[32], sig64_out) -> error_t | Adapt to valid signature |
ufsecp_ecdsa_adaptor_extract | (ctx, pre_sig[130], sig64, secret32_out) -> error_t | Extract adaptor secret |
Pedersen Commitments
| Function | Signature | Description |
|---|---|---|
ufsecp_pedersen_commit | (ctx, value[32], blinding[32], commitment33_out) -> error_t | C = valueH + blindingG |
ufsecp_pedersen_verify | (ctx, commitment33, value[32], blinding[32]) -> error_t | Verify commitment |
ufsecp_pedersen_verify_sum | (ctx, pos, n_pos, neg, n_neg) -> error_t | Verify balance: sum(pos) == sum(neg) |
ufsecp_pedersen_blind_sum | (ctx, blinds_in, n_in, blinds_out, n_out, sum32_out) -> error_t | Compute blinding factor sum |
ufsecp_pedersen_switch_commit | (ctx, value, blinding, switch_blind, commitment33_out) -> error_t | Switch commitment (3-gen) |
Zero-Knowledge Proofs
Size constants: UFSECP_ZK_KNOWLEDGE_PROOF_LEN (64), UFSECP_ZK_DLEQ_PROOF_LEN (64), UFSECP_ZK_RANGE_PROOF_MAX_LEN (675).
| Function | Signature | Description |
|---|---|---|
ufsecp_zk_knowledge_prove | (ctx, secret, pubkey33, msg32, aux_rand, proof_out[64]) -> error_t | Prove knowledge of discrete log |
ufsecp_zk_knowledge_verify | (ctx, proof[64], pubkey33, msg32) -> error_t | Verify knowledge proof |
ufsecp_zk_dleq_prove | (ctx, secret, G33, H33, P33, Q33, aux_rand, proof_out[64]) -> error_t | Prove DLEQ: logG(P) == logH(Q) |
ufsecp_zk_dleq_verify | (ctx, proof[64], G33, H33, P33, Q33) -> error_t | Verify DLEQ proof |
ufsecp_zk_range_prove | (ctx, value, blinding, commitment33, aux_rand, proof_out, proof_len*) -> error_t | Bulletproof range proof |
ufsecp_zk_range_verify | (ctx, commitment33, proof, proof_len) -> error_t | Verify Bulletproof range proof |
ufsecp_zk_ecdsa_snark_witness | (ctx, msg32, pubkey33, sig64, witness_out*) -> error_t | ECDSA SNARK witness (760 bytes) — ForeignFieldLimbs decomposition for PLONK/Halo2 |
ufsecp_zk_schnorr_snark_witness | (ctx, msg32, pubkey_x32, sig64, witness_out*) -> error_t | BIP-340 Schnorr SNARK witness (472 bytes) — ForeignFieldLimbs decomposition for PLONK/Halo2 |
Multi-Coin Wallet
Coin type constants (BIP-44): UFSECP_COIN_BITCOIN (0), UFSECP_COIN_LITECOIN (2), UFSECP_COIN_DOGECOIN (3), UFSECP_COIN_DASH (5), UFSECP_COIN_ETHEREUM (60), UFSECP_COIN_BITCOIN_CASH (145), UFSECP_COIN_TRON (195).
| Function | Signature | Description |
|---|---|---|
ufsecp_coin_address | (ctx, pubkey33, coin_type, testnet, addr_out, addr_len*) -> error_t | Default address for any coin |
ufsecp_coin_derive_from_seed | (ctx, seed, seed_len, coin_type, account, change, index, testnet, privkey_out, pubkey_out, addr_out, addr_len*) -> error_t | Full derivation from seed |
ufsecp_coin_wif_encode | (ctx, privkey, coin_type, testnet, wif_out, wif_len*) -> error_t | WIF for any coin |
ufsecp_btc_message_sign | (ctx, msg, msg_len, privkey, base64_out, base64_len*) -> error_t | Bitcoin message sign (BIP-137) |
ufsecp_btc_message_verify | (ctx, msg, msg_len, pubkey33, base64_sig) -> error_t | Bitcoin message verify |
ufsecp_btc_message_hash | (msg, msg_len, digest32_out) -> error_t | Bitcoin message hash |
Ethereum
Available when built with -DSECP256K1_BUILD_ETHEREUM=ON (default ON).
| Function | Signature | Description |
|---|---|---|
ufsecp_keccak256 | (data, len, digest32_out) -> error_t | Keccak-256 hash |
ufsecp_eth_address | (ctx, pubkey33, addr20_out) -> error_t | 20-byte Ethereum address |
ufsecp_eth_address_checksummed | (ctx, pubkey33, addr_out, addr_len*) -> error_t | EIP-55 checksummed address string |
ufsecp_eth_personal_hash | (msg, msg_len, digest32_out) -> error_t | EIP-191 personal_sign hash |
ufsecp_eth_sign | (ctx, msg32, privkey, r_out, s_out, v_out*, chain_id) -> error_t | ECDSA with recovery (EIP-155 v) |
ufsecp_eth_ecrecover | (ctx, msg32, r, s, v, addr20_out) -> error_t | Recover address from v,r,s |
GPU Operations
Backend-neutral GPU acceleration surface. All functions use opaque ufsecp_gpu_ctx* (separate from CPU ufsecp_ctx*). Include <ufsecp/ufsecp_gpu.h>.
GPU Error Codes (100--106): UFSECP_ERR_GPU_UNAVAILABLE (100), UFSECP_ERR_GPU_DEVICE (101), UFSECP_ERR_GPU_LAUNCH (102), UFSECP_ERR_GPU_MEMORY (103), UFSECP_ERR_GPU_UNSUPPORTED (104), UFSECP_ERR_GPU_BACKEND (105), UFSECP_ERR_GPU_QUEUE (106).
Current GPU C ABI failure semantics:
- output buffers are cleared to their invalid/zero default before processing for result-bearing batch operations, and cleared again if backend dispatch returns non-OK
ufsecp_bip352_prepare_scan_planandufsecp_gpu_bip352_scan_batchrejectscan_privkey32 == 0andscan_privkey32 >= n; rejected scan keys leaveplan264_out/prefix64_outzeroed- secret-bearing GPU backends erase uploaded ECDH, BIP-352, and BIP-324 key buffers before releasing host/shared/device storage
ufsecp_gpu_*_verify_collectis excluded from output pre-clearing because itskey_bufferis intentionally an in/out marker buffer used by fallback callers
Discovery
| Function | Signature | Description |
|---|---|---|
ufsecp_gpu_backend_count | (ids_out*, max) -> uint32_t | Number of compiled backends; optionally fills array |
ufsecp_gpu_backend_name | (bid) -> const char* | Human-readable backend name ("CUDA", "OpenCL", "Metal") |
ufsecp_gpu_is_available | (bid) -> int | 1 if backend has >= 1 usable device, 0 otherwise |
ufsecp_gpu_device_count | (bid) -> uint32_t | Devices visible to backend bid |
ufsecp_gpu_device_info | (bid, dev, info_out*) -> error_t | Fill ufsecp_gpu_device_info_t (name, memory, CUs, clock) |
Context Lifecycle
| Function | Signature | Description |
|---|---|---|
ufsecp_gpu_ctx_create | (ctx_out**, bid, dev) -> error_t | Create GPU context on backend bid, device dev |
ufsecp_gpu_ctx_destroy | (ctx*) -> void | Destroy context (NULL-safe) |
ufsecp_gpu_last_error | (ctx*) -> error_t | Last error code from ctx |
ufsecp_gpu_last_error_msg | (ctx*) -> const char* | Last error message string |
ufsecp_gpu_error_str | (code) -> const char* | Error code to string (CPU + GPU codes) |
Batch Operations and Extensions
| Function | Signature | Description |
|---|---|---|
ufsecp_gpu_generator_mul_batch | (ctx, scalars32[], n, pubkeys33_out[]) -> error_t | k*G for n scalars |
ufsecp_gpu_ecdsa_verify_batch | (ctx, msgs32[], pubs33[], sigs64[], n, results_out[]) -> error_t | ECDSA batch verify |
ufsecp_gpu_ecdsa_verify_opaque_rows / ufsecp_gpu_ecdsa_verify_lbtc_rows | (ctx, rows, stride, n, results_out[]) -> error_t | Direct strided opaque ECDSA rows; CUDA/OpenCL/Metal parse and low-S normalize on device |
ufsecp_gpu_schnorr_verify_batch | (ctx, msgs32[], pubs_x32[], sigs64[], n, results_out[]) -> error_t | Schnorr/BIP-340 batch verify |
ufsecp_gpu_ecdsa_verify_lbtc_columns | (ctx, digests32[], pubs33[], sigs64[], n, results_out[]) -> error_t | Direct libbitcoin ECDSA column (SoA) verify; opaque-LE sig parsed on device. C ABI completeness only — the libbitcoin-direct surface is the C++ engine entrypoint, which reaches GPU via install_gpu_columns_verify_hook (see below), not this C ABI. |
ufsecp_gpu_schnorr_verify_lbtc_columns | (ctx, digests32[], xonly32[], sigs64[], n, results_out[]) -> error_t | Direct libbitcoin Schnorr column (SoA) verify; BIP-340 parsed on device. C ABI completeness only (see above). |
ufsecp_gpu_ecdsa_verify_collect | (ctx, msgs32[], pubs33[], sigs64[], n, key_buffer[]) -> error_t | ECDSA batch verify, in-place 1-byte/row verdict (valid→0, invalid→left); libbitcoin collect. Native on CUDA, OpenCL, and Metal |
ufsecp_gpu_schnorr_verify_collect | (ctx, msgs32[], pubs_x32[], sigs64[], n, key_buffer[]) -> error_t | Schnorr batch verify, in-place verdict; native on CUDA, OpenCL, and Metal (see ecdsa_verify_collect) |
ufsecp_gpu_ecdh_batch | (ctx, privkeys32[], pubs33[], n, secrets32_out[]) -> error_t | ECDH shared secrets (SECRET-BEARING) |
ufsecp_gpu_hash160_pubkey_batch | (ctx, pubs33[], n, hashes20_out[]) -> error_t | SHA-256 + RIPEMD-160 of pubkeys |
ufsecp_gpu_tagged_hash_var | (ctx, tag_hash32, msgs, msg_lens[], stride, n, out32) -> error_t | BIP-340 tagged hash over per-item variable-length messages (TapLeaf scripts); each msg_lens[i] must be in [1,256] |
ufsecp_gpu_hash256 | (ctx, inputs, input_len, n, out32) -> error_t | Batch Bitcoin HASH256 (double SHA-256) of fixed-length inputs; input_len must be in [1,320] |
ufsecp_gpu_hash256_var | (ctx, inputs, input_lens[], stride, n, out32) -> error_t | Batch Bitcoin HASH256 (double SHA-256) over per-item variable-length inputs; row i = inputs[i*stride .. i*stride+input_lens[i]), bytes beyond input_lens[i] up to stride are ignored padding. No tag prefix, no transaction parsing — GPU treats each row as an opaque byte string; rows up to stride <= 4 MiB (kMaxHash256VarStride). PUBLIC-DATA / variable-time |
ufsecp_gpu_msm | (ctx, scalars32[], points33[], n, result33_out) -> error_t | Multi-scalar multiplication |
ufsecp_gpu_frost_verify_partial_batch | (ctx, z_i32[], D_i33[], E_i33[], Y_i33[], rho_i32[], lambda_ie32[], negate_R[], negate_key[], n, results_out[]) -> error_t | Batch FROST partial verification |
ufsecp_gpu_ecrecover_batch | (ctx, msgs32[], sigs64[], recids[], n, pubkeys33_out[], valid_out[]) -> error_t | Batch public-key recovery from recoverable ECDSA signatures |
ufsecp_gpu_zk_knowledge_verify_batch | (ctx, proofs64[], pubkeys65[], msgs32[], n, results[]) -> error_t | Batch ZK knowledge proof verification (PUBLIC) |
ufsecp_gpu_zk_dleq_verify_batch | (ctx, proofs64[], G65[], H65[], P65[], Q65[], n, results[]) -> error_t | Batch DLEQ proof verification (PUBLIC) |
ufsecp_gpu_bulletproof_verify_batch | (ctx, proofs324[], commits65[], H65, n, results[]) -> error_t | Batch Bulletproof range proof verification (PUBLIC) |
ufsecp_gpu_bip324_aead_encrypt_batch | (ctx, keys32[], nonces12[], plain[], sizes[], max_payload, n, wire_out[]) -> error_t | Batch BIP-324 ChaCha20-Poly1305 AEAD encrypt (SECRET: keys sent to GPU) |
ufsecp_gpu_bip324_aead_decrypt_batch | (ctx, keys32[], nonces12[], wire[], sizes[], max_payload, n, plain_out[], valid[]) -> error_t | Batch BIP-324 ChaCha20-Poly1305 AEAD decrypt (SECRET) |
ufsecp_gpu_zk_ecdsa_snark_witness_batch | (ctx, msgs32[], pubs33[], sigs64[], n, witnesses760_out[]) -> error_t | Batch ECDSA SNARK witness generation — eprint 2025/695 (PUBLIC inputs) |
ufsecp_gpu_zk_schnorr_snark_witness_batch | (ctx, msgs32[], pubkeys_x32[], sigs64[], n, witnesses472_out[]) -> error_t | Batch BIP-340 Schnorr SNARK witness generation (PUBLIC inputs). GPU kernels pending — CPU fallback returns Unsupported via virtual dispatch. |
ufsecp_gpu_bip352_scan_batch | (ctx, scan_privkey32, spend_pubkey33, tweaks33[], n, prefix64_out[]) -> error_t | BIP-352 Silent Payment batch scan — returns upper-64-bit x-coordinate prefix per tweak (SECRET-BEARING: scan key sent to GPU) |
BIP-352 CPU utility
| Function | Signature | Description |
|---|---|---|
ufsecp_bip352_prepare_scan_plan | (scan_privkey32, plan264_out) -> error_t | Precompute 264-byte BIP-352 GLV wNAF scan plan for repeated GPU batch scans; rejects zero/order-or-larger scan keys and zeroes the plan on error |
libbitcoin-direct column verify (C++ engine surface)
Header: <secp256k1/batch_verify.hpp>. These are the single public surface the
libbitcoin-direct engine calls (ufsecp::lbtc::* invokes exactly these). They
verify column (Structure-of-Arrays) batches and, when a GPU accelerator is
installed, use it transparently — the caller sees one API, one boolean-plus-per-row
result, no CPU/GPU split, and no recoverable GPU status. Verification is
variable-time over PUBLIC data (pubkey, signature, message) — correct by design.
namespace secp256k1 {
// Column entrypoints. Return true iff all rows verify. When out_results is
// non-null it is filled 1=valid / 0=invalid per row. count == 0 returns true.
// Null column pointers or a size overflow return false and zero out_results
// (fail-closed input validation). When a GPU columns hook is installed the
// entrypoint attempts GPU first and falls back to the deterministic CPU column
// path — with identical results — whenever the hook declines. An operational
// GPU/backend error is a decline -> CPU fallback, NEVER a consensus-invalid row.
bool ecdsa_batch_verify_opaque_columns(const std::uint8_t* digests32,
const std::uint8_t* pubkeys33,
const std::uint8_t* sigs64,
std::size_t count,
std::uint8_t* out_results = nullptr,
std::size_t max_threads = 0);
bool schnorr_batch_verify_bip340_columns(const std::uint8_t* digests32,
const std::uint8_t* xonly32,
const std::uint8_t* sigs64,
std::size_t count,
std::uint8_t* out_results = nullptr,
std::size_t max_threads = 0);
// Installable GPU accelerator hook (PUBLIC-DATA verify only). Null by default =>
// pure CPU. Self-installed by the GPU-host layer (gpu_engine_hook.cpp) via a
// static initializer whenever the GPU host is linked; no compile-time macro.
// kind == 0 : ECDSA opaque-LE columns (keys = pubkeys33)
// kind == 1 : Schnorr BIP-340 columns (keys = xonly32)
// Return contract:
// 1 -> handled; out_results fully written; ALL rows valid.
// 0 -> handled; out_results fully written; at least one row invalid.
// -1 -> NOT handled (GPU unavailable/unsupported/operational error); the engine
// MUST fall back to the CPU column path. A declining hook MUST NOT write a
// consensus-invalid all-zero buffer to signal "not handled".
using GpuColumnsVerifyHook = int (*)(int kind,
const std::uint8_t* digests32, const std::uint8_t* keys,
const std::uint8_t* sigs64, std::size_t count,
std::uint8_t* out_results) noexcept;
// Install (nullptr clears). Thread-safe. Returns the previous hook (save/restore).
GpuColumnsVerifyHook install_gpu_columns_verify_hook(GpuColumnsVerifyHook hook) noexcept;
} // namespace secp256k1
Semantics:
- One caller surface. There is no caller-visible
_gpuvariant and no recoverable GPU status on this surface. The caller never chunks, never selects CPU vs GPU, and never manages GPU buffers; chunking and reusable staging are engine/backend-owned. - GPU is an internal accelerator. With a hook installed, each entrypoint
attempts the GPU column verify (
kind0=ECDSA / 1=Schnorr). With no hook, or on a-1decline, the engine completes the batch on the deterministic CPU column path with byte-identical boolean + per-row results. - ECDSA high-S is consensus-valid here. The libbitcoin-direct opaque ECDSA entrypoints accept mathematically valid high-S signatures and do not rewrite caller buffers. Low-S strictness remains a shim/standardness policy, not this consensus verify contract.
- Fatal, not invalid. Operational GPU/backend errors are declines converted to
a CPU fallback — never consensus-invalid rows or an all-zero OK result. Only an
unrecoverable inability to complete the math (with no fallback) fails hard (a
fatal engine failure, not consensus data). Malformed layout (null column
pointers / size overflow) is fail-closed
falsewith zeroed results (input validation, not a GPU error). - Default is pure CPU. The hook is null unless the libbitcoin-GPU build installs one, so the default direct build is CPU-only until that enablement lands.
Performance Tips
CPU
-
Use in-place operations when possible:
// Slower: creates temporary point = point.add(other); // Faster: no allocation point.add_inplace(other); -
Use KPlan for fixed-K multiplication:
// If K is constant and Q varies, precompute K once KPlan plan = KPlan::from_scalar(K); for (auto& Q : points) { result = Q.scalar_mul_with_plan(plan); } -
Use
from_limbsfor binary I/O (notfrom_bytes):// Database/binary files: use from_limbs (native little-endian) FieldElement::from_limbs(limbs); // Hex strings/test vectors: use from_bytes (big-endian) FieldElement::from_bytes(bytes);
CUDA
- Batch operations: Process thousands of points in parallel
- Avoid divergence: Use branchless algorithms where possible
- Memory coalescing: Align data structures to 32/64 bytes
- Use hybrid 32-bit multiplication: Enabled by default (
SECP256K1_CUDA_USE_HYBRID_MUL=1)
Examples
Generate Bitcoin Address (CPU)
#include <secp256k1/point.hpp>
#include <secp256k1/scalar.hpp>
using namespace secp256k1::fast;
int main() {
// Private key (256-bit)
Scalar private_key = Scalar::from_hex(
"E9873D79C6D87DC0FB6A5778633389F4453213303DA61F20BD67FC233AA33262"
);
// Public key = private_key x G
Point G = Point::generator();
Point public_key = G.scalar_mul(private_key);
// Get compressed public key (33 bytes)
auto compressed = public_key.to_compressed();
// Print as hex
for (auto byte : compressed) {
printf("%02x", byte);
}
printf("\n");
return 0;
}
Batch Point Generation (CUDA)
#include <secp256k1.cuh>
using namespace secp256k1::cuda;
__global__ void generate_points_kernel(
const Scalar* private_keys,
AffinePoint* public_keys,
int count
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= count) return;
JacobianPoint result;
scalar_mul_generator(&private_keys[idx], &result);
jacobian_to_affine(&result, &public_keys[idx]);
}
void generate_points(
const Scalar* d_private_keys,
AffinePoint* d_public_keys,
int count
) {
int threads = 256;
int blocks = (count + threads - 1) / threads;
generate_points_kernel<<<blocks, threads>>>(
d_private_keys, d_public_keys, count
);
}
Verify Self-Test (CPU)
#include <secp256k1/point.hpp>
#include <iostream>
int main() {
bool ok = secp256k1::fast::Selftest(true);
if (ok) {
std::cout << "All tests passed!" << std::endl;
return 0;
} else {
std::cerr << "TESTS FAILED!" << std::endl;
return 1;
}
}
Build Configuration Macros
CPU
| Macro | Default | Description |
|---|---|---|
SECP256K1_USE_ASM | ON | Enable x64/RISC-V assembly |
SECP256K1_RISCV_FAST_REDUCTION | ON | Fast modular reduction (RISC-V) |
SECP256K1_RISCV_USE_VECTOR | ON | RVV vector extension |
CUDA
| Macro | Default | Description |
|---|---|---|
SECP256K1_CUDA_USE_HYBRID_MUL | 1 | 32-bit hybrid multiplication (~10% faster) |
SECP256K1_CUDA_USE_MONTGOMERY | 0 | Montgomery domain arithmetic |
SECP256K1_CUDA_LIMBS_32 | 0 | Use 8x32-bit limbs (experimental) |
Platform Support
| Platform | Assembly | SIMD | Status |
|---|---|---|---|
| x86-64 Linux/Windows/macOS | BMI2/ADX | AVX2 | [OK] Production |
| RISC-V 64 | RV64GC | RVV 1.0 | [OK] Production |
| ARM64 (Android/iOS/macOS) | MUL/UMULH | NEON | [OK] Production |
| CUDA (sm_75+) | PTX | -- | [OK] Production |
| ROCm/HIP (AMD) | Portable | -- | [OK] CI |
| OpenCL 3.0 | PTX | -- | [OK] Production |
| WebAssembly | Portable | -- | [OK] Production |
| ESP32-S3 / ESP32 | Portable | -- | [OK] Tested |
| STM32F103 (Cortex-M3) | UMULL | -- | [OK] Tested |
libsecp256k1 Shim Extensions
Functions in compat/libsecp256k1_shim/ that extend the libsecp256k1 API with
shim-specific lifecycle helpers. These have no counterpart in upstream libsecp256k1.
MuSig2 Session Cleanup
/* Explicitly release the shim's internal state for a MuSig2 session that was
* abandoned before secp256k1_musig_partial_sig_agg (e.g. on protocol abort).
* Calling on a never-initialised or already-cleared cache is a no-op.
* Not needed for completed sessions — partial_sig_agg auto-cleans. */
void secp256k1_musig_keyagg_cache_clear(secp256k1_musig_keyagg_cache *keyagg_cache);
When to call: Only when a MuSig2 signing session is aborted before the final
secp256k1_musig_partial_sig_agg call. Completed sessions clean up automatically.
Version
UltrafastSecp256k1 v4.5.0
For more information, see the README or GitHub repository.