4.0 KiB
Compatibility Targets
UltrafastSecp256k1 supports two operational modes for input validation. This document describes the precise semantics and how each mode is engaged.
General mode (internal)
Internal helpers such as Scalar::from_bytes() and FieldElement::from_bytes()
apply modular reduction to out-of-range inputs:
Scalar::from_bytes(bytes32)-- reduces mod n (curve order)FieldElement::from_bytes(bytes32)-- reduces mod p (field prime)
These are used for intermediate computations (e.g. hash-to-scalar in RFC 6979 nonce generation) where reduction is mathematically correct and required.
General-mode functions must NOT be used on public/untrusted inputs.
Bitcoin strict mode (public API)
All public-facing parsing and verification functions enforce canonical encoding as required by BIP-340, BIP-32, and standard Bitcoin consensus rules:
| Rule | BIP reference | Enforcement |
|---|---|---|
| Private key: 1 <= sk < n | BIP-340 signing | Scalar::parse_bytes_strict_nonzero |
| Schnorr r: r < p | BIP-340 verify | FieldElement::parse_bytes_strict |
| Schnorr s: 1 <= s < n | BIP-340 verify | Scalar::parse_bytes_strict_nonzero |
| Pubkey x: x < p | BIP-340 / compressed | FieldElement::parse_bytes_strict |
Values at or above the modulus are rejected immediately -- never reduced.
Strict parsing API
// Returns false if bytes >= n (no reduction, no mutation of out)
bool Scalar::parse_bytes_strict(const uint8_t* bytes32, Scalar& out);
// Returns false if bytes >= n OR bytes == 0
bool Scalar::parse_bytes_strict_nonzero(const uint8_t* bytes32, Scalar& out);
// Returns false if bytes >= p (no reduction, no mutation of out)
bool FieldElement::parse_bytes_strict(const uint8_t* bytes32, FieldElement& out);
// Returns false if r >= p, s >= n, or s == 0
bool SchnorrSignature::parse_strict(const uint8_t* sig64, SchnorrSignature& out);
C ABI strict behavior
The C ABI (ufsecp_* functions) defaults to Bitcoin strict for all
public inputs:
| Function | Strict check | Error on violation |
|---|---|---|
ufsecp_seckey_verify |
1 <= sk < n | UFSECP_ERR_BAD_KEY |
ufsecp_schnorr_verify |
r < p, s < n, s != 0, pk.x < p | UFSECP_ERR_BAD_SIG / UFSECP_ERR_BAD_KEY |
ufsecp_ec_pubkey_parse |
x < p (compressed/x-only) | UFSECP_ERR_BAD_PUBKEY |
Downstream integrators can distinguish between encoding rejection
(UFSECP_ERR_BAD_SIG, UFSECP_ERR_BAD_KEY) and cryptographic verification
failure (UFSECP_ERR_VERIFY_FAIL) via the returned error code.
Build flag: -DUFSECP_BITCOIN_STRICT=ON
When this CMake flag is enabled (default: ON), the compile-time define
UFSECP_BITCOIN_STRICT is set globally. This ensures:
- All public API paths use strict parsing (already the default behavior).
- A compile-time assertion documents that the library is built in Bitcoin-compatible strict mode.
- CI includes a dedicated job that builds and tests with this flag.
When set to OFF, the library still uses strict parsing in its public API but the compile-time marker is absent. This is intended only for non-Bitcoin use cases that may add custom reduction policies in the future.
Interoperability with libsecp256k1
UltrafastSecp256k1 aims for accept/reject parity with libsecp256k1 on all standard inputs:
- Same BIP-340 test vectors pass/fail
- Same edge-case rejection (r=p, s=n, pk.x=p, etc.)
- Same ECDSA low-S normalization behavior
The test_bip340_strict test suite validates 31 non-canonical rejection
scenarios. The test_bip340_vectors suite validates all 15 official
BIP-340 test vectors (0-14).
Self-audit and external reproducibility
The unified audit runner (unified_audit_runner) is a self-assessment
tool. It is designed to be reproducible by outside reviewers as well; the goal is open verification, not a paperwork substitute.
See SECURITY.md for the project's security posture and
audit status.