btclib_secp256k1 package

Submodules

btclib_secp256k1.context module

Shared libsecp256k1 context, and what its illegal callback reports.

btclib_secp256k1.context.check() → None[source]

Raise what libsecp256k1 reported on this thread, if anything.

The message is the failed precondition itself, as libsecp256k1 stringifies the condition of its own check: signing twice with the same MuSig2 secret nonce, for one, is reported as the failed magic check of the nonce that the first signature zeroed.

It is for a call made through lib directly, as a MuSig2 session is, and it is the caller’s to make: what it reports is whatever was recorded last on this thread by anything at all. Call it immediately after the call whose return value you are explaining, and read nothing into a message from any other one.

A wrapper is one of those others. Handed an object libsecp256k1 cannot read, most wrappers answer their own verdict – False from a verification, an ordering from a comparison, their own exception where a return code let them raise – and the reason is recorded here on the way past. So a message on the thread may be a wrapper’s rather than the caller’s, which is the whole of why this asks to be called at once. The README’s Parsing the key once names the wrappers that raise the reason themselves, through _checked, and leave nothing here.

What was recorded is cleared, so a second call reports nothing and a later one cannot inherit this call’s message.

Raises:

ValueError – if libsecp256k1 reported a violated precondition.

btclib_secp256k1.dsa module

Elliptic Curve Digital Signature Algorithm (ECDSA).

A signature crosses this boundary in one of its serializations, DER or the 64-byte compact form, and parse_der, parse_compact, serialize_der and serialize_compact are what open and close each of them. They are here for the reason keys.parse is: a caller doing more than one thing with one signature – asking whether it is low-s and then verifying it, verifying it against several keys, storing it in the other form – parses it once and hands the object to the private halves, where normalize, is_low_s, is_low_r, to_der and to_compact each parse and serialize one of their own.

btclib_secp256k1.dsa.is_low_r(signature_bytes: bytes | bytearray | memoryview) → bool[source]

Return True if the DER signature has the low r.

Not a rule of ECDSA, unlike is_low_s, and not something verify asks: a high-r signature is valid and always was. It is a size policy, and what it says is that this signature is one octet shorter in DER than it might have been – which is what a caller enforcing it on its own signatures, or measuring how many of somebody else’s carry it, is asking about. sign(grind=True) is how one is made.

Parameters:

signature_bytes – the signature in DER encoding.

Returns:

True if the high bit of r is clear.

Raises:
  • ValueError – if the DER signature is malformed, or if r or s is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to serialize it, which no signature it parsed can make it do.

Example

>>> import hashlib
>>> from btclib_secp256k1 import dsa
>>> msg = hashlib.sha256(b"hello").digest()
>>> dsa.is_low_r(dsa.sign(msg, 1, grind=True))
True
btclib_secp256k1.dsa.is_low_s(signature_bytes: bytes | bytearray | memoryview) → bool[source]

Return True if the DER signature is in the normalized lower-s form.

ECDSA signatures are malleable: negating s modulo the group order yields a second valid signature of the same message, which the lower-s requirement rules out.

Parameters:

signature_bytes – the signature in DER encoding.

Returns:

True if s is the lower of the two, which is what verify requires and what sign always produces.

Raises:

ValueError – if the DER signature is malformed, or if r or s is not in [1, n-1].

btclib_secp256k1.dsa.nonce_rfc6979(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, attempt: int = 0) → bytes[source]
btclib_secp256k1.dsa.nonce_rfc6979(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, attempt: int = 0, *, into: bytearray | memoryview) → None
btclib_secp256k1.dsa.nonce_rfc6979(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, attempt: int = 0, *, into: bytearray | memoryview | None) → bytes | None

Return the RFC6979 nonce sign derives for a message and a key.

libsecp256k1 exports its nonce function as a callable pointer, and this calls through it with what secp256k1_ecdsa_sign passes: the message hash, the key, no algorithm tag, and the extra entropy. That signing call selects the default nonce function, which libsecp256k1 documents as the same pointer as this one – an identity tests/nonces_test.py asserts rather than assumes. So what comes back is the k of the signature sign makes of the same arguments: r is the x of k times the generator, reduced, which is what that same file holds it to.

It is here because a nonce is the one part of signing these bindings compute and never show, which leaves a python implementation of RFC6979 with published vectors and no oracle. recovery.sign derives its nonce the same way and this answers for it too.

The nonce is the secret the signature is built on. Read into python it has left constant-time code, and a caller that signs with one it read here is doing the arithmetic this package delegates precisely so that it is not done in python. What this is for is checking a derivation, not driving one.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – the 32 bytes of extra entropy sign mixes in, or None for the RFC6979 nonce alone. Whichever was given to sign is what reproduces its nonce, None included.

  • attempt – which candidate to answer. RFC6979 retries when the one it derives is not a scalar in [1, n-1], and libsecp256k1 drives that counter itself; 0 is what it takes first and what every signature this package makes has used.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte nonce – or None where into was given and holds it.

Raises:
  • TypeError – if the attempt is not an int, if an argument is not bytes, or if into is not a writable bytearray or memoryview of octets.

  • ValueError – if the message hash is not 32 bytes, if aux_rand32 is given and is not 32 bytes, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if the attempt is out of range.

  • RuntimeError – if libsecp256k1 fails to derive one, which RFC6979 answers for every input.

Example

>>> from btclib_secp256k1 import dsa
>>> nonce = dsa.nonce_rfc6979(bytes(32), 1)
>>> len(nonce)
32
btclib_secp256k1.dsa.normalize(signature_bytes: bytes | bytearray | memoryview) → bytes[source]

Convert a DER signature to its normalized lower-s form.

This is for a caller that needs the normalized bytes – storing them, forwarding them, comparing them. Normalizing in order to verify is verify(…, normalize=True) instead, which is the same normalization without the serialization and the second parse between it and the verification.

Parameters:

signature_bytes – the signature in DER encoding.

Returns:

The same signature with s replaced by n - s where s was the higher of the two, in DER encoding. A signature already normalized is returned unchanged.

Raises:
  • ValueError – if the DER signature is malformed, or if r or s is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no input can make it do.

btclib_secp256k1.dsa.parse_compact(signature_bytes: bytes | bytearray | memoryview) → Any[source]

Parse a 64-byte compact signature into its internal representation.

The other way into the object parse_der answers with, and the cheaper one: there is no encoding to walk, only two scalars to prove in [1, n-1].

Parameters:

signature_bytes – the 64 bytes of r and s.

Returns:

The libsecp256k1 signature object. s is not normalized, which is what _is_low_s_ asks and _normalize_ changes.

Raises:

ValueError – if the input is not 64 bytes, or if r or s is not in [1, n-1].

btclib_secp256k1.dsa.parse_der(signature_bytes: bytes | bytearray | memoryview) → Any[source]

Parse a DER signature into its internal representation.

What keys.parse is to a public key, and the argument of every private half here: serialize_der is what turns it back into bytes, and parse_compact reads the other serialization into the same object.

Parameters:

signature_bytes – the signature in DER encoding.

Returns:

The libsecp256k1 signature object.

Raises:

ValueError – if the DER signature is malformed, or if r or s is not in [1, n-1].

btclib_secp256k1.dsa.serialize_compact(signature: Any) → bytes[source]

Serialize an internal signature in its 64-byte compact form.

Parameters:

signature – the libsecp256k1 signature object, as parse_der and parse_compact return.

Returns:

The 64 bytes of r and s, each big endian and zero padded.

Raises:

RuntimeError – if libsecp256k1 refuses the object – one it cannot read – or fails for any other reason, which a signature it parsed cannot make it do. context.check is what tells the two apart.

btclib_secp256k1.dsa.serialize_der(signature: Any) → bytes[source]

Serialize an internal signature in DER form.

Parameters:

signature – the libsecp256k1 signature object, as parse_der and parse_compact return.

Returns:

Its DER encoding, at most 72 bytes.

Raises:

RuntimeError – if libsecp256k1 refuses the object – one it cannot read – or fails for any other reason, which the 72-byte buffer makes unreachable. context.check is what tells the two apart.

btclib_secp256k1.dsa.sign(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, compact: bool = False, grind: bool = False, *, verify: bool = True, pubkey: bytes | bytearray | memoryview | None = None) → bytes[source]

Create an ECDSA signature.

The nonce is the deterministic RFC6979 one, so the signature is a function of the message and the key alone unless aux_rand32 is given.

Which serialization to answer with is the caller’s, as it is everywhere a key is answered: a signature is r and s, and DER is what the wire carries rather than what a caller holds. Asking for the compact form is serialize_compact in place of serialize_der, where reaching it through to_compact is that DER encoding parsed straight back apart.

grind is the one thing here that is more than a libsecp256k1 call, and it is Bitcoin Core’s CKey::Sign scheme rather than an invention of this package: signing again, with a counter mixed into the nonce, until r has its high bit clear and DER therefore spends no leading zero octet on it. It costs what the octet is worth – two signatures on average, and the tail is longer than that – so it is asked for and never done by default. s is not ground for and could not be: libsecp256k1 has already returned the lower of the two.

verify is the second thing beyond a libsecp256k1 call, and it is the other half of that same CKey::Sign: the signature is checked under the public key of the very key that made it, and one that fails is never answered with. What it catches is not a bad argument – those have all raised by then – but a computation that went wrong, a bit flipped in memory or a fault induced on purpose, whose cost is a published signature that is invalid and may say something about the key. It is the one argument here that is on by default, and the asymmetry with grind is the point: grinding buys an octet of DER, this buys the guarantee that what was answered is a signature. Core pays it on every signature and offers no way not to; here there is one, because the check is a point multiplication and a verification and the caller who has measured that against its own threat model is the one entitled to refuse it – ssa.sign refuses it more cheaply, BIP340 needing the public key to sign at all where ECDSA needs it for neither of the two things sign does.

What that comes to, since “more than grinding” is the useful comparison and the sentence above does not make it: 31.67 microseconds against 12.15, where ssa.sign is 28.57 against 15.87. So the check is more than the signature it checks, and the 6.8 between the two increments is the secp256k1_ec_pubkey_create this one has to do and BIP340’s does not. changelog/v0.8.0.3.md names the session every figure in this module comes from. A caller grinding outside this call – signing repeatedly and choosing among the answers – pays it once per attempt rather than once, and verify=False on the attempts with a check of whichever signature is kept is the shape that costs what one signature’s check costs.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – 32 bytes of extra entropy mixed into the nonce, or None for the RFC6979 nonce alone. Never a shorter value: entropy is not a serialization, and padding one would make a caller mistake a valid argument.

  • compact – whether to answer the 64-byte r || s rather than DER.

  • grind – whether to sign again until r is the low one. Refused together with aux_rand32: grinding is written into those very 32 octets, so a caller asking for both is asking for two different values of one argument, and Core’s counter is what makes the result reproducible by anyone else.

  • verify – whether to check the signature under the public key of the key that made it before returning it. It costs a point multiplication and a verification, and False is what a caller that has measured those against its own threat model passes.

  • pubkey – the public key of this private key, where the caller already holds it, so that the check does not derive it again. That derivation is the larger half of what the check costs, and this is how not to pay it twice. It is taken on trust rather than checked against the private key, which would cost the multiplication it saves; where it is wrong, the check says so and says which of the two things went wrong. Refused beside verify=False, which declines the check it is for.

Returns:

The signature, in the lower-s form libsecp256k1 always produces – DER, or the 64 octets of r || s where compact asks for them. Where grind asks for it, of the low r as well.

Raises:
  • ValueError – if the message hash is not 32 bytes, if aux_rand32 is given and is not 32 bytes, if aux_rand32 and grind are given together, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], if pubkey is not a public key or is given beside verify=False, or if pubkey is not this private key’s – which is told apart from the failure below rather than reported as it.

  • RuntimeError – if libsecp256k1 fails to serialize the signature, which no input can make it do, or if the check asks and the signature does not verify under the key this private key actually has.

Example

>>> import hashlib
>>> from btclib_secp256k1 import dsa
>>> msg = hashlib.sha256(b"hello").digest()
>>> dsa.is_low_s(dsa.sign(msg, 1))
True
>>> dsa.sign(msg, 1, grind=True, compact=True)[0] < 0x80
True
btclib_secp256k1.dsa.signature_verify(signature_bytes: bytes | bytearray | memoryview, compact: bool = False) → bool[source]

Return True if the octets are a signature.

What keys.pubkey_verify is to a public key: the proof a parse makes, with nothing kept and no exception to catch. A library validating an input at its own boundary has this; what the octets are wrong about is its own to phrase.

It says nothing about a message or a key, verify being that question, and nothing about the lower-s form, which is is_low_s: a signature is r and s in [1, n-1], and that is what this answers for.

Parameters:
  • signature_bytes – the signature, in the serialization below.

  • compact – whether it is the 64 octets of r || s rather than DER, which cannot be read off the length: see verify.

Returns:

True if the octets are a signature in that serialization; False for octets of any other length too, as keys.pubkey_verify answers for a key.

Raises:

TypeError – if the value is not bytes at all, which is a malformed argument and not a signature to have a verdict on.

Example

>>> from btclib_secp256k1 import dsa
>>> dsa.signature_verify(dsa.sign(bytes(32), 1))
True
>>> dsa.signature_verify(bytes.fromhex("3006"))
False
btclib_secp256k1.dsa.to_compact(signature_bytes: bytes | bytearray | memoryview) → bytes[source]

Convert a DER signature into its 64-byte compact form.

Parameters:

signature_bytes – the signature in DER encoding.

Returns:

The 64 bytes of r and s, each big endian and zero padded.

Raises:
  • ValueError – if the DER signature is malformed, or if r or s is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to serialize it, which no input can make it do.

btclib_secp256k1.dsa.to_der(signature_bytes: bytes | bytearray | memoryview) → bytes[source]

Convert a 64-byte compact signature into its DER form.

Parameters:

signature_bytes – the 64 bytes of r and s.

Returns:

The same signature in DER encoding. s is not normalized – a high-s input gives a DER signature verify refuses, which normalize is for.

Raises:
  • ValueError – if the input is not 64 bytes, or if r or s is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to serialize it, which no input can make it do.

btclib_secp256k1.dsa.verify(msg_bytes: bytes | bytearray | memoryview, pubkey_bytes: bytes | bytearray | memoryview, signature_bytes: bytes | bytearray | memoryview, normalize: bool = False, compact: bool = False) → bool[source]

Verify a ECDSA signature.

A signature which is not in the normalized lower-s form is rejected, and the refusal is libsecp256k1’s rather than this wrapper’s: secp256k1_ecdsa_verify accepts the lower-s form alone, so that the second signature is_low_s describes does not verify. normalize is therefore not a choice between two behaviours the library offers but the one way of declining to pass that refusal on, and what it verifies is s replaced by n - s – a signature the caller was never handed.

Which of the two forms a signature carries was the signer’s choice, so a caller checking signatures it did not make normalizes rather than refuses, and says so here rather than round-tripping the signature through normalize and back into DER. What it accepts in exchange is the malleability itself: two encodings verify one message under one key, and whatever hashes or stores the signature has both to account for. Bitcoin Core makes the same refusal a standardness rule, SCRIPT_VERIFY_LOW_S, so a signature from elsewhere and a signature a transaction can carry are not the same set. The default is the refusal, that being what a caller enforcing the lower-s form of its own signatures wants too.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • pubkey_bytes – the public key, 33 or 65 bytes.

  • signature_bytes – the signature, DER encoded or the 64 octets of r || s, as compact says.

  • normalize – whether to verify the lower-s form of the signature rather than reject a signature that is not in it. The rejection is libsecp256k1’s, and True is what accepts the malleability it rules out.

  • compact – whether the signature is the 64-byte r || s rather than DER. Which of the two it is has to be said and cannot be read off the length: a DER signature of 64 octets exists, r and s of 29 bytes each, and it begins with the 0x30 a compact r may begin with too.

Returns:

True if the signature is valid for that key and message.

Raises:

ValueError – if the message hash is not 32 bytes, if the signature is malformed in the serialization it was said to be in or has r or s outside [1, n-1], or if the public key is not a valid point. A well-formed signature that simply does not verify is False, not an exception.

Example

>>> import hashlib
>>> from btclib_secp256k1 import dsa, keys
>>> msg = hashlib.sha256(b"hello").digest()
>>> dsa.verify(msg, keys.pubkey_from_prvkey(1), dsa.sign(msg, 1))
True

btclib_secp256k1.ecdh module

Elliptic Curve Diffie-Hellman (ECDH).

btclib_secp256k1.ecdh.shared_point(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]
btclib_secp256k1.ecdh.shared_point(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, compressed: bool = True, *, into: bytearray | memoryview) → None
btclib_secp256k1.ecdh.shared_point(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, compressed: bool = True, *, into: bytearray | memoryview | None) → bytes | None

Multiply a public key by a private key, in constant time.

The point keys.pubkey_tweak_mul answers, and the one shared_secret hashes: this is secp256k1_ecdh, whose multiplication is secp256k1_ecmult_const, constant time in the scalar, where pubkey_tweak_mul runs the variable-time secp256k1_ecmult. So a secret scalar belongs here – an ECDH private key, BIP352’s scan key – and a public one, a verification equation’s, in keys.

secp256k1_ecdh answers the point only to a hash function, which libsecp256k1 takes as a C callback. This one is C too, compiled into the vendored library by this package’s build: it copies x and y out, and nothing enters python until the call has returned. A python callback would run in the middle of the constant-time call, with the point in python objects, and is why shared_secret’s hash is not configurable.

What the guarantee covers is the multiplication. The serialization is assembled in python from the coordinates, and the point is then a python object like any other secret answered here: SECURITY.md is what that does and does not mean.

Parameters:
  • pubkey_bytes – the other party’s public key, 33 or 65 bytes.

  • prvkey – this party’s private key, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

  • into – a writable buffer of 33 bytes, or of 65 where compressed is False, to receive the result instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The serialized point kP, as keys.serialize writes it – or None where into was given and holds it.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if the public key is not a valid point, if the private key is not 32 bytes or does not fit in them, if it is zero or at or above the group order, or if into is not the length of the serialization.

Example

>>> from btclib_secp256k1 import ecdh, keys
>>> # 3 * (7 * G) is 21 * G
>>> point = ecdh.shared_point(keys.pubkey_from_prvkey(7), 3)
>>> point == keys.pubkey_from_prvkey(21)
True
btclib_secp256k1.ecdh.shared_secret(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int) → bytes[source]
btclib_secp256k1.ecdh.shared_secret(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview) → None
btclib_secp256k1.ecdh.shared_secret(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview | None) → bytes | None

Compute the ECDH shared secret.

The result is the SHA256 of the compressed shared point, i.e. the libsecp256k1 default hash function; it is computed in constant time.

The hash function is not configurable, by decision. libsecp256k1 takes it as a C callback, so exposing it would mean calling back into python from the middle of the computation, with the shared point passing through python objects; and it would buy nothing, the point being available as shared_point(pubkey_bytes, prvkey), through this same call and its constant-time secp256k1_ecmult_const. A protocol needing another derivation applies it to that: SHA256 of its compressed form is what this function returns. Wanting both of them is where the private halves earn their keep – one keys.parse, then _shared_secret_ and _shared_point_ of it – the two public halves parsing the same key twice.

Parameters:
  • pubkey_bytes – the other party’s public key, 33 or 65 bytes.

  • prvkey – this party’s private key, 32 bytes or an int below 2**256.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte shared secret – or None where into was given and holds it.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if the public key is not a valid point, if the private key is not 32 bytes or does not fit in them, or if it is not a valid scalar.

btclib_secp256k1.ellswift module

ElligatorSwift encoding and x-only ECDH, according to BIP324.

https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki

btclib_secp256k1.ellswift.create(prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None) → bytes[source]

Create the 64-byte ElligatorSwift encoding of a private key’s public key.

This is safer than encoding the public key of that private key, as the key itself is used as entropy for the encoding.

Parameters:
  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – 32 bytes of auxiliary randomness, or None for fresh randomness.

Returns:

The 64-byte ElligatorSwift encoding of the public key.

Raises:

ValueError – if aux_rand32 is given and is not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

btclib_secp256k1.ellswift.decode(ell_bytes: bytes | bytearray | memoryview, compressed: bool = True) → bytes[source]

Decode a 64-byte ElligatorSwift public key into a public key.

Parameters:
  • ell_bytes – the 64-byte encoding.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized public key. Every 64 bytes decode to a point, which is what makes the encoding indistinguishable from random: there is nothing to reject.

Raises:
  • ValueError – if the input is not 64 bytes.

  • RuntimeError – if libsecp256k1 fails to decode or serialize, which no 64 bytes can make it do.

btclib_secp256k1.ellswift.encode(pubkey_bytes: bytes | bytearray | memoryview, aux_rand32: bytes | bytearray | memoryview | None = None) → bytes[source]

Encode a public key as 64 ElligatorSwift bytes.

The randomness must not be a deterministic function of the public key; when it is not provided, it is freshly generated.

Parameters:
  • pubkey_bytes – the public key to encode, 33 or 65 bytes.

  • aux_rand32 – the 32 bytes deciding which of the encodings of that key is produced, or None for fresh randomness.

Returns:

The 64-byte ElligatorSwift encoding, indistinguishable from uniform bytes.

Raises:
  • ValueError – if the public key is not a valid point, or if aux_rand32 is given and is not 32 bytes.

  • RuntimeError – if libsecp256k1 fails to encode, which no valid input can make it do.

btclib_secp256k1.ellswift.xdh(ell_a_bytes: bytes | bytearray | memoryview, ell_b_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, party: int) → bytes[source]
btclib_secp256k1.ellswift.xdh(ell_a_bytes: bytes | bytearray | memoryview, ell_b_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, party: int, *, into: bytearray | memoryview) → None
btclib_secp256k1.ellswift.xdh(ell_a_bytes: bytes | bytearray | memoryview, ell_b_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, party: int, *, into: bytearray | memoryview | None) → bytes | None

Compute the x-only ECDH shared secret of two ElligatorSwift keys.

The private key must be the one of the given party: 0 for party A, 1 for party B; the correspondence is not checked. The 32-byte secret is derived with the BIP324 hash function.

Parameters:
  • ell_a_bytes – the 64-byte encoding of party A’s key.

  • ell_b_bytes – the 64-byte encoding of party B’s key.

  • prvkey – the private key of the party below, 32 bytes or an int below 2**256.

  • party – 0 if the private key is A’s, 1 if it is B’s.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte shared secret, which both parties compute alike – or None where into was given and holds it. The two encodings enter the hash in the order they are given here, so BIP324’s initiator goes first.

Raises:
  • TypeError – if the party is not an int, or if into is not a writable bytearray or memoryview of octets.

  • ValueError – if either encoding is not 64 bytes, if party is not 0 or 1, or if the private key is not 32 bytes, does not fit in them, or is not a valid scalar.

btclib_secp256k1.hashes module

Tagged hashing, according to BIP340.

https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki

btclib_secp256k1.hashes.tagged_sha256(tag_bytes: bytes | bytearray | memoryview, msg_bytes: bytes | bytearray | memoryview) → bytes[source]

Return the BIP340 tagged hash of a message.

That is SHA256(SHA256(tag) || SHA256(tag) || msg): the tag separates the domains of the protocols using it, so that what one of them signs cannot be read as a message of another. The BIP340 challenge and the BIP341 taproot tags (TapLeaf, TapBranch, TapTweak) are built with it.

Parameters:
  • tag_bytes – the domain separation tag, of any length.

  • msg_bytes – the message to hash, of any length.

Returns:

The 32-byte tagged hash.

Raises:

RuntimeError – if libsecp256k1 fails, which no input can make it do.

Example

>>> from btclib_secp256k1 import hashes
>>> hashes.tagged_sha256(b"TapLeaf", b"").hex()
'5212c288a377d1f8164962a5a13429f9ba6a7b84e59776a52c6637df2106facb'

btclib_secp256k1.keys module

Secp256k1 key and point algebra.

These are the libsecp256k1 secret and public key operations, i.e. the scalar and point arithmetic underlying key derivation (BIP32) and key aggregation; libsecp256k1 calls a private key a secret key, hence its seckey function names.

Public keys are returned in compressed form, unless otherwise required.

class btclib_secp256k1.keys.PubkeyTweakChain(pubkey_bytes: bytes | bytearray | memoryview)[source]

Bases: object

Add a sequence of tweaks to a public key, parsing it only once.

pubkey_tweak_add parses its argument and serializes its result, so a caller adding tweak after tweak to its own output – a BIP32 path walked one index at a time, each needing the previous step’s serialized key to hash into the next tweak – re-parses at every step the very point the step before had already built, and only serialized because that step’s caller needed the bytes. This holds the parsed point across the calls instead: the first tweak is the only one that pays for a parse, and every step still returns the bytes its caller needs.

pubkey is that point without a tweak added, for a caller that has reached the end of the path and wants the key it arrived at, or that wants the key it started from in the other serialization.

Parameters:

pubkey_bytes – the public key the chain starts from, 33 or 65 bytes.

Raises:

ValueError – if the public key is not a valid point.

Example

>>> from btclib_secp256k1 import keys
>>> generator = keys.pubkey_from_prvkey(1, compressed=False)
>>> chain = keys.PubkeyTweakChain(generator)
>>> step1 = chain.tweak_add(2)
>>> step2 = chain.tweak_add(3)
>>> step1 == keys.pubkey_tweak_add(generator, 2)
True
>>> step2 == keys.pubkey_tweak_add(step1, 3)
True
>>> chain.pubkey() == step2
True
pubkey(compressed: bool = True) → bytes[source]

Return the key the chain holds, with no tweak added.

Returns:

The serialized point, with every tweak added so far. Before the first tweak_add that is the key the chain was built from, which is how this doubles as the reserialize of it.

Parameters:

compressed – whether to return 33 bytes rather than 65.

Raises:

RuntimeError – if libsecp256k1 fails to serialize it, which no valid input can make it do.

tweak_add(tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add the generator multiplied by the tweak, to the held key.

Parameters:
  • tweak – the tweak, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized point, with this tweak and every earlier one already added.

Raises:
  • ValueError – if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid – which ends the chain. libsecp256k1 says of secp256k1_ec_pubkey_tweak_add that “pubkey will be set to an invalid value if this function returns 0”, and the point this holds is that pubkey: there is nothing left to step from, so a caller catching this builds a chain afresh rather than asking again.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

btclib_secp256k1.keys.parse(pubkey_bytes: bytes | bytearray | memoryview, name: str = 'public key') → Any[source]

Parse a public key into its internal libsecp256k1 representation.

The internal form is what the raw lib calls take, and what serialize turns back into bytes: serialize(parse(key)) is the compressed form of a key given in either form, which is reserialize and the reason it exists. It is also what every private half of this package takes, for which see the package docstring.

Parameters:
  • pubkey_bytes – the public key, 33 or 65 bytes.

  • name – what the key is, as the exception should call it, for a caller passing more than one kind of public key – silentpayments passes a scan public key, a spend public key, and one left to the default, and which one was refused is the whole of what its caller needs.

Returns:

The libsecp256k1 public key object.

Raises:

ValueError – if the bytes are not a valid point in either serialization.

btclib_secp256k1.keys.prvkey_negate(prvkey: bytes | bytearray | memoryview | int) → bytes[source]
btclib_secp256k1.keys.prvkey_negate(prvkey: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview) → None
btclib_secp256k1.keys.prvkey_negate(prvkey: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview | None) → bytes | None

Negate a private key.

Parameters:
  • prvkey – the private key, 32 bytes or an int below 2**256.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32 bytes of n - k, the key of the negated public key – or None where into was given and holds them.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if it is not 32 bytes, does not fit in them, or is not in [1, n-1]; or if into is not 32 bytes.

btclib_secp256k1.keys.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int) → bytes[source]
btclib_secp256k1.keys.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview) → None
btclib_secp256k1.keys.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview | None) → bytes | None

Add a tweak to a private key.

This is the private-key side of BIP32 derivation, and of any other scheme adding a scalar to a key. xonly.prvkey_tweak_add is the BIP341 one, which negates the key first where its point has odd y.

Parameters:
  • prvkey – the private key, 32 bytes or an int below 2**256.

  • tweak – the tweak, 32 bytes or an int below 2**256.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32 bytes of (k + t) mod n – or None where into was given and holds them.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if either value is not 32 bytes or does not fit in them, if the private key is not in [1, n-1], if the sum is zero, which is the one tweak with no valid result, or if into is not 32 bytes.

btclib_secp256k1.keys.prvkey_tweak_mul(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int) → bytes[source]
btclib_secp256k1.keys.prvkey_tweak_mul(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview) → None
btclib_secp256k1.keys.prvkey_tweak_mul(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview | None) → bytes | None

Multiply a private key by a tweak.

Parameters:
  • prvkey – the private key, 32 bytes or an int below 2**256.

  • tweak – the tweak, 32 bytes or an int below 2**256.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32 bytes of (k * t) mod n – or None where into was given and holds them.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if either value is not 32 bytes or does not fit in them, if the private key is not in [1, n-1], if the tweak is zero or at or above the group order, or if into is not 32 bytes.

btclib_secp256k1.keys.prvkey_verify(prvkey: bytes | bytearray | memoryview | int) → bool[source]

Return True if the private key is a valid scalar, i.e. in [1, n-1].

Parameters:

prvkey – the private key, 32 bytes or an int below 2**256.

Returns:

True if it is in [1, n-1]; False for zero, for anything at or above the group order, for octets of any other length and for an int that does not fit in 32 bytes. Those are verdicts and not errors: the length is part of the verdict, as in pubkey_verify.

Raises:

TypeError – if the value is neither bytes nor an int, which is a malformed argument and not a key to have a verdict on.

btclib_secp256k1.keys.pubkey_cmp(pubkey1_bytes: bytes | bytearray | memoryview, pubkey2_bytes: bytes | bytearray | memoryview) → int[source]

Compare two public keys, in lexicographic order of compressed form.

The order is the one of the compressed serialization, whichever form the arguments are given in.

Parameters:
  • pubkey1_bytes – the first public key, 33 or 65 bytes.

  • pubkey2_bytes – the second public key, 33 or 65 bytes.

Returns:

A negative number, zero, or a positive number, according to whether the first key sorts before, equal to, or after the second.

Raises:

ValueError – if either key is not a valid point.

btclib_secp256k1.keys.pubkey_combine(pubkeys_bytes: Sequence[bytes | bytearray | memoryview], compressed: bool = True) → bytes[source]

Add public keys together.

Parameters:
  • pubkeys_bytes – the public keys, each 33 or 65 bytes. At least one is required.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized sum of the points.

Raises:
  • ValueError – if the sequence is empty, if any key is not a valid point, or if the sum is the point at infinity, which has no serialization.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

btclib_secp256k1.keys.pubkey_from_prvkey(prvkey: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Return the public key of a private key, i.e. the point kG.

This is the generator multiplication, with the serialization flag this module’s other producers all take: compressed=False answers the 65 octets of 0x04 || x || y, which is the form a caller reading coordinates wants and the cheap one to parse back. Every private-to-public conversion that wants the compressed form – BIP32 neutering, a fingerprint, an address – is this call and nothing after it.

Parameters:
  • prvkey – the private key, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized point kG – 33 bytes whose first octet carries the parity of y, or the 65 bytes of 0x04 || x || y.

Raises:
  • ValueError – if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to serialize the point, which no valid key can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> keys.pubkey_from_prvkey(1).hex()[:10]
'0279be667e'
btclib_secp256k1.keys.pubkey_negate(pubkey_bytes: bytes | bytearray | memoryview, compressed: bool = True) → bytes[source]

Negate a public key.

Parameters:
  • pubkey_bytes – the public key, 33 or 65 bytes.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The point with the same x and the other y, serialized.

Raises:
  • ValueError – if the public key is not a valid point.

  • RuntimeError – if libsecp256k1 fails to negate or serialize it, which no valid key can make it do.

btclib_secp256k1.keys.pubkey_sort(pubkeys_bytes: Sequence[bytes | bytearray | memoryview], compressed: bool = True) → list[bytes][source]

Sort public keys, in lexicographic order of compressed form.

This is the ordering of a BIP67 multisig script, and the one MuSig2 key aggregation applies when the participants have not agreed on a different one.

Parameters:
  • pubkeys_bytes – the public keys, each 33 or 65 bytes. An empty sequence sorts to an empty list.

  • compressed – whether to return 33 bytes each rather than 65.

Returns:

The same keys, serialized, in ascending order.

Raises:
  • ValueError – if any key is not a valid point.

  • RuntimeError – if libsecp256k1 fails to serialize, which no valid input can make it do.

btclib_secp256k1.keys.pubkey_sum(pubkeys_bytes: Sequence[bytes | bytearray | memoryview], compressed: bool = True) → bytes | None[source]

Add public keys together, answering None for the point at infinity.

pubkey_combine with the one sum that is no public key answered rather than refused. A caller doing arithmetic has that sum as a value – P + (-P) is the identity and not a malformed argument – and the two spellings are the same call.

The infinity has no serialization, which is why it is None and not octets: a secp256k1_pubkey is a point of the curve and never the identity, so there is nothing for this to hand back.

Parameters:
  • pubkeys_bytes – the public keys, each 33 or 65 bytes. At least one is required.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized sum of the points, or None where they sum to the point at infinity.

Raises:
  • ValueError – if the sequence is empty, or if any key is not a valid point.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> pubkey = keys.pubkey_from_prvkey(7)
>>> keys.pubkey_sum([pubkey, keys.pubkey_negate(pubkey)]) is None
True
>>> keys.pubkey_sum([pubkey]) == pubkey
True
btclib_secp256k1.keys.pubkey_tweak_add(pubkey_bytes: bytes | bytearray | memoryview, tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add the generator multiplied by the tweak to a public key.

This is the public-key side of BIP32 derivation: the key of prvkey_tweak_add(k, t) is pubkey_tweak_add(pubkey(k), t). Adding more than one tweak to the same key is PubkeyTweakChain, which parses the key once rather than once per tweak.

Parameters:
  • pubkey_bytes – the public key, 33 or 65 bytes.

  • tweak – the tweak, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized point P + tG.

Raises:
  • ValueError – if the public key is not a valid point, if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

btclib_secp256k1.keys.pubkey_tweak_mul(pubkey_bytes: bytes | bytearray | memoryview, tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Multiply a public key by a tweak.

This is the multiplication of an arbitrary point, as opposed to the multiplication of the generator pubkey_from_prvkey is, and is the shared point of an ECDH exchange: see ecdh.shared_secret, which hashes it. It is not constant time: secp256k1_ec_pubkey_tweak_mul runs secp256k1_ecmult, the wNAF multiplication, which is variable time in the tweak – unlike secp256k1_ecmult_const, which is what secp256k1_ecdh calls to reach that same point. ecdh.shared_point is that call answering the point, and the one for a tweak that is a secret.

Parameters:
  • pubkey_bytes – the public key, 33 or 65 bytes.

  • tweak – the scalar to multiply by, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized point tP.

Raises:
  • ValueError – if the public key is not a valid point, if the tweak is not 32 bytes or does not fit in them, or if it is zero or at or above the group order.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

btclib_secp256k1.keys.pubkey_tweak_mul_sum(pubkeys_bytes: Sequence[bytes | bytearray | memoryview], tweaks: Sequence[bytes | bytearray | memoryview | int], compressed: bool = True) → bytes | None[source]

Multiply each public key by its tweak and add the products.

The names it is spelled with, in that order: a pubkey_tweak_mul per pair and one pubkey_sum over the products. That is the multi-scalar multiplication a caller writes as a verification equation – u*H + v*Q of ECDSA and BIP340, MuSig2’s aggregate of a key per signer, BIP352’s tweak data – and the whole of what it adds is that no product is serialized: written with the public halves, each pubkey_tweak_mul serializes 65 octets that the sum then parses again, and the terms are the only place a caller has to put them.

The naive form of it, deliberately: a term at a time and one sum, with none of the shared precomputation of Strauss or Pippenger. libsecp256k1 has secp256k1_ecmult_multi_var, which is internal and not declared in include/secp256k1.h, so this is the only multi-scalar multiplication the public API can be composed of – and the saving here is the crossing rather than the arithmetic, flat in the number of terms where a batched algorithm would grow with it.

This is the second function here that is more than one libsecp256k1 decision, xonly.from_prvkey being the first, and the README’s Design section says why the rule is what it is: the boundary computes nothing of its own. Neither does this – the terms are secp256k1_ec_pubkey_tweak_mul and the sum is secp256k1_ec_pubkey_combine, in the one order that spells the equation – and what is saved is a serialization and a parse per term, about a seventh of the call from three terms up and a little less at two. PubkeyTweakChain is an exception of the other kind and not of this one: its tweak_add is one decision and a serialization like every other, and what it holds is a parsed key across calls the caller makes one at a time.

The infinity is pubkey_sum’s None, for the reason it is there and is more likely here: a verification equation is written to land on it, u*H + v*Q with v*Q the negation of u*H being the accepting case of a check spelled as a difference.

Parameters:
  • pubkeys_bytes – the public keys, each 33 or 65 bytes. At least one is required, as the sum requires it.

  • tweaks – the scalar each key is multiplied by, one per key, 32 bytes or an int below 2**256.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized sum of the products, or None where they sum to the point at infinity.

Raises:
  • TypeError – if tweaks is a str, a cffi array of primitives or a one-dimensional buffer of anything but byte-strings, objects or structs (bytes, bytearray, memoryview, array.array): one tweak, not a list of them.

  • ValueError – if the two sequences are of different lengths, if the sequence of keys is empty, if any key is not a valid point, or if any tweak is not 32 bytes, does not fit in them, or multiplies its key to something invalid – zero and the group order being the two scalars there is no product for.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> generator = keys.pubkey_from_prvkey(1)
>>> pubkey = keys.pubkey_from_prvkey(7)
>>> # 2*G + 3*(7*G) is 23*G
>>> total = keys.pubkey_tweak_mul_sum([generator, pubkey], [2, 3])
>>> total == keys.pubkey_from_prvkey(23)
True
>>> # and a sum landing on the identity is None, not a refusal
>>> negated = keys.pubkey_negate(pubkey)
>>> keys.pubkey_tweak_mul_sum([pubkey, negated], [3, 3]) is None
True
btclib_secp256k1.keys.pubkey_verify(pubkey_bytes: bytes | bytearray | memoryview) → bool[source]

Return True if the octets are a public key libsecp256k1 accepts.

The public-key twin of prvkey_verify, and the call for a library validating a key at its own boundary: secp256k1_ec_pubkey_parse is the proof, and this is that proof with nothing kept. parse hands back an object whose lifetime becomes the caller’s, and reserialize hands back octets it already had – 0.37 us of serialization for an answer that was in the argument.

A verdict and not an exception, as prvkey_verify is: what the octets are wrong about is the caller’s to phrase, and a library validating an input has its own word for it.

Parameters:

pubkey_bytes – the public key, 33 or 65 bytes.

Returns:

True if it is a point of the curve in either serialization; False for octets of any other length too, which is a verdict here where every other entry point taking a key raises: there is nothing to do with such a key either way, and a caller asking whether it has one has asked about the length as well.

Raises:

TypeError – if the value is not bytes at all, which is a malformed argument and not a key to have a verdict on.

btclib_secp256k1.keys.reserialize(pubkey_bytes: bytes | bytearray | memoryview, compressed: bool = True) → bytes[source]

Prove octets a public key, and answer them in the form asked for.

serialize(parse(key)) as one call, which is two things a caller wants separately and always together.

It is the validation: a library proving a key at its own boundary has parse and nothing to do with what parse returns, and this answers octets instead of an object whose lifetime is the caller’s.

And it is the conversion, which nothing else here offers: a caller holding an uncompressed key and needing the compressed one to hash has no other call to make, and one holding a compressed key and about to make several more calls with it has a reason to ask for the other form. The uncompressed serialization is the cheap one to open – parse is 0.269 us on 65 bytes against 2.326 on 33, both coordinates being there to read where a compressed key is a field square root – so reserialize(key, compressed=False) pays that root once and leaves every later call at the price of reading it.

Parameters:
  • pubkey_bytes – the public key, 33 or 65 bytes.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The same point, serialized as asked.

Raises:
  • ValueError – if the bytes are not a valid point in either serialization.

  • RuntimeError – if libsecp256k1 fails to serialize it, which no valid key can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> compressed = keys.pubkey_from_prvkey(1)
>>> uncompressed = keys.reserialize(compressed, compressed=False)
>>> uncompressed == keys.pubkey_from_prvkey(1, compressed=False)
True
>>> keys.reserialize(uncompressed) == compressed
True
btclib_secp256k1.keys.serialize(pubkey: Any, compressed: bool = True) → bytes[source]

Serialize an internal public key, in compressed form by default.

Parameters:
  • pubkey – the libsecp256k1 public key object, as parse returns.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The 33-byte compressed serialization, or the 65-byte uncompressed one.

Raises:

RuntimeError – if libsecp256k1 refuses the object – a NULL pointer, or a secp256k1_pubkey nothing has written to – or fails for any other reason, which a key it produced cannot make it do. Which of the two it was is on the thread rather than in the message, and context.check is what reads it.

Example

>>> from btclib_secp256k1 import keys
>>> uncompressed = keys.pubkey_from_prvkey(1, compressed=False)
>>> keys.serialize(keys.parse(uncompressed)).hex()[:10]
'0279be667e'

btclib_secp256k1.musig module

MuSig2, a two-round multi-signature scheme for BIP340 keys.

According to BIP327: https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki

The final signature this protocol produces is a plain BIP340 one, over the aggregate (and possibly tweaked) key pubkey_agg computes: ssa.verify is what checks it, and nothing here duplicates that call.

`secp256k1_musig_keyagg_cache` and `secp256k1_musig_session` have no serialization, unlike every other object this module wraps. They are, in the header’s own words, structures with “no serialization and parsing functions (yet)”, so they cannot cross this package’s boundary as octets – which is the rule the package docstring states for everything else. #87 declined an opaque handle for the reason that “a handle is a lifetime someone has to own and invalidate”, and _secret.py records the clause that reopens it: the handle “belongs where the signing state lives”. A MuSig2 session is exactly that, so KeyAggCache and Session below are that exception, taken rather than refused – refusing it would leave this module unable to do anything a caller could not already do through the raw lib. Neither object is secret: a keyagg cache is aggregated public keys, and the header says of a session that it “is not required to be kept secret for the signing protocol to be secure”. So the two are ordinary held state, the third outpost past the boundary alongside ssa.Signer and keys.PubkeyTweakChain – and, unlike either of those, one this module cannot build any other way.

A third object, the secret nonce, is the one that must be wiped, and `SecretNonce` is the model `ssa.Signer` already set for a held secret: a with statement that overwrites it on the way out, whether the block ended in a signature or in an exception, and a wiped object that refuses to sign rather than signing with the zeros left behind. libsecp256k1 already zeroes a secnonce inside partial_sign – “overwrites the given secnonce with zeros and will abort if given a secnonce that is all zeros” – which covers a session that runs to completion. What wipe and the with statement add is the session that does not: one abandoned between nonce_gen and partial_sign, which the C side never sees and so never overwrites. SECURITY.md records this as the second buffer in this package whose zeroing is asked for rather than done, ssa.Signer’s keypair being the first.

`partial_sign` verifies the partial signature it produces, by default. secp256k1_musig_partial_sign “does not verify the output partial signature, deviating from the BIP 327 specification”, and its header recommends verifying it with secp256k1_musig_partial_sig_verify “to prevent random or adversarially provoked computation errors” – which is BIP340’s own argument for ssa.sign’s verify, on a signature made from more moving parts than a solo one. verify=True is the default for the same reason it is there: the cost is a verification against a nonce and a key already in hand, and BIP327 recommends paying it.

A parse failure and a tweak failure both tell this package nothing. Verified against secp256k1_musig_pubnonce_parse: a refusal answers a bare 0, through an empty illegal callback, with no message for context.check to raise. pubkey_agg, nonce_agg and partial_sig_agg all take arrays of already-parsed objects, which the C API’s own calling convention already asks a caller to build – so KeyAggCache, nonce_agg and Session.partial_sig_agg each parse their array one contribution at a time rather than handing a comprehension to the array helper, and the index that failed is in the ValueError because the loop that found it already knew it.

class btclib_secp256k1.musig.KeyAggCache(pubkeys_bytes: Sequence[bytes | bytearray | memoryview])[source]

Bases: object

The aggregate of a set of signers’ public keys, and its tweaks.

What secp256k1_musig_keyagg_cache holds: the aggregate public key BIP327 calls Q, the per-signer coefficients it is built from, and the running sum of any tweak applied since. It has no serialization, which the module docstring explains, and holding it here rather than parsing it back from octets on every call is not an optimization over some other spelling – there is no other spelling, a cache built once being the only way nonce_gen, Session and SecretNonce.partial_sign can be handed the aggregation they need.

Building one is secp256k1_musig_pubkey_agg, which orders the keys exactly as given: keys.pubkey_sort first is BIP327’s own rule for reaching one aggregate key independently of the order the signers’ public keys arrived in, and is the caller’s to apply, this taking the sequence as it stands.

Parameters:

pubkeys_bytes – the signers’ public keys, 33 or 65 bytes each, in aggregation order. At least one is required.

Raises:
  • ValueError – if the sequence is empty, or if one of the keys is not a valid point – named by its position in the sequence, pubkeys_bytes[0] being “public key at index 0”.

  • RuntimeError – if libsecp256k1 fails to aggregate or serialize the result, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys, musig
>>> pubkeys = [keys.pubkey_from_prvkey(1), keys.pubkey_from_prvkey(2)]
>>> cache = musig.KeyAggCache(pubkeys)
>>> len(cache.agg_pubkey)
32
pubkey_ec_tweak_add(tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add a plain EC tweak to the aggregate key, e.g. a BIP32 step.

The MuSig2 counterpart of keys.pubkey_tweak_add: the tweaking method is the same, and what differs is that it is applied to this cache’s running aggregate rather than to an argument, so that signing for the tweaked key is SecretNonce.partial_sign against the cache afterwards rather than against a key nobody holds the private share of directly.

Parameters:
  • tweak – the tweak, 32 bytes or an int below 2**256. Callers are responsible for deriving it in a way that does not reduce MuSig2’s security, BIP32 being such a way.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized tweaked aggregate public key.

Raises:
  • ValueError – if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

pubkey_get(compressed: bool = True) → bytes[source]

Return the current aggregate public key, tweaks included.

Useful for the non-xonly form of the aggregate key – plain tweaking, or batch-verifying more than one key aggregation, which libsecp256k1 does not implement – where agg_pubkey is the x-only one and BIP341 taproot tweaking wants the full point.

Parameters:

compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized aggregate public key, as it stands after every pubkey_ec_tweak_add and pubkey_xonly_tweak_add made so far.

Raises:

RuntimeError – if libsecp256k1 fails to read or serialize it, which a cache this built cannot make it do.

pubkey_xonly_tweak_add(tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add a BIP341 x-only tweak to the aggregate key, for a taproot output.

xonly.tweak_add’s equation, applied to this cache instead of to a bare argument: xonly.tweak_add_check on the x-only form of what this returns, agg_pubkey and the same tweak, holds exactly as it does for a solo taproot key. This is required, rather than xonly.tweak_add on agg_pubkey directly, wherever a signature is wanted for the tweaked key: xonly.tweak_add leaves this cache untweaked, so a later SecretNonce.partial_sign would sign for the untweaked aggregate instead.

Parameters:
  • tweak – the tweak, 32 bytes or an int below 2**256. Callers are responsible for deriving it in a way that does not reduce MuSig2’s security, BIP341 being such a way.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized tweaked aggregate public key.

Raises:
  • ValueError – if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

class btclib_secp256k1.musig.SecretNonce(secnonce: Any, pubnonce: Any, pubkey: Any)[source]

Bases: object

A signer’s secret nonce, held between nonce_gen and partial_sign.

nonce_gen and nonce_gen_counter are the ways to obtain one – this is never built from octets, there being no parser for a secret nonce by design: “Avoid copying (or serializing) the secnonce. This reduces the possibility that it is used more than once for signing”, the header says, and a parser is exactly a way to make a copy.

What ssa.Signer documents about holding a keypair holds here for the same reason: wipe, or the with statement that calls it on the way out of the block, overwrites the secret nonce whether the block produced a signature or raised. partial_sign also wipes it, being the call whose whole point is to consume it – so a nonce used once is already gone by the time wipe might run again, and wiping a second time is not an error, exactly as ssa.Signer.wipe is not either.

A SecretNonce told neither is dropped holding the secret, cffi freeing the memory without overwriting it: SECURITY.md names this as the second buffer in this package whose zeroing is asked for rather than done.

Signing at most once is the whole reason this class exists, and on a free-threaded interpreter that has to be true of two threads calling partial_sign on the same object, not only of one: reading self._secnonce and then clearing it are two statements, and two threads each passing the read before either reaches the clear would both go on to drive secp256k1_musig_partial_sign over the same native secnonce at once, an unsynchronized concurrent access on the exact memory a nonce-reuse leak comes from. _take is where the read and the clear become one statement instead of two, under a lock private to this instance – there being no reason for two SecretNonce objects to serialize on each other’s locks, unlike the one shared libsecp256k1 context. Thread safety here is that lock, not the constness ssa.Signer’s relies on: this is the one buffer in the package meant to be read exactly once, where a keypair is meant to be read any number of times.

Parameters:
  • secnonce – the libsecp256k1 secret nonce object nonce_gen or nonce_gen_counter built.

  • pubnonce – the matching public nonce object, already parsed.

  • pubkey – the signer’s already-parsed public key, which partial_sign compares with the key it is given and keeps for its own verify.

Example

>>> from btclib_secp256k1 import keys, musig
>>> prvkey, pubkey = 1, keys.pubkey_from_prvkey(1)
>>> with musig.nonce_gen(pubkey, prvkey) as secnonce:
...     len(secnonce.pubnonce)
66
partial_sign(prvkey: bytes | bytearray | memoryview | int, keyagg_cache: KeyAggCache, session: Session, *, verify: bool = True) → bytes[source]

Produce this signer’s partial signature, and wipe the secret nonce.

Whatever the outcome – a signature, a refusal or an exception – the secret nonce this holds does not survive the call: wipe below is not needed afterwards, and calling it anyway is not an error, as the class docstring says.

Parameters:
  • prvkey – this signer’s private key, 32 bytes or an int below 2**256, matching the public key nonce_gen or nonce_gen_counter built this nonce for.

  • keyagg_cache – the key aggregation this session signs under, the same one the nonce was generated against if one was.

  • session – the session Session.__init__ built from the aggregate nonce this signer’s public nonce contributed to.

  • verify – whether to check the partial signature against this signer’s public nonce and public key before returning it, as the module docstring explains and BIP327’s own header recommends.

Returns:

The 32-byte partial signature.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache, or session is not a Session.

  • ValueError – if this secret nonce has already been spent or wiped, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if it does not match this secret nonce, or keyagg_cache or session do not.

  • RuntimeError – if libsecp256k1 fails to extract the public key of the private key given, which a private key already verified cannot make it do, or if verify asks and the partial signature does not verify, which no input reaching this far can make happen.

wipe() → None[source]

Overwrite the secret nonce, ending what this can sign.

Signing afterwards raises rather than signing with the zeros left behind, and there is no reviving it: nothing here keeps the nonce anywhere else, so a session abandoned this way is restarted with a fresh nonce_gen rather than resumed. Takes the same lock partial_sign does, so a wipe racing a partial_sign call on another thread cannot see the buffer between the two: whichever of them clears self._secnonce first is the one that goes on to act on it, and the other finds nothing left to take.

class btclib_secp256k1.musig.Session(aggnonce_bytes: bytes | bytearray | memoryview, msg32: bytes | bytearray | memoryview, keyagg_cache: KeyAggCache)[source]

Bases: object

A MuSig2 signing session, opened from the signers’ aggregate nonce.

What secp256k1_musig_nonce_process produces: the message, the aggregate nonce and the key aggregation are fixed into it, and SecretNonce.partial_sign, partial_sig_verify and partial_sig_agg all read the session rather than those three again. Like KeyAggCache, this has no serialization – the module docstring explains why that is taken as an exception here – and, also like it, holds nothing secret: the header says a session “is not required to be kept secret for the signing protocol to be secure”.

Parameters:
  • aggnonce_bytes – the aggregate of the signers’ public nonces, 66 bytes, as nonce_agg answered it.

  • msg32 – the 32-byte message this session signs.

  • keyagg_cache – the key aggregation this session signs under.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if aggnonce_bytes is not 66 bytes or not one BIP327 defines, or if the message is not 32 bytes.

  • RuntimeError – if libsecp256k1 fails to build the session, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys, musig
>>> pubkeys = [keys.pubkey_from_prvkey(1), keys.pubkey_from_prvkey(2)]
>>> cache = musig.KeyAggCache(pubkeys)
>>> secnonces = [
...     musig.nonce_gen(pk, i + 1) for i, pk in enumerate(pubkeys)
... ]
>>> pubnonces = [s.pubnonce for s in secnonces]
>>> aggnonce = musig.nonce_agg(pubnonces)
>>> session = musig.Session(aggnonce, bytes(32), cache)
partial_sig_agg(partial_sigs_bytes: Sequence[bytes | bytearray | memoryview]) → bytes[source]

Aggregate the signers’ partial signatures into the final one.

The result is a plain BIP340 signature – ssa.verify against the key aggregation’s pubkey_get (or its x-only form, agg_pubkey, where no tweak was applied) is how a caller checks it, this answering 1 “which does NOT mean the resulting signature verifies”, in the header’s own words.

Parameters:

partial_sigs_bytes – the signers’ partial signatures, 32 bytes each, one per signer of this session. At least one is required.

Returns:

The 64-byte aggregate signature.

Raises:
  • ValueError – if the sequence is empty, or if one of the partial signatures is not 32 bytes or not one below the curve order – named by its position in the sequence, partial_sigs_bytes[0] being “partial signature at index 0”.

  • RuntimeError – if libsecp256k1 fails to aggregate the result, which no valid input can make it do.

partial_sig_verify(partial_sig_bytes: bytes | bytearray | memoryview, pubnonce_bytes: bytes | bytearray | memoryview, pubkey_bytes: bytes | bytearray | memoryview, keyagg_cache: KeyAggCache) → bool[source]

Verify one signer’s partial signature, within this session.

Not required in a regular session: an aggregate signature that fails ssa.verify was made from at least one bad partial signature, but does not say which. This is the call that finds out, at the cost of one verification per signer instead of one for the aggregate.

Parameters:
  • partial_sig_bytes – the partial signature to verify, 32 bytes.

  • pubnonce_bytes – the public nonce of the signer it is attributed to, 66 bytes, exactly as it went into nonce_agg.

  • pubkey_bytes – the public key of that signer, 33 or 65 bytes, exactly as it went into the KeyAggCache this session’s key aggregation came from.

  • keyagg_cache – that key aggregation.

Returns:

True if the partial signature is valid for that signer’s nonce and key, within this session.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if any of the three byte arguments is not the right length or not one libsecp256k1 will read.

btclib_secp256k1.musig.aggnonce_parse(aggnonce_bytes: bytes | bytearray | memoryview, name: str = 'aggregate nonce') → Any[source]

Parse an aggregate public nonce into its internal representation.

Parameters:
  • aggnonce_bytes – the aggregate nonce, 66 bytes, as nonce_agg answered it.

  • name – what the nonce is, as the exception should call it.

Returns:

The libsecp256k1 aggregate nonce object.

Raises:

ValueError – if it is not 66 bytes, or not one BIP327 defines.

btclib_secp256k1.musig.aggnonce_serialize(aggnonce: Any) → bytes[source]

Serialize a parsed aggregate public nonce.

Parameters:

aggnonce – the libsecp256k1 aggregate nonce object, as aggnonce_parse returns.

Returns:

Its 66 bytes.

Raises:

RuntimeError – if libsecp256k1 refuses the object, which one it produced cannot make it do.

btclib_secp256k1.musig.nonce_agg(pubnonces_bytes: Sequence[bytes | bytearray | memoryview]) → bytes[source]

Aggregate the signers’ public nonces into one, for Session.

Any party can do this, trusted or not: an aggregate nonce computed wrong makes the final signature invalid rather than makes it forge, so BIP327 lets an untrusted coordinator collect the round-one nonces and hand every signer this one value back instead of every other signer’s.

Parameters:

pubnonces_bytes – the signers’ public nonces, 66 bytes each, in the order Session.partial_sig_agg will later take their partial signatures. At least one is required.

Returns:

The 66-byte aggregate nonce.

Raises:
  • ValueError – if the sequence is empty, or if one of the nonces is not 66 bytes or not one BIP327 defines – named by its position in the sequence, pubnonces_bytes[0] being “public nonce at index 0”.

  • RuntimeError – if libsecp256k1 fails to aggregate or serialize the result, which no valid input can make it do.

btclib_secp256k1.musig.nonce_gen(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int | None = None, msg32: bytes | bytearray | memoryview | None = None, keyagg_cache: KeyAggCache | None = None, extra_input32: bytes | bytearray | memoryview | None = None) → SecretNonce[source]

Start a signing session by generating a nonce.

Round one of BIP327: the public nonce this answers is sent to every other signer, or to whoever aggregates them, and the secret nonce is kept – held by the SecretNonce this returns, until a matching Session exists to call its partial_sign.

The 32 bytes of session randomness BIP327 requires, unique to this call and never reused, is not a parameter: it is generated here with secrets.token_bytes, the same source the rest of this package’s randomness comes from, because the one thing a caller could do with it – supply their own – is the one thing the header warns against doing twice.

Parameters:
  • pubkey_bytes – the public key of the signer this nonce is for, 33 or 65 bytes. The secnonce this answers can only sign for a keypair matching it, which SecretNonce.partial_sign checks before it asks libsecp256k1 to sign.

  • prvkey – this signer’s private key, 32 bytes or an int below 2**256, if already known. Optional, and folded into the nonce derivation to increase misuse-resistance where given; using the same private key across several MuSig2 sessions is fine.

  • msg32 – the 32-byte message this session will sign, if already known. Optional, for the same reason.

  • keyagg_cache – the key aggregation this session signs under, if already known. Optional, for the same reason.

  • extra_input32 – 32 bytes that do not repeat in normal use, such as the current time, folded into the derivation alongside the above. Optional, and no substitute for the session randomness above, which is what carries the uniqueness requirement.

Returns:

The secret nonce, held rather than returned as octets – the module docstring says why, and SecretNonce is what a caller wipes if this session is abandoned before partial_sign.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if the public key is not a valid point, if the private key is given and is not 32 bytes, does not fit in them, or is not in [1, n-1], or if msg32 or extra_input32 is given and is not 32 bytes.

btclib_secp256k1.musig.nonce_gen_counter(prvkey: bytes | bytearray | memoryview | int, nonrepeating_cnt: int, msg32: bytes | bytearray | memoryview | None = None, keyagg_cache: KeyAggCache | None = None, extra_input32: bytes | bytearray | memoryview | None = None) → SecretNonce[source]

Start a signing session, deriving the nonce from a counter instead.

The alternative to nonce_gen for a signer with a non-repeating counter rather than good randomness available – a hardware signer keeping one in place of an RNG it does not trust, which is what this exists for. The counter must never repeat for this private key: two calls with the same keypair and the same nonrepeating_cnt, on any device signing with that key, reuse the nonce.

Parameters:
  • prvkey – this signer’s private key, 32 bytes or an int below 2**256. Mandatory here, where nonce_gen’s is optional: the counter’s uniqueness is a property of the key it counts for.

  • nonrepeating_cnt – the counter value, an int in [0, 2**64 - 1], unique to this call for this private key.

  • msg32 – the 32-byte message this session will sign, if already known.

  • keyagg_cache – the key aggregation this session signs under, if already known.

  • extra_input32 – 32 bytes that do not repeat in normal use, folded into the derivation alongside the above.

Returns:

The secret nonce, as nonce_gen returns it.

Raises:
  • TypeError – if nonrepeating_cnt is not an int, or if keyagg_cache is not a KeyAggCache.

  • ValueError – if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], if nonrepeating_cnt is out of range, if extra_input32 is given and is not 32 bytes, or if msg32 is given and is not 32 bytes.

  • RuntimeError – if libsecp256k1 fails to extract the public key or to generate the nonce, which a private key already verified cannot make it do.

btclib_secp256k1.musig.partial_sig_parse(partial_sig_bytes: bytes | bytearray | memoryview, name: str = 'partial signature') → Any[source]

Parse a partial signature into its internal representation.

Parameters:
  • partial_sig_bytes – the partial signature, 32 bytes, as SecretNonce.partial_sign answered it.

  • name – what the signature is, as the exception should call it.

Returns:

The libsecp256k1 partial signature object.

Raises:

ValueError – if it is not 32 bytes, or not one below the curve order.

btclib_secp256k1.musig.partial_sig_serialize(partial_sig: Any) → bytes[source]

Serialize a parsed partial signature.

Parameters:

partial_sig – the libsecp256k1 partial signature object, as partial_sig_parse returns.

Returns:

Its 32 bytes.

Raises:

RuntimeError – if libsecp256k1 refuses the object, which one it produced cannot make it do.

btclib_secp256k1.musig.pubnonce_parse(pubnonce_bytes: bytes | bytearray | memoryview, name: str = 'public nonce') → Any[source]

Parse a signer’s public nonce into its internal representation.

Parameters:
  • pubnonce_bytes – the public nonce, 66 bytes, as nonce_gen or nonce_gen_counter answered it through the SecretNonce they built.

  • name – what the nonce is, as the exception should call it, for a caller parsing more than one array of them.

Returns:

The libsecp256k1 public nonce object.

Raises:

ValueError – if it is not 66 bytes, or not one BIP327 defines.

btclib_secp256k1.musig.pubnonce_serialize(pubnonce: Any) → bytes[source]

Serialize a parsed public nonce.

Parameters:

pubnonce – the libsecp256k1 public nonce object, as pubnonce_parse returns.

Returns:

Its 66 bytes.

Raises:

RuntimeError – if libsecp256k1 refuses the object, which one it produced cannot make it do.

btclib_secp256k1.recovery module

ECDSA public key recovery.

A recoverable signature is the compact one and the recovery id together, and parse_compact and serialize_compact are what open and close that pair; dsa.serialize_der is what writes the same signature without the id, which is what to_der drops.

btclib_secp256k1.recovery.parse_compact(signature_bytes: bytes | bytearray | memoryview, recid: int) → Any[source]

Parse a compact signature and its recovery id.

What dsa.parse_compact is to a signature without an id, and the argument of the private halves here.

Parameters:
  • signature_bytes – the 64-byte compact signature.

  • recid – the recovery id, 0 to 3.

Returns:

The libsecp256k1 recoverable signature object.

Raises:
  • TypeError – if the recovery id is not an int.

  • ValueError – if the signature is not 64 bytes, if r or s is not in [1, n-1], or if recid is outside 0 to 3.

btclib_secp256k1.recovery.recover(msg_bytes: bytes | bytearray | memoryview, signature_bytes: bytes | bytearray | memoryview, recid: int, compressed: bool = True) → bytes[source]

Recover the public key from a recoverable ECDSA signature.

Parameters:
  • msg_bytes – the 32-byte hash the signature was made over.

  • signature_bytes – the 64-byte compact signature.

  • recid – the recovery id, 0 to 3. A wrong one recovers a different key rather than failing, so it is part of the signature and not a guess.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized recovered public key.

Raises:
  • TypeError – if the recovery id is not an int.

  • ValueError – if the message hash is not 32 bytes, if the signature is not 64 bytes or has r or s outside [1, n-1], if recid is outside 0 to 3, or if no key can be recovered.

  • RuntimeError – if libsecp256k1 fails to serialize the key, which no input can make it do.

btclib_secp256k1.recovery.serialize_compact(signature: Any) → tuple[bytes, int][source]

Serialize an internal recoverable signature, id and all.

Parameters:

signature – the libsecp256k1 recoverable signature object, as parse_compact returns.

Returns:

The 64-byte compact signature and its recovery id, which is the pair parse_compact reads back.

Raises:
  • RuntimeError – if libsecp256k1 refuses the object – one it cannot read – or fails to serialize for any other reason, which a signature it parsed cannot make it do. context.check is what tells the two apart.

  • RuntimeError – if libsecp256k1 fails for any other reason, which a signature it parsed cannot make it do.

btclib_secp256k1.recovery.sign(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, *, verify: bool = True, pubkey: bytes | bytearray | memoryview | None = None) → tuple[bytes, int][source]

Create a recoverable ECDSA signature.

verify is what dsa.sign and ssa.sign take, in the shape this signature asks for: the key is recovered from the signature and compared with the signer’s, rather than the signature verified against it. That is Bitcoin Core’s own distinction between CKey::Sign and CKey::SignCompact, and the reason is the recovery id – a verification does not look at it, so a signature carrying the wrong one verifies and then recovers a key that is not the signer’s. Recovering is what a caller of this module does with the answer, so the check is the one that question deserves.

What it catches is not a bad argument – those have all raised by then – but a computation gone wrong, by bad memory or by a fault induced on purpose, whose cost is a published signature that is invalid or attributes itself to somebody else.

It is also the dearest of the three, which is the other half of what a caller weighing it needs: 34.41 microseconds against 12.02, where dsa.sign measured beside it in the same session was 31.54 against 12.06 and ssa.sign is 28.57 against 15.87. A recovery is about a verification’s work, and the comparison and the derivation are the rest. changelog/v0.8.0.3.md names the session.

pubkey is how a caller already holding the key stops the comparison deriving it, and what it removes is the same call dsa.sign removes, at the same price: the check here is the dearest of the three, the saving is not. In a session of its own: 35.00 microseconds with the key derived, 29.67 with a compressed one handed in, 27.49 with an uncompressed one, against 12.07 unchecked and a noise of ±0.02. The 7.51 that removes is secp256k1_ec_pubkey_create, which timed alone there is 7.53, and what stays is the recovery and the comparison – about 3 microseconds over what dsa.sign pays for a verification, whether the key is handed in or derived. The README carries both tables, and _abort_unless_recovered what the trust costs in diagnosis.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – 32 bytes of extra entropy mixed into the nonce, or None for the RFC6979 nonce alone.

  • verify – whether to recover the key and refuse a signature that does not give the signer’s back. It costs a recovery, a point multiplication and a comparison, and False is what a caller that has measured those against its own threat model passes.

  • pubkey – the public key of this private key, where the caller already holds it, so that the comparison does not derive it again. That derivation is the multiplication named above, and this is how not to pay it twice. It is taken on trust rather than checked against the private key, which would cost the multiplication it saves; where it is wrong, the check says so and says which of the three things went wrong. Refused beside verify=False, which declines the check it is for.

Returns:

The 64-byte compact signature and its recovery id. The id is 0 or 1 for any signature this function produces; 2 and 3 exist for a nonce point whose x exceeded the group order, which no key reaches in practice.

Raises:
  • ValueError – if the message hash is not 32 bytes, if aux_rand32 is given and is not 32 bytes, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], if pubkey is not a public key or is given beside verify=False, or if pubkey is not this private key’s – which is told apart from the failure below rather than reported as it.

  • RuntimeError – if libsecp256k1 fails to serialize the signature, which no input can make it do, or if verify asks and the signature does not recover the key that made it.

Example

>>> import hashlib
>>> from btclib_secp256k1 import keys, recovery
>>> msg = hashlib.sha256(b"hello").digest()
>>> signature, recid = recovery.sign(msg, 1)
>>> pubkey = recovery.recover(msg, signature, recid)
>>> pubkey == keys.pubkey_from_prvkey(1)
True
btclib_secp256k1.recovery.to_der(signature_bytes: bytes | bytearray | memoryview, recid: int) → bytes[source]

Convert a recoverable signature into a plain DER signature.

The recovery id is dropped, as it is not part of the DER encoding. Beware: the conversion does not normalize the signature, so a high-s input is rejected by dsa.verify; signatures produced by sign are always low-s.

Parameters:
  • signature_bytes – the 64-byte compact signature.

  • recid – the recovery id, 0 to 3. It is required because libsecp256k1 parses the pair before dropping the id, and refuses one out of range.

Returns:

The same signature in DER encoding, s unchanged.

Raises:
  • TypeError – if the recovery id is not an int.

  • ValueError – if the signature is not 64 bytes or has r or s outside [1, n-1], or if recid is outside 0 to 3.

  • RuntimeError – if libsecp256k1 fails to convert or serialize the signature, which no input can make it do.

btclib_secp256k1.silentpayments module

Silent Payments.

According to BIP352: https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki

What libsecp256k1 implements, and therefore what is wrapped here, is the elliptic curve half of the protocol: the sum of the input keys, the Diffie-Hellman shared secret, and the outputs and tweaks derived from it. Addresses, output script types and transaction parsing are not part of it – which is why every function here takes keys and a serialized outpoint rather than a transaction, and why deciding which inputs are eligible, and which of the eligible ones are taproot, is the caller’s: BIP352 states those rules over scripts, and there is no script here.

This is the module with the most keys to parse per call, and so the one where the private halves earn the most: a wallet scanning block after block parses each of its own keys once and hands the objects to _scan_outputs_, where scan_outputs parses the spend key and every transaction output again at every transaction.

btclib_secp256k1.silentpayments.create_outputs(recipients_bytes: Sequence[tuple[bytes | bytearray | memoryview, bytes | bytearray | memoryview]], outpoint_smallest36: bytes | bytearray | memoryview, taproot_prvkeys: Sequence[bytes | bytearray | memoryview | int] = (), prvkeys: Sequence[bytes | bytearray | memoryview | int] = ()) → list[bytes][source]

Create the taproot outputs paying a list of Silent Payments addresses.

This is the sender’s side, and it needs the private keys of every input the payment is funded from: the shared secret is derived from their sum. Which inputs are eligible is BIP352’s rule over their scripts and so the caller’s to apply, as is the split below – a taproot input contributes the key of its even-y point, which is why its private key goes to taproot_prvkeys and not to prvkeys.

All the outputs returned must appear in the transaction. Dropping one can make the others unfindable by their recipient, the derivation being over the whole set.

Parameters:
  • recipients_bytes – the addresses to pay, each a pair of the recipient’s scan and spend public keys, 33 or 65 bytes each. The same address may appear more than once, which pays it that many outputs. At least one is required.

  • outpoint_smallest36 – the 36-byte serialization of the lexicographically smallest outpoint of all the transaction inputs, eligible or not. Choosing it is the caller’s, and choosing it wrongly makes the payment unfindable rather than invalid – BIP352’s own vectors are what to check an implementation of that against.

  • taproot_prvkeys – the private keys of the taproot inputs, 32 bytes or an int below 2**256 each.

  • prvkeys – the private keys of the other eligible inputs.

Returns:

The 32-byte x-only public key of one taproot output per recipient, in the order the recipients were given.

Raises:
  • TypeError – if taproot_prvkeys or prvkeys is a str, a cffi array of primitives or a one-dimensional buffer of anything but byte-strings, objects or structs (bytes, bytearray, memoryview, array.array): one key, not a list of them.

  • ValueError – if no recipient or no private key is given, if the outpoint is not 36 bytes, if any public key is not a valid point, if any private key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if libsecp256k1 refuses the set – the sum of the private keys being zero, an output landing on an invalid point, or more outputs asked of one scan key than BIP352 allows it.

Example

>>> from btclib_secp256k1 import keys, silentpayments
>>> scan_pubkey = keys.pubkey_from_prvkey(1)
>>> spend_pubkey = keys.pubkey_from_prvkey(2)
>>> outputs = silentpayments.create_outputs(
...     [(scan_pubkey, spend_pubkey)], bytes(36), prvkeys=[3]
... )
>>> len(outputs), len(outputs[0])
(1, 32)
btclib_secp256k1.silentpayments.label(scan_prvkey: bytes | bytearray | memoryview | int, m: int) → tuple[bytes, bytes][source]

Create the m-th label of a scan key, and its tweak.

A label lets one scan key receive at more than one address: the recipient publishes labeled_spend_pubkey instead of the spend public key, and hands the label back to scan_outputs so that an output paid to it is recognized. BIP352 reserves m = 0 for change.

Labels cost interoperability and, in a light client, scanning speed: BIP352 recommends creating the change label and no other, and distributing the unlabeled address.

Parameters:
  • scan_prvkey – the recipient’s scan private key, 32 bytes or an int below 2**256.

  • m – which label, an int below 2**32. Zero is BIP352’s change label.

Returns:

The 33-byte label, which is what scan_outputs is keyed on, and the 32-byte tweak, which is the secret that spends what was paid to it.

Raises:
  • TypeError – if m is not an int.

  • ValueError – if m is out of range, or if the scan key is not 32 bytes, does not fit in them, or is not in [1, n-1].

Example

>>> from btclib_secp256k1 import silentpayments
>>> label, tweak = silentpayments.label(1, 0)
>>> len(label), len(tweak)
(33, 32)
btclib_secp256k1.silentpayments.labeled_spend_pubkey(spend_pubkey_bytes: bytes | bytearray | memoryview, label_bytes: bytes | bytearray | memoryview, compressed: bool = True) → bytes[source]

Add a label to a spend public key.

The result is the spend public key of the Silent Payments address that label opens: an address is the recipient’s scan public key and this key, and what makes them different addresses of one scan key is this sum.

Parameters:
  • spend_pubkey_bytes – the recipient’s unlabeled spend public key, 33 or 65 bytes.

  • label_bytes – the 33-byte label, as label returns it.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized labeled spend public key.

Raises:
  • ValueError – if the spend public key is not a valid point, if the label is not 33 bytes or is not one, or if the two sum to the point at infinity, which has no serialization and which a label BIP352 made cannot produce.

  • RuntimeError – if libsecp256k1 fails to serialize the result, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys, silentpayments
>>> spend_pubkey = keys.pubkey_from_prvkey(2)
>>> label, _ = silentpayments.label(1, 0)
>>> len(silentpayments.labeled_spend_pubkey(spend_pubkey, label))
33
btclib_secp256k1.silentpayments.parse_label(label_bytes: bytes | bytearray | memoryview) → Any[source]

Parse a 33-byte label into its internal representation.

What keys.parse is to a public key, this is to a label, and for the same reason: those 33 bytes are a compressed point, so reading them back is a field square root. A recipient that keeps its labels as bytes – the cache scan_outputs takes is exactly that – parses one here and hands it to _labeled_spend_pubkey_, rather than through the public half which parses it again at every address.

Parameters:

label_bytes – the label, as label returned it.

Returns:

The libsecp256k1 label object.

Raises:

ValueError – if it is not 33 bytes, or not a valid point.

btclib_secp256k1.silentpayments.prevouts_summary(outpoint_smallest36: bytes | bytearray | memoryview, taproot_pubkeys_bytes: Sequence[bytes | bytearray | memoryview] = (), pubkeys_bytes: Sequence[bytes | bytearray | memoryview] = ()) → bytes[source]

Summarize the inputs of a transaction, for scanning it.

This is what the recipient’s side needs of a transaction, and all of it: the sum of its eligible input public keys and its smallest outpoint, computed once and handed to scan_outputs for every scan key that scans the transaction.

The keys are split the way BIP352 reads them: a taproot input contributes the even-y point of its 32-byte x-only key, any other eligible input the full key its script commits to.

Parameters:
  • outpoint_smallest36 – the 36-byte serialization of the lexicographically smallest outpoint of all the transaction inputs, eligible or not.

  • taproot_pubkeys_bytes – the x-only public keys of the taproot inputs, 32, 33 or 65 bytes each: an x names the even-y point whichever way it arrives, for which see xonly.parse.

  • pubkeys_bytes – the public keys of the other eligible inputs, 33 or 65 bytes each.

Returns:

The 36-byte smallest outpoint followed by the 33-byte compressed sum of the eligible input public keys, a taproot key counting as its even-y point. It is a serialization, portable across platforms and versions, and holds no secret. scan_outputs parses it, and refuses what is not one.

Raises:

ValueError – if no public key is given, if the outpoint is not 36 bytes, if any key is not a valid point, or if the inputs sum to the point at infinity, which is BIP352’s “not a Silent Payments transaction” and which the recipient skips.

Example

>>> from btclib_secp256k1 import keys, silentpayments
>>> summary = silentpayments.prevouts_summary(
...     bytes(36), pubkeys_bytes=[keys.pubkey_from_prvkey(3)]
... )
>>> len(summary) == silentpayments.SUMMARY_SIZE
True
btclib_secp256k1.silentpayments.scan_outputs(tx_outputs_bytes: Sequence[bytes | bytearray | memoryview], scan_prvkey: bytes | bytearray | memoryview | int, summary_bytes: bytes | bytearray | memoryview, spend_pubkey_bytes: bytes | bytearray | memoryview, labels: Mapping[bytes, bytes | bytearray | memoryview] | None = None) → list[tuple[bytes, bytes, bytes | None]][source]

Find the outputs of a transaction that pay a Silent Payments address.

This is the recipient’s side. It needs the scan private key, because the shared secret is derived from it, and the unlabeled spend public key even where the address published was a labeled one: what a label changes is the address, not what is scanned for.

Parameters:
  • tx_outputs_bytes – the 32-byte x-only public keys of the transaction’s taproot outputs, in vout order. At least one is required.

  • scan_prvkey – the recipient’s scan private key, 32 bytes or an int below 2**256.

  • summary_bytes – the summary of the transaction’s inputs, as prevouts_summary returned it: SUMMARY_SIZE bytes, parsed here, so that bytes read from elsewhere are checked like any other key.

  • spend_pubkey_bytes – the recipient’s unlabeled spend public key, 33 or 65 bytes.

  • labels – the recipient’s label cache, mapping each 33-byte label to its 32-byte tweak, or None where no labeled address was published. Only a label in here can be found: BIP352 makes recognizing one a lookup rather than a computation, and this is that lookup. The keys are bytes and only bytes, unlike every other argument here: a bytearray and a memoryview are what a mapping cannot be keyed on, being unhashable.

Returns:

One triple per output found, in vout order – its 32-byte x-only public key, the 32-byte tweak to add to the spend private key to spend it, and the 33-byte label it was found with, or None where it was paid to the unlabeled address. An empty list where the transaction pays this address nothing.

Raises:
  • TypeError – if labels is given and is not a mapping.

  • ValueError – if no output is given, if any of them is not a valid x-only public key, if the scan key is not 32 bytes, does not fit in them, or is not in [1, n-1], if the summary is not the right length or does not hold a valid public key, if the spend public key is not a valid point, if a label or a label tweak is the wrong length, if a label tweak is not in [1, n-1], or if libsecp256k1 refuses the scan.

Example

>>> from btclib_secp256k1 import keys, silentpayments
>>> scan_pubkey = keys.pubkey_from_prvkey(1)
>>> spend_pubkey = keys.pubkey_from_prvkey(2)
>>> outputs = silentpayments.create_outputs(
...     [(scan_pubkey, spend_pubkey)], bytes(36), prvkeys=[3]
... )
>>> summary = silentpayments.prevouts_summary(
...     bytes(36), pubkeys_bytes=[keys.pubkey_from_prvkey(3)]
... )
>>> found = silentpayments.scan_outputs(
...     outputs, 1, summary, spend_pubkey
... )
>>> [pubkey for pubkey, _tweak, _label in found] == outputs
True
btclib_secp256k1.silentpayments.serialize_label(label_obj: Any) → bytes[source]

Serialize a label libsecp256k1 produced.

What keys.serialize is to a public key: the 33 bytes are what a recipient keys its label cache on, and what label answers with.

Parameters:

label_obj – the libsecp256k1 label object, as _label_ returns.

Returns:

Its 33 bytes, which are the compressed point it is.

Raises:

RuntimeError – if libsecp256k1 refuses the object – one it cannot read – or fails to serialize for any other reason, which a label it produced cannot make it do. context.check is what tells the two apart.

btclib_secp256k1.ssa module

Variant of Elliptic Curve Schnorr Signature Algorithm (ECSSA).

According to BIP340-Schnorr: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki

class btclib_secp256k1.ssa.Signer(prvkey: bytes | bytearray | memoryview | int)[source]

Bases: object

Sign under one private key repeatedly, building the keypair once.

sign builds a secp256k1_keypair and wipes it before returning, so a caller signing a second message under the same key builds it again – and that keypair is about half of what a BIP340 signature costs here, being the point multiplication of the public key. This holds one across calls instead, so the first signature is the only one paying for it, and sign and sign_custom are the same signatures over it. pubkey() is the x-only key those signatures verify against, read off that same keypair rather than derived a second time.

What that hands the caller is the lifetime of a secret, and it is the trade this makes deliberately. A keypair is the private key in libsecp256k1’s own layout, in memory this package owns and can overwrite; the two functions own one for the length of a call and wipe it in a finally, while a signer holds it until it is told to let go. wipe is that instruction, and the with statement is how to give it without having to remember: on the way out of the block the keypair is overwritten, whether the block ended in a signature or in an exception. A wiped signer refuses to sign rather than signing with the zeros, and it cannot be revived – the private key is kept nowhere else here, which is the point – so signing again means building another one.

A signer told neither way is dropped holding the key: cffi frees the keypair without overwriting it, and nothing here runs behind it to do so. A finalizer would, at a time nothing specifies, which is a guarantee in the shape of one and not in fact – so the instruction stays the caller’s to give, and SECURITY.md records this as the one buffer of the package whose zeroing is asked for rather than done.

What it does not change is the python side: the bytes or int handed in here is a python object like any other, and SECURITY.md records why that copy cannot be taken back.

Parameters:

prvkey – the private key, 32 bytes or an int below 2**256. The keypair holds it as given. Every signature is of its x-only public key, so libsecp256k1 negates the key where its y is odd, as BIP340 prescribes – inside each signature, not once here.

Raises:

ValueError – if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

Example

>>> from btclib_secp256k1 import keys, ssa, xonly
>>> msg, prvkey = bytes(32), 1
>>> pubkey, _ = xonly.from_pubkey(keys.pubkey_from_prvkey(prvkey))
>>> with ssa.Signer(prvkey) as signer:
...     sig = signer.sign(msg, bytes(32))
>>> sig == ssa.sign(msg, prvkey, bytes(32))
True
>>> ssa.verify(msg, pubkey, sig)
True
pubkey() → tuple[bytes, int][source]

Return the x-only public key this signer signs under.

The keypair already holds the point, so this is a read of it and not a multiplication: xonly.from_prvkey is the same answer derived from the private key, and costs the point multiplication this signer has already paid for.

Returns:

The 32-byte x coordinate BIP340 verifies against, and the parity of the y of the private key’s own point: 0 for even, 1 for odd. A signature made here verifies against the 32 bytes whichever the parity is, which is what the negation BIP340 prescribes is for.

Raises:
  • ValueError – if this signer has been wiped.

  • RuntimeError – if libsecp256k1 fails to convert or serialize the key, which a keypair it built cannot make it do.

sign(msg_bytes: bytes | bytearray | memoryview, aux_rand32: bytes | bytearray | memoryview | None = None, *, verify: bool = True) → bytes[source]

Create a Schnorr signature of a 32-byte message hash.

The signature ssa.sign makes of the same arguments, over the keypair built when this signer was.

Parameters:
  • msg_bytes – the 32-byte message hash.

  • aux_rand32 – the 32 bytes of auxiliary randomness BIP340 defines, or None for fresh randomness.

  • verify – whether to check the signature under this signer’s own public key before returning it, as ssa.sign documents and BIP340 prescribes. It is the same cost here as there, the point being read off the keypair either way – and because the signature is the cheaper, the keypair being built already, it is the larger share: 20.82 microseconds against 8.18, where ssa.sign is 28.57 against 15.87. The check is more than half again this signature and four fifths of that one, which is the same increment either way. 1.545 of a signature here against dsa.sign’s 1.607 is the reading worth having: this is where BIP340’s cheaper check costs what ECDSA’s does, the keypair already built having taken the signature down to where the increment dominates.

Returns:

The 64-byte signature.

Raises:
  • ValueError – if the message hash is not 32 bytes, if aux_rand32 is given and is not 32 bytes, or if this signer has been wiped.

  • RuntimeError – if libsecp256k1 fails to sign, which no input can make it do, or if verify asks and the signature does not verify.

sign_custom(msg_bytes: bytes | bytearray | memoryview, aux_rand32: bytes | bytearray | memoryview | None = None, *, verify: bool = True) → bytes[source]

Create a Schnorr signature of a message of any length.

The signature ssa.sign_custom makes of the same arguments, and what that function says about hashing a message first holds here too.

Parameters:
  • msg_bytes – the message, of any length.

  • aux_rand32 – the 32 bytes of auxiliary randomness, or None for fresh randomness.

  • verify – whether to check the signature under this signer’s own public key before returning it, as ssa.sign documents and BIP340 prescribes.

Returns:

The 64-byte signature.

Raises:
  • ValueError – if aux_rand32 is given and is not 32 bytes, or if this signer has been wiped.

  • RuntimeError – if libsecp256k1 fails to sign, which no input can make it do, or if verify asks and the signature does not verify.

wipe() → None[source]

Overwrite the keypair, ending what this signer can do.

The deliberate half of the trade above, and what __exit__ calls. Signing afterwards raises rather than signing with the zeros left behind, and wiping twice is not an error: a signer used as a context manager and wiped inside the block is the case that makes it one.

btclib_secp256k1.ssa.nonce_bip340(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None) → bytes[source]
btclib_secp256k1.ssa.nonce_bip340(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, *, into: bytearray | memoryview) → None
btclib_secp256k1.ssa.nonce_bip340(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, *, into: bytearray | memoryview | None) → bytes | None

Return the BIP340 nonce sign derives for a message and a key.

libsecp256k1 exports its nonce function as a callable pointer, and this calls through it with what secp256k1_schnorrsig_sign32 passes: the message, the key the signature is made with, the x-only public key, the BIP0340/nonce tag and the auxiliary randomness. So what comes back is the k of the signature sign makes of the same arguments – the first 32 octets of that signature are the x of k times the generator, which is what tests/nonces_test.py holds it to.

The key BIP340 signs with is the one of the even-y point, so a private key whose point has odd y enters the derivation negated, and the x-only public key with it. Both are derived here rather than asked of the caller: that is what makes the answer the nonce of the signature rather than of a key nobody signs with.

It is here for the reason dsa.nonce_rfc6979 is: a python implementation of BIP340’s nonce derivation has published vectors and no oracle, and the aux is where implementations diverge.

The nonce is the secret the signature is built on, and reading it into python takes it out of constant-time code: see dsa.nonce_rfc6979, which says what that means.

Parameters:
  • msg_bytes – the message, of any length.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – the 32 bytes of auxiliary randomness, or None for the derivation libsecp256k1 makes without any – which answers what 32 zero octets answer, the tagged hash of those zeros being what it substitutes. sign given None generates 32 fresh octets instead of passing none, so what reproduces a signature’s nonce is the aux that signature was made with.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte nonce – or None where into was given and holds it.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if aux_rand32 is given and is not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to derive one, which the tag above keeps it from doing.

Example

>>> from btclib_secp256k1 import ssa
>>> nonce = ssa.nonce_bip340(bytes(32), 1, bytes(32))
>>> len(nonce)
32
btclib_secp256k1.ssa.sign(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, *, verify: bool = True) → bytes[source]

Create a Schnorr signature of a 32-byte message hash.

The keypair this needs is built from the private key and wiped before returning, which is the right cost for one signature and half the cost of each of several: signing more than once under one key is Signer, which builds it once.

verify is BIP340’s own last step – “If Verify(bytes(P), m, sig) returns failure, abort” – and the one thing here that is more than a libsecp256k1 call. A computation gone wrong, memory gone bad or a fault induced on purpose, yields a signature that is invalid and may leak something about the secret key, and not answering with one is the whole of the protection. It is on by default because the BIP puts it inside the algorithm rather than beside it, and it can be turned off because the BIP says that too, “if the computation cost is prohibitive” – which here is one verification and no multiplication: 28.57 microseconds against 15.87, where ECDSA’s same check is 31.67 against 12.15 for having to derive the public key first. The cheaper of the two is the one a specification asks for. changelog/v0.8.0.3.md names the session those come from, and _abort_unless_verified carries the rest of the reasoning.

Parameters:
  • msg_bytes – the 32-byte message hash.

  • prvkey – the private key, 32 bytes or an int below 2**256. The signature is of its x-only public key, so libsecp256k1 negates the key where its y is odd, as BIP340 prescribes.

  • aux_rand32 – the 32 bytes of auxiliary randomness BIP340 defines, or None for fresh randomness. Never a shorter value: BIP340 defines a 32-byte a, and padding a short one would make a caller mistake a valid argument.

  • verify – whether to check the signature under its own public key before returning it. It costs a verification and no point multiplication, the keypair already holding the point, and False is what a caller that has measured that against its own threat model passes.

Returns:

The 64-byte signature.

Raises:
  • ValueError – if the message hash is not 32 bytes, if aux_rand32 is given and is not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to sign, which no input can make it do, or if verify asks and the signature does not verify.

Example

>>> from btclib_secp256k1 import keys, ssa, xonly
>>> msg, prvkey = bytes(32), 1
>>> pubkey, _ = xonly.from_pubkey(keys.pubkey_from_prvkey(prvkey))
>>> sig = ssa.sign(msg, prvkey, bytes(32))
>>> ssa.verify(msg, pubkey, sig)
True
>>> ssa.sign(msg, prvkey, bytes(32), verify=False) == sig
True
btclib_secp256k1.ssa.sign_custom(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, aux_rand32: bytes | bytearray | memoryview | None = None, *, verify: bool = True) → bytes[source]

Create a Schnorr signature of a message of any length.

BIP340 signs messages of arbitrary length, while bitcoin only ever signs a 32-byte hash of what it commits to: unless the protocol at hand says otherwise, hash the message with a tag of its own (hashes.tagged_sha256) and sign that instead, so that a signature cannot be read as one of a different protocol. For a 32-byte message the signature is the one sign returns. Signing more than one message under one key is Signer.sign_custom, for the reason given there.

Parameters:
  • msg_bytes – the message, of any length.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • aux_rand32 – the 32 bytes of auxiliary randomness, or None for fresh randomness.

  • verify – whether to check the signature under its own public key before returning it, as sign documents and BIP340 prescribes. One verification and no point multiplication, which on a 32-byte message is sign’s 28.57 microseconds against 15.87, the extraparams struct this fills aside; a longer message costs more to sign and to check by the same hash.

Returns:

The 64-byte signature.

Raises:
  • ValueError – if aux_rand32 is given and is not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to sign, which no input can make it do, or if verify asks and the signature does not verify.

btclib_secp256k1.ssa.verify(msg_bytes: bytes | bytearray | memoryview, pubkey_bytes: bytes | bytearray | memoryview, signature_bytes: bytes | bytearray | memoryview) → bool[source]

Verify a Schnorr signature against a public key, in any of its forms.

BIP340 verifies against an x coordinate, so 02 || x, 03 || x and 04 || x || y are one key and any of the three is accepted: the y is not consulted, a signer whose point has odd y having signed with n - d for exactly that reason. Which form to hand in is a question of cost – the uncompressed one is read rather than lifted, see xonly.parse.

Parameters:
  • msg_bytes – the message, of any length. It is the 32-byte hash for a signature made by sign.

  • pubkey_bytes – the public key, 32, 33 or 65 bytes.

  • signature_bytes – the 64-byte signature.

Returns:

True if the signature is valid for that key and message.

Raises:

ValueError – if the signature is not 64 bytes, or if the public key is not a valid point in one of the three serializations. A well-formed signature that simply does not verify is False, not an exception.

btclib_secp256k1.xonly module

X-only public keys and their tweaking.

According to BIP340-Schnorr and to the BIP341 taproot key path: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki

An x-only public key is the 32-byte x coordinate of the point with even y; the parity returned along a tweaked key is the one of the tweaked point, to be committed to by the taproot output.

A public key here is an x coordinate, and `02 || x`, `03 || x` and `04 || x || y` all name it. Every entry point taking a key takes any of the three, and none of them consults the y: lift_x is the even-y point whatever serialization the x arrived in, and a signer whose key is the odd-y one signs with n - d for exactly that reason. So the parity is a property of the serialization and not of the key, and there is no discarding for an argument check to make visible – from_pubkey returns it because a caller converting a key it holds may want to know which of the two forms it was handed, not because the two are different keys.

Which serialization to hand in is a question of cost and not of meaning: keys.parse reads the uncompressed form for 0.269 us, both coordinates being there, where the compressed form and the 32-byte one are a field square root at 2.326.

btclib_secp256k1.xonly.from_keypair(keypair_obj: Any) → tuple[bytes, int][source]

Return the x-only public key of a keypair, and the y parity.

The keypair already holds the point, so this is a read of it rather than a multiplication: ssa.Signer.pubkey is this call on the keypair a signer holds, and a MuSig2 session driven through lib holds another. from_prvkey is the same answer for a caller holding the private key and no keypair.

This is the one entry point taking a libsecp256k1 object that is not half of a parse/serialize pair; the package docstring says why.

Parameters:

keypair_obj – the libsecp256k1 keypair object, as ssa.Signer holds and as secp256k1_keypair_create writes.

Returns:

The 32-byte x coordinate, and the parity of y – 0 for even, 1 for odd. The parity is of the point the private key gives, which the keypair holds as given: the negation BIP340 prescribes is done when signing.

Raises:
  • ValueError – if the object is not a keypair libsecp256k1 will read – a NULL pointer, or one that has been wiped, which is reported as the zero it holds where the x of a point should be. That is a RuntimeError here rather than a ValueError, libsecp256k1 answering the same 0 for it as for a failure of its own, and context.check is what tells the two apart.

  • RuntimeError – if libsecp256k1 fails for any other reason, or fails to serialize the result, which a keypair it built cannot make it do.

btclib_secp256k1.xonly.from_prvkey(prvkey: bytes | bytearray | memoryview | int) → tuple[bytes, int][source]

Return the x-only public key of a private key, and the y parity.

The BIP340 and BIP341 form of keys.pubkey_from_prvkey, and the one to reach for when the 32 bytes are what is wanted: it is that call and from_pubkey with neither the serialization nor the parse between them, the point going straight from the multiplication into the conversion that drops its y.

Parameters:

prvkey – the private key, 32 bytes or an int below 2**256.

Returns:

The 32-byte x coordinate of kG, and the parity of its y – 0 for even, 1 for odd. That parity is what BIP340 signing negates the key for, so a signer wanting only the key it signs under can ignore it.

Raises:
  • ValueError – if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1 fails to convert or serialize the point, which no valid key can make it do.

Example

>>> from btclib_secp256k1 import keys, xonly
>>> pubkey = keys.pubkey_from_prvkey(1)
>>> xonly.from_prvkey(1) == xonly.from_pubkey(pubkey)
True
btclib_secp256k1.xonly.from_pubkey(pubkey_bytes: bytes | bytearray | memoryview) → tuple[bytes, int][source]

Convert a public key into its x-only form and y parity.

Parameters:

pubkey_bytes – the public key, 32, 33 or 65 bytes.

Returns:

The 32-byte x coordinate, and the parity of the y it was handed – 0 for even, 1 for odd, and 0 for a key that arrived x-only, an x naming the even-y point. The parity is answered rather than dropped because a caller may want to know which serialization it held, not because the two are different keys.

Raises:
  • ValueError – if the public key is not a valid point, or is not 32, 33 or 65 bytes.

  • RuntimeError – if libsecp256k1 fails to convert or serialize it, which no valid key can make it do.

btclib_secp256k1.xonly.parse(pubkey_bytes: bytes | bytearray | memoryview, name: str = 'public key') → Any[source]

Parse a public key, in any of its serializations, into its x.

The x-only counterpart of keys.parse, and the argument of ssa._verify_: a caller that has proved octets a public key has made the call BIP340 verification makes anyway, and can hand the result on rather than make it twice.

Every entry point of this module that takes a public key reaches it, which is what makes them all take any of the three serializations. Which one costs what: the 32-byte form is secp256k1_xonly_pubkey_parse, a field square root at 2.326 us, and so is the compressed form; the uncompressed form is 0.269, both coordinates being there to read, and the x-only conversion that follows it reads the y rather than lifting one.

Parameters:
  • pubkey_bytes – the public key, 32, 33 or 65 bytes.

  • name – what the key is, as the exception should call it, for a caller passing more than one kind of x-only key – silentpayments passes a taproot input and a transaction output, and which one was refused is the whole of what its caller needs.

Returns:

The libsecp256k1 x-only public key object.

Raises:

ValueError – if it is not a valid point, or is not 32, 33 or 65 bytes.

btclib_secp256k1.xonly.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int) → bytes[source]
btclib_secp256k1.xonly.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview) → None
btclib_secp256k1.xonly.prvkey_tweak_add(prvkey: bytes | bytearray | memoryview | int, tweak: bytes | bytearray | memoryview | int, *, into: bytearray | memoryview | None) → bytes | None

Add a tweak to the private key of an x-only public key.

The private key is first negated, if needed, to be the one of the even y point, so that the x-only public key of the result is the tweak_add of the x-only public key of the input: this is the private key to sign a taproot key path spending with.

Parameters:
  • prvkey – the internal private key, 32 bytes or an int below 2**256.

  • tweak – the tweak, 32 bytes or an int below 2**256.

  • into – a writable 32-byte buffer to receive the result, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte tweaked private key – or None where into was given and holds it.

Raises:
  • TypeError – if into is not a writable bytearray or memoryview of octets.

  • ValueError – if either value is not 32 bytes or does not fit in them, if the private key is not in [1, n-1], if the tweak or the resulting key is invalid, or if into is not 32 bytes.

  • RuntimeError – if libsecp256k1 fails to extract the key, which no valid input can make it do.

btclib_secp256k1.xonly.pubkey_verify(pubkey_bytes: bytes | bytearray | memoryview) → bool[source]

Return True if the octets are an x-only public key.

The x-only twin of keys.pubkey_verify, and the question a caller holding an x coordinate has: is there a point with this x? A caller with no xonly twin to reach for asks it of keys.pubkey_verify by writing 0x02 || x first, which is this call with a concatenation in front of it – and a compressed key built to be thrown away.

Parameters:

pubkey_bytes – the public key, 32, 33 or 65 bytes.

Returns:

True if the octets name a point of the curve; False for octets of any other length too, as keys.pubkey_verify answers for a key.

Raises:

TypeError – if the value is not bytes at all, which is a malformed argument and not a key to have a verdict on.

Example

>>> from btclib_secp256k1 import xonly
>>> pubkey, _ = xonly.from_prvkey(1)
>>> xonly.pubkey_verify(pubkey)
True
>>> xonly.pubkey_verify(bytes(32))
False
btclib_secp256k1.xonly.serialize(xonly_pubkey: Any) → bytes[source]

Return the 32 bytes of a parsed x-only public key.

What keys.serialize is to a full public key, and the other half of parse: there is no compression flag beside it, an x-only key having one serialization and it being the x.

Parameters:

xonly_pubkey – the libsecp256k1 x-only public key object, as parse returns.

Returns:

The 32-byte x coordinate.

Raises:
  • RuntimeError – if libsecp256k1 refuses the object – a NULL pointer, or a secp256k1_xonly_pubkey nothing has written to – or fails for any other reason, which a key it produced cannot make it do. context.check is what tells the two apart.

  • RuntimeError – if libsecp256k1 fails for any other reason, which a key it produced cannot make it do.

btclib_secp256k1.xonly.to_pubkey(pubkey_bytes: bytes | bytearray | memoryview, compressed: bool = True) → bytes[source]

Return the full public key an x-only one names, i.e. its even-y point.

The other direction of from_pubkey, and the lift a caller has no other call for: an x-only key is an x, the point it names is the one with even y, and reading that y is secp256k1_ec_pubkey_parse of 0x02 || x. libsecp256k1 has no call from an x-only object back to a point, so a caller wanting the y writes those 33 octets itself; this is that, with the concatenation where the rule about it is.

Parameters:
  • pubkey_bytes – the public key, 32, 33 or 65 bytes. A key that arrives with an odd y names the same x-only key its negation does, and this answers the even-y point of both.

  • compressed – whether to return 33 bytes rather than 65. The uncompressed form is the one that carries the y a caller asked this for.

Returns:

The serialized even-y point of that x.

Raises:
  • ValueError – if the key is not a valid point, or is not 32, 33 or 65 bytes.

  • RuntimeError – if libsecp256k1 fails to serialize it, which no valid key can make it do.

Example

>>> from btclib_secp256k1 import keys, xonly
>>> pubkey = keys.pubkey_from_prvkey(1)
>>> x, parity = xonly.from_pubkey(pubkey)
>>> parity
0
>>> xonly.to_pubkey(x) == pubkey
True
>>> xonly.to_pubkey(keys.pubkey_negate(pubkey)) == pubkey
True
btclib_secp256k1.xonly.tweak_add(pubkey_bytes: bytes | bytearray | memoryview, tweak: bytes | bytearray | memoryview | int) → tuple[bytes, int][source]

Add the generator multiplied by the tweak to an x-only public key.

This is the BIP341 taproot output key, given the internal key and the TapTweak hash.

Parameters:
  • pubkey_bytes – the internal key, 32, 33 or 65 bytes. The uncompressed form is the cheap one to hand in: see parse.

  • tweak – the tweak, 32 bytes or an int below 2**256.

Returns:

The 32-byte tweaked x-only key, and the parity of its y – that parity is what a taproot output commits to, and what tweak_add_check is given back.

Raises:
  • ValueError – if the key is not a valid point, or is not 32, 33 or 65 bytes, if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1 fails to convert or serialize the result, which no valid input can make it do.

btclib_secp256k1.xonly.tweak_add_check(tweaked_pubkey_bytes: bytes | bytearray | memoryview, tweaked_parity: int, pubkey_bytes: bytes | bytearray | memoryview, tweak: bytes | bytearray | memoryview | int) → bool[source]

Check that a tweaked x-only public key is the tweak of another one.

This is the verification of a taproot commitment: it is cheaper than recomputing the tweak, as it compares the serialized keys.

Parameters:
  • tweaked_pubkey_bytes – the 32-byte x-only key to check.

  • tweaked_parity – the parity of its y, 0 or 1, as tweak_add returned it.

  • pubkey_bytes – the internal key, 32, 33 or 65 bytes.

  • tweak – the tweak, 32 bytes or an int below 2**256.

Returns:

True if tweaking the internal key by that tweak gives that key and that parity. 32 bytes which are the x coordinate of no point at all are one of the ways of being False: this compares the serialization rather than parsing it, which is where the saving over recomputing the tweak comes from.

Raises:
  • TypeError – if the parity is not an int.

  • ValueError – if either key is not 32 bytes, if the internal key is not a valid x coordinate, if the parity is not 0 or 1, or if the tweak is not 32 bytes or does not fit in them. The tweaked key is not parsed, and so is never invalid: see Returns.

btclib_secp256k1.zkp package

BlockstreamResearch/secp256k1-zkp, under its own namespace, marked beta.

#283 decided the namespace rather than a docstring: every entry point this package draws from the fork rather than from mainline libsecp256k1 lives under zkp, so the import line itself says which library answered – mainline’s own musig (BIP327) and zkp’s (BIP327 plus the adaptor extensions) build incompatible session objects, and cannot be confused for one another once the caller has to write zkp.musig to reach the second. #603 is the plan this subpackage executes, and the criterion that promotes it into the published wheels; #606 is this subpackage itself, its loader and its own context; a wrapper module per secp256k1-zkp module wrapped sits beneath it, imported by the caller that reaches for one rather than by this module. __all__ here is the allowlist __getattr__ below checks a name against, and holds ffi and lib because those are the names that loader binds; it is not an index of what sits beneath.

beta is a fact about the fork this subpackage draws from, not about the wrapping: secp256k1-zkp cuts no tagged release, so the pin has nothing for a vendored-source review to anchor against the way mainline’s does. That belongs to the namespace once, in SECURITY.md, rather than to every entry point beneath it or to a warning every caller would have to filter – filterwarnings = [“error”] in this project’s own pyproject.toml is what such a warning would turn into a hard failure on the very first zkp import, here and downstream, for a status a namespace already states for free.

This subpackage always exists; the extension it wraps mostly does not. BTCLIB_LIBSECP256K1_ZKP=true is what scripts/cffi_build.py builds _btclib_secp256k1_zkp under, and no published wheel sets it (#603) – so import btclib_secp256k1.zkp always succeeds, and the first access to ffi or lib is what actually reaches for the extension: where the build has none, the caller reads how to get one in the ImportError this raises, chained from the bare “No module named” that a plain import _btclib_secp256k1_zkp would have left them with instead.

That first access is also the only place the extension is imported at all: ffi and lib are read the way __version__ is at the top of the package – a module-level __getattr__ (PEP 562) building and caching each on first use – so import btclib_secp256k1, which never reaches this subpackage on its own, keeps paying nothing for a second core it may never load, and import btclib_secp256k1.zkp on its own pays nothing either.

btclib_secp256k1.zkp.context module

zkp’s own libsecp256k1 context, and what it reports through it.

Mirrors btclib_secp256k1.context, one library over: two statically linked cores share nothing at run time, which is what makes this necessary rather than a convenience. context.py’s own docstring has the reasoning ctx and check repeat here – a shared context, randomized once at creation, and a thread-local record of what its illegal-argument callback last reported that check raises. A call made through zkp’s lib is explained by zkp’s check, never by the primary package’s: two libraries reporting into one thread-local would attribute one library’s message to a call the other library made.

Building either needs the extension btclib_secp256k1.zkp loads on first access to ffi or lib, so ctx is read the same lazy way here: a module-level __getattr__ builds it, and the context, on first access, rather than at import of this module. That keeps import btclib_secp256k1.zkp.context on its own safe without the flag – which is what lets docs/source/btclib_secp256k1.rst document this module’s own docstring, the documentation build never setting BTCLIB_LIBSECP256K1_ZKP.

_bindings below is the one place every module wrapping a zkp-only entry point (#607 onward) reaches for ffi, lib and ctx together: each such module defers the three to its own first call rather than to its own top level, for the reason this module’s __getattr__ already states for ctx alone, and _bindings is that deferral made once, here, instead of once per module. Called through context._bindings() rather than copied, so that a fifth wrapper module takes on no ordering requirement of its own – reading ctx first, as this module’s __getattr__ already does, is what makes ffi and lib real, and a caller of _bindings never reads either before that has happened.

_pubkey_parse and _array_or_null below are shared the same way, and can be: every wrapper module gets its ffi from _bindings, so the subpackage has one ffi, and a secp256k1_pubkey * parsed for one module is the type every other one takes.

Deferred to the first call is also what the primary package’s own context.py is not: that one builds at plain module scope, which Python’s own import lock serializes – every thread importing it blocks on the same lock, and one of them runs the module body while the rest wait for the result already sitting in sys.modules. Nothing here imports the extension at module scope, by decision, so nothing plays that part for _load below: two threads whose first touch of ctx overlaps both pass its “ctx” not in globals() check before either has written anything, and both build a context, README.md’s Thread safety section’s own guarantee – “it runs once before any thread exists” – holding only of the sequential path (#717). _lock is what _load takes instead, held for the whole build and rechecked once acquired: a thread that waited for it finds ctx already there and returns that one, never a second context built to be discarded.

btclib_secp256k1.zkp.ecdsa_adaptor module

ECDSA adaptor signatures.

This module wraps every entry point secp256k1_ecdsa_adaptor.h declares: encrypt, verify, decrypt and recover. An adaptor signature is an ECDSA signature that is not yet valid: it is encrypted under a public key Y, and whoever knows the secret key y of Y can decrypt it into an ordinary signature. Whoever then sees both the adaptor signature and the decrypted signature can recover y, which is what makes the construction useful for atomic swaps and DLCs.

WARNING. The adaptor signature leaks the Diffie-Hellman point of the signing key and the encryption key: given X = x*G, Y = y*G and the adaptor signature, x*Y = y*X is easy to compute. Do not use the signing key in protocols that rely on the hardness of the computational Diffie-Hellman problem, such as a key exchange or ElGamal encryption. The header says the same.

An adaptor signature is 162 bytes, R || R’ || s’ || e || s: the 33-byte compressed points R = k*Y and R’ = k*G, the scalar s’ = k^-1 (m + R.x * x), and a proof (e, s) that R and R’ have the same discrete logarithm k with respect to Y and G. verify checks the proof and that s’ is a signature on the message under R’. Public keys cross this boundary as SEC bytes and signatures as the 64 bytes of r || s, as in zkp.ecdsa_s2c.

The nonce function is fixed to the library’s default: the C entry point accepts another, and this module does not offer one. The default’s auxiliary randomness is encrypt’s aux_rand32.

This module reads context._bindings() inside each function rather than at its own top level, for the reason zkp.context’s docstring gives.

btclib_secp256k1.zkp.ecdsa_adaptor.decrypt(deckey: bytes | bytearray | memoryview | int, adaptor_sig162: bytes | bytearray | memoryview) → bytes[source]

Complete an adaptor signature into an ECDSA signature.

The adaptor signature is not verified here, and a signature decrypted from an unverified one may be invalid: call verify first. The result is the low-s form.

Parameters:
  • deckey – the decryption key, the private key of the encryption public key: 32 bytes or an int below 2**256.

  • adaptor_sig162 – the 162-byte adaptor signature.

Returns:

The signature, as the 64 bytes of r || s.

Raises:
  • TypeError – if an argument is not of a type its name allows.

  • ValueError – if the decryption key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if the adaptor signature is not 162 bytes, its s’ is not in [1, n-1], or its R.x is 0 modulo n.

  • RuntimeError – if libsecp256k1-zkp fails to serialize the signature, which no signature it produced can make it do.

btclib_secp256k1.zkp.ecdsa_adaptor.encrypt(msg32: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, enckey: bytes | bytearray | memoryview, *, aux_rand32: bytes | bytearray | memoryview | None = None, verify: bool = True) → bytes[source]

Create an adaptor signature, encrypted under a public key.

With verify, the result is checked by verify under the public key of prvkey before it is returned, as ecdsa_s2c.sign checks its own: what that catches is the computation going wrong.

Parameters:
  • msg32 – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • enckey – the encryption public key, SEC compressed or uncompressed.

  • aux_rand32 – 32 bytes of auxiliary randomness for the nonce, as BIP340 recommends, or None for none.

  • verify – whether to check the adaptor signature before returning it.

Returns:

The 162-byte adaptor signature.

Raises:
  • TypeError – if an argument is not of a type its name allows.

  • ValueError – if the message hash or aux_rand32 is not 32 bytes, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if the encryption key is not a valid point.

  • RuntimeError – if verify asks and the adaptor signature does not verify, which no input can make happen.

Example

>>> from btclib_secp256k1 import keys
>>> from btclib_secp256k1.zkp import ecdsa_adaptor
>>> enckey = keys.pubkey_from_prvkey(2)
>>> adaptor_sig = ecdsa_adaptor.encrypt(bytes(32), 1, enckey)
>>> ecdsa_adaptor.verify(
...     adaptor_sig, keys.pubkey_from_prvkey(1), bytes(32), enckey
... )
True
btclib_secp256k1.zkp.ecdsa_adaptor.recover(sig64: bytes | bytearray | memoryview, adaptor_sig162: bytes | bytearray | memoryview, enckey: bytes | bytearray | memoryview) → bytes[source]
btclib_secp256k1.zkp.ecdsa_adaptor.recover(sig64: bytes | bytearray | memoryview, adaptor_sig162: bytes | bytearray | memoryview, enckey: bytes | bytearray | memoryview, *, into: bytearray | memoryview) → None
btclib_secp256k1.zkp.ecdsa_adaptor.recover(sig64: bytes | bytearray | memoryview, adaptor_sig162: bytes | bytearray | memoryview, enckey: bytes | bytearray | memoryview, *, into: bytearray | memoryview | None) → bytes | None

Recover the decryption key from a signature and its adaptor signature.

Whoever completes an adaptor signature into sig64 has revealed the decryption key to anyone holding both. Neither is verified here beyond what recovery itself checks: that sig64 and the adaptor signature share r, and that the key they imply matches enckey.

Parameters:
  • sig64 – the 64-byte completed signature, r || s.

  • adaptor_sig162 – the 162-byte adaptor signature it was completed from.

  • enckey – the encryption public key, SEC compressed or uncompressed.

  • into – a writable 32-byte buffer to receive the key, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte decryption key – or None where into was given and holds it.

Raises:
  • TypeError – if an argument is not of a type its name allows, or if into is not a writable buffer of contiguous one-dimensional octets.

  • ValueError – if sig64 is not 64 bytes or is not a valid compact signature, if the adaptor signature is not 162 bytes or its s’ is not in [1, n-1], if the encryption key is not a valid point, if the signature and the adaptor signature do not match, or if into is not 32 bytes.

btclib_secp256k1.zkp.ecdsa_adaptor.verify(adaptor_sig162: bytes | bytearray | memoryview, pubkey: bytes | bytearray | memoryview, msg32: bytes | bytearray | memoryview, enckey: bytes | bytearray | memoryview) → bool[source]

Verify an adaptor signature.

Answers whether the decryption key can be extracted from the adaptor signature and the signature completed from it, which is what the adaptor signature’s proof and its signature on the message under the signer’s key establish together.

Parameters:
  • adaptor_sig162 – the 162-byte adaptor signature.

  • pubkey – the signer’s public key, SEC compressed or uncompressed.

  • msg32 – the 32-byte hash of the message.

  • enckey – the encryption public key, SEC compressed or uncompressed.

Returns:

True if the adaptor signature verifies, False if it does not, a malformed one included.

Raises:
  • TypeError – if an argument is not of a type its name allows.

  • ValueError – if the adaptor signature is not 162 bytes or the message hash is not 32, or if a public key is not a valid point.

btclib_secp256k1.zkp.ecdsa_s2c module

ECDSA sign-to-contract, and the anti-exfil protocol built on it.

This module wraps every entry point secp256k1_ecdsa_s2c.h declares, one for one. Sign-to-contract lets a signer commit to 32 bytes of data inside an ECDSA signature’s own nonce, by offsetting the nonce point with hash(R, data) * G; opening_parse and opening_serialize carry that commitment’s opening across a process boundary, sign is what makes it, and verify_commit is what checks it.

The anti-exfil half is the reason this module exists rather than being deferred: a hardware wallet could claim to produce RFC6979 nonces and instead bias them to leak the private key one signature at a time, with nothing on the host side able to tell the two apart, and anti_exfil_host_commit, anti_exfil_signer_commit, anti_exfil_sign and anti_exfil_host_verify are the commit-reveal protocol the header’s own module docstring lays out – host commits to randomness, signer’s public nonce commits to that commitment, host reveals the randomness, signer signs committing to it for real, host checks the signature’s nonce matches what was committed to at each step. Nothing here drives the protocol itself, which needs two round trips with a signing device this package has no notion of; anti_exfil_host_commit, anti_exfil_signer_commit, anti_exfil_sign and anti_exfil_host_verify are the calls the steps that need one are made of – the host’s own reveal, step 3, needs none from this module.

This module reads context._bindings() – btclib_secp256k1.zkp.context’s own ffi, lib and ctx, forced together – inside each function rather than at its own top level: zkp.context’s own docstring has the reasoning, and importing this module alone stays safe with no flagged build, which is what lets docs/source/btclib_secp256k1.rst document it.

Signatures cross this boundary in the 64-byte compact form only, r || s; a caller wanting DER has btclib_secp256k1.dsa.to_der, which is a serialization detail unrelated to the commitment this module is for. An opening is the 33-byte compressed form opening_serialize writes and opening_parse reads back; sign and anti_exfil_signer_commit answer it directly, there being no reason to make a caller round-trip through the parsed object for the common case.

btclib_secp256k1.zkp.ecdsa_s2c.anti_exfil_host_commit(rand32: bytes | bytearray | memoryview) → bytes[source]

Create the host’s commitment to its randomness.

The first step of the anti-exfil protocol: the host draws rand32 from a cryptographically secure RNG and sends this commitment to the signer, keeping rand32 itself back until step 3.

Parameters:

rand32 – the 32 bytes of randomness to commit to.

Returns:

The 32-byte commitment.

Raises:
  • ValueError – if rand32 is not 32 bytes.

  • RuntimeError – if libsecp256k1-zkp fails to compute the commitment, which valid arguments cannot make it do.

btclib_secp256k1.zkp.ecdsa_s2c.anti_exfil_host_verify(signature_bytes: bytes | bytearray | memoryview, msg_bytes: bytes | bytearray | memoryview, pubkey_bytes: bytes | bytearray | memoryview, host_data32: bytes | bytearray | memoryview, opening_bytes: bytes | bytearray | memoryview) → bool[source]

Verify a signature made under the anti-exfil protocol. Step 5.

True only where both hold: the signature verifies under pubkey_bytes for msg_bytes, and it commits to host_data32 under opening_bytes – the opening the signer answered at step 2, for the same randomness the host revealed at step 3. Together they say the signer could not have biased this signature’s nonce without the host’s own randomness changing it, which is what the whole protocol is for.

Parameters:
  • signature_bytes – the signature the signer produced, the 64 bytes of r || s.

  • msg_bytes – the 32-byte hash of the message.

  • pubkey_bytes – the signer’s public key, 33 or 65 bytes.

  • host_data32 – the randomness the host revealed at step 3.

  • opening_bytes – the opening from anti_exfil_signer_commit, step 2.

Returns:

True if the signature is valid for that key and message, and commits to host_data32 under opening_bytes.

Raises:

ValueError – if the signature is not 64 bytes or is not a valid compact signature, if the message hash or the data are not 32 bytes, if the public key is not a valid point, or if the opening is not 33 bytes or does not encode a point on the curve.

btclib_secp256k1.zkp.ecdsa_s2c.anti_exfil_sign(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, host_data32: bytes | bytearray | memoryview, *, verify: bool = True) → bytes[source]

Sign, committing to the host’s randomness. Step 4 of the protocol.

The same call as sign, with host_data32 – the randomness the host revealed at step 3 – as the data committed to, and no opening answered: the host already holds it, from anti_exfil_signer_commit at step 2.

verify is sign’s, minus the commitment check: this call answers no opening for secp256k1_ecdsa_s2c_verify_commit to check against, and the host’s anti_exfil_host_verify at step 5 is where the signature and the commitment from step 2 are checked together.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • host_data32 – the randomness the host revealed.

  • verify – whether to check the signature under the key’s own public key before returning it.

Returns:

The signature, as the 64 bytes of r || s.

Raises:
  • ValueError – if the message hash or the data are not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1-zkp fails to serialize the signature, which no signature it produced can make it do, or if verify asks and the signature does not verify.

btclib_secp256k1.zkp.ecdsa_s2c.anti_exfil_signer_commit(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, rand_commitment32: bytes | bytearray | memoryview) → bytes[source]

Compute the signer’s original public nonce, step 2 of the protocol.

The signer answers this opening to the host without yet signing: anti_exfil_sign is step 4, once the host has revealed the randomness this commits to.

Parameters:
  • msg_bytes – the 32-byte hash of the message to be signed.

  • prvkey – the private key that will sign, 32 bytes or an int below 2**256.

  • rand_commitment32 – the host’s commitment, from anti_exfil_host_commit.

Returns:

The signer’s public nonce, as the 33-byte compressed serialization of its opening.

Raises:
  • ValueError – if the message hash or the commitment are not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1-zkp fails to compute the opening, which valid arguments cannot make it do.

btclib_secp256k1.zkp.ecdsa_s2c.opening_parse(opening_bytes: bytes | bytearray | memoryview) → Any[source]

Parse a serialized sign-to-contract opening.

Parameters:

opening_bytes – the opening’s 33-byte compressed serialization, as opening_serialize returns and sign and anti_exfil_signer_commit already answer.

Returns:

The libsecp256k1-zkp opening object, as verify_commit and anti_exfil_host_verify take it – through their own opening_bytes argument, which parses it again; this is for a caller working with the object directly.

Raises:

ValueError – if the bytes are not 33 long, or do not encode a point on the curve.

btclib_secp256k1.zkp.ecdsa_s2c.opening_serialize(opening: Any) → bytes[source]

Serialize a sign-to-contract opening to its 33-byte compressed form.

Parameters:

opening – the libsecp256k1-zkp opening object, as opening_parse returns.

Returns:

Its 33-byte compressed serialization.

Raises:

RuntimeError – if libsecp256k1-zkp refuses the object – one it cannot read – or fails for any other reason, which an opening opening_parse produced cannot make it do.

btclib_secp256k1.zkp.ecdsa_s2c.sign(msg_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int, s2c_data32: bytes | bytearray | memoryview, *, verify: bool = True) → tuple[bytes, bytes][source]

Create an ECDSA signature committing to 32 bytes of data.

The nonce function is fixed to the RFC6979 default: sign-to-contract only works with it, s2c_data32 being mixed into the very nonce it derives, and the C entry point itself takes no nonce-function argument at all, so no other one can be supplied.

verify is dsa.sign’s, and dsa.sign’s docstring has the reason it is on by default: the signature is checked under the key’s own public key, and the commitment with secp256k1_ecdsa_s2c_verify_commit against s2c_data32, before either is returned.

Parameters:
  • msg_bytes – the 32-byte hash of the message.

  • prvkey – the private key, 32 bytes or an int below 2**256.

  • s2c_data32 – the 32 bytes of data to commit to.

  • verify – whether to check the signature and the commitment before returning them.

Returns:

The signature, as the 64 bytes of r || s, and the opening of its commitment, as the 33-byte compressed serialization verify_commit and opening_parse take.

Raises:
  • ValueError – if the message hash or the data are not 32 bytes, or if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1].

  • RuntimeError – if libsecp256k1-zkp fails to serialize the signature or the opening, which neither it produced can make it do, or if verify asks and the signature or its commitment does not verify.

Example

>>> from btclib_secp256k1.zkp import ecdsa_s2c
>>> signature, opening = ecdsa_s2c.sign(bytes(32), 1, bytes(32))
>>> ecdsa_s2c.verify_commit(signature, bytes(32), opening)
True
btclib_secp256k1.zkp.ecdsa_s2c.verify_commit(signature_bytes: bytes | bytearray | memoryview, data32: bytes | bytearray | memoryview, opening_bytes: bytes | bytearray | memoryview) → bool[source]

Verify a sign-to-contract commitment.

Answers whether the signature’s nonce commits to data32 under the given opening – a property of the signature’s own r, checked without verifying the signature itself. anti_exfil_host_verify checks both together.

Parameters:
  • signature_bytes – the signature containing the commitment, the 64 bytes of r || s.

  • data32 – the 32 bytes of data the opening should commit to.

  • opening_bytes – the opening sign or anti_exfil_signer_commit answered, as its 33-byte compressed serialization.

Returns:

True if the signature’s nonce commits to data32 under this opening – and False for an incorrect opening, which is not necessarily a signature that fails to verify: verify_commit answers a property of r alone.

Raises:

ValueError – if the signature is not 64 bytes or is not a valid compact signature, if the data are not 32 bytes, or if the opening is not 33 bytes or does not encode a point on the curve.

btclib_secp256k1.zkp.generator module

secp256k1_generator.h: generators, and Pedersen commitments.

A generator is a second base point, unrelated to G by any known discrete logarithm, that a Pedersen commitment x*G + v*gen blinds a value v under. h() is the static one the header itself fixes and generate/generate_blinded are the ways to make another; parse and serialize are the octet boundary for either, and pedersen_commitment_parse/pedersen_commitment_serialize are the same boundary for the 33-byte commitment pedersen_commit builds.

ISS 608’s own body names a downstream consumer already pinned against the x-coordinate of secp256k1_generator_h, independently of this library: h() is what turns that pin into an assertion against the library itself rather than against a copied literal.

Every function here needs BTCLIB_LIBSECP256K1_ZKP’s extension, and none of them reaches for it at import time: context._bindings() is where each defers to it, on the first call rather than on import btclib_secp256k1.zkp.generator, the same reason btclib_secp256k1.zkp.context’s own __getattr__ docstring gives for deferring its own ctx – so that importing that module never reaches for the extension on its own.

pedersen_blind_sum and pedersen_blind_generator_blind_sum answer a blinding factor through btclib_secp256k1._secret.take, the same function the primary package’s own wrappers use, rather than through a local equivalent: take overwrites through ffi.buffer(buffer) and asks that buffer for its own length rather than typing it, so it does not care which ffi built what it is reading, the same trade zkp.musig’s own module docstring makes for _secret.wipe.

btclib_secp256k1.zkp.generator.generate(seed32: bytes | bytearray | memoryview) → bytes[source]

Generate a generator, distributed uniformly over the curve.

The result has no known discrete logarithm with respect to any other generator this produces, or to the base generator G.

Parameters:

seed32 – 32 bytes seeding the generator.

Returns:

Its 33-byte serialization.

Raises:
  • ValueError – if seed32 is not 32 bytes.

  • RuntimeError – if libsecp256k1-zkp refuses the seed, which is “highly unlikely” in the header’s own words and cannot happen for a seed this package generated.

btclib_secp256k1.zkp.generator.generate_blinded(seed32: bytes | bytearray | memoryview, blind32: bytes | bytearray | memoryview | int) → bytes[source]

Generate a generator, blinded by a secret tweak.

Equivalent to generate followed by a plain EC tweak of the result read as a public key, and back – computed directly instead, in one call.

Parameters:
  • seed32 – 32 bytes seeding the generator.

  • blind32 – the secret value to blind it with, 32 bytes or an int below 2**256.

Returns:

Its 33-byte serialization.

Raises:

ValueError – if seed32 is not 32 bytes, if blind32 is not 32 bytes or does not fit in them, if blind32 is out of range, or if libsecp256k1-zkp refuses the seed – “highly unlikely” in the header’s own words and cannot happen for a seed this package generated. The two failures share one return code and are not told apart.

btclib_secp256k1.zkp.generator.h() → bytes[source]

Return the static generator the header calls h, serialized.

“Static constant generator ‘h’ maintained for historical reasons”, in the header’s own words: secp256k1_generator_h, read once rather than parsed from a literal, is what lets a caller pin their own copy of it against this library instead of against a copy of the bytes.

Returns:

Its 33 bytes.

btclib_secp256k1.zkp.generator.parse(generator_bytes: bytes | bytearray | memoryview, name: str = 'generator') → Any[source]

Parse a 33-byte generator into its internal representation.

Parameters:
  • generator_bytes – the generator, 33 bytes, as serialize, generate, generate_blinded or h answered it.

  • name – what the generator is, as the exception should call it.

Returns:

The libsecp256k1-zkp generator object.

Raises:

ValueError – if it is not 33 bytes, or not a valid generator.

btclib_secp256k1.zkp.generator.pedersen_blind_generator_blind_sum(values: Sequence[int], generator_blinds: Sequence[bytes | bytearray | memoryview | int], blinding_factors: Sequence[bytes | bytearray | memoryview | int], n_inputs: int) → bytes[source]
btclib_secp256k1.zkp.generator.pedersen_blind_generator_blind_sum(values: Sequence[int], generator_blinds: Sequence[bytes | bytearray | memoryview | int], blinding_factors: Sequence[bytes | bytearray | memoryview | int], n_inputs: int, *, into: bytearray | memoryview) → None
btclib_secp256k1.zkp.generator.pedersen_blind_generator_blind_sum(values: Sequence[int], generator_blinds: Sequence[bytes | bytearray | memoryview | int], blinding_factors: Sequence[bytes | bytearray | memoryview | int], n_inputs: int, *, into: bytearray | memoryview | None) → bytes | None

Correct the last blinding factor so every sum cancels.

For blinded generators A’ = A + r*G, a Pedersen commitment P = v*A’ + r’*G really has the form v*A + (v*r + r’)*G: this subtracts the sum of v*r + r’ over every element but the last from the last element of blinding_factors, so that the total sum of (v*r + r’) over all of them is zero.

Parameters:
  • values – the asset values, one per commitment, an int in [0, 2**64) each.

  • generator_blinds – the asset (generator) blinding factors, r in the equation above, one per commitment, 32 bytes or an int below 2**256 each.

  • blinding_factors – the commitment blinding factors, r’ in the equation above, one per commitment, 32 bytes or an int below 2**256 each – every one but the last is used as given, and only the last is what this recomputes.

  • n_inputs – how many of the initial elements, across all three sequences, are negated in the final sum. Strictly less than len(values): the library’s own ARG_CHECK(n_total > n_inputs) refuses n_inputs == len(values), the 0, 0 case – empty sequences included – among them.

  • into – a writable 32-byte buffer to receive the corrected blinding factor, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The corrected last blinding factor, 32 bytes – the caller’s own blinding_factors with its last element replaced by this is the array libsecp256k1-zkp’s own in/out semantics describe – or None where into was given and holds it.

Raises:
  • TypeError – if generator_blinds or blinding_factors is a str, a cffi array of primitives or a one-dimensional buffer of anything but byte-strings, objects or structs (bytes, bytearray, memoryview, array.array): one blinding factor, not a list of them. Also if n_inputs is not an int, if one of values is not an int, or if into is not a writable buffer of contiguous one-dimensional octets.

  • ValueError – if the three sequences are not the same length, if n_inputs is not in [0, len(values)), if one of values is a bool or does not fit in 8 bytes, if one of the blinding factors is not 32 bytes, does not fit in them, or is out of range, or if into is not 32 bytes. A bad element is named by its position in its own sequence, values[0] being “value at index 0”.

  • RuntimeError – if libsecp256k1-zkp refuses one of the factors, which is a ~2**-127 event for random ones.

btclib_secp256k1.zkp.generator.pedersen_blind_sum(blinds: Sequence[bytes | bytearray | memoryview | int], npositive: int) → bytes[source]
btclib_secp256k1.zkp.generator.pedersen_blind_sum(blinds: Sequence[bytes | bytearray | memoryview | int], npositive: int, *, into: bytearray | memoryview) → None
btclib_secp256k1.zkp.generator.pedersen_blind_sum(blinds: Sequence[bytes | bytearray | memoryview | int], npositive: int, *, into: bytearray | memoryview | None) → bytes | None

Sum blinding factors, first npositive positive, the rest negative.

Parameters:
  • blinds – the blinding factors, 32 bytes or an int below 2**256 each.

  • npositive – how many of blinds, from the start, are summed with a positive sign; the rest are summed with a negative one.

  • into – a writable 32-byte buffer to receive the sum, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte sum – or None where into was given and holds it.

Raises:
  • TypeError – if blinds is a str, a cffi array of primitives or a one-dimensional buffer of anything but byte-strings, objects or structs (bytes, bytearray, memoryview, array.array): one blinding factor, not a list of them. Also if npositive is not an int, or if into is not a writable buffer of contiguous one-dimensional octets.

  • ValueError – if one of blinds is not 32 bytes, does not fit in them, or is out of range – named by its position in the sequence, blinds[0] being “blinding factor at index 0” – if npositive is out of [0, len(blinds)], or if into is not 32 bytes.

  • RuntimeError – if libsecp256k1-zkp refuses one of the factors, which is a ~2**-127 event for a random one.

btclib_secp256k1.zkp.generator.pedersen_commit(blind: bytes | bytearray | memoryview | int, value: int, gen_bytes: bytes | bytearray | memoryview | None = None) → bytes[source]

Commit to a value, blind*G + value*gen.

Parameters:
  • blind – the blinding factor, 32 bytes or an int below 2**256. Generated and verified the same way as an ordinary libsecp256k1 private key.

  • value – the value to commit to, an int in [0, 2**64).

  • gen_bytes – the generator gen, 33 bytes, or None for h() – the plain, unblinded Pedersen commitment most callers want.

Returns:

The 33-byte serialized commitment.

Raises:
  • TypeError – if value is not an int.

  • ValueError – if blind is not 32 bytes, does not fit in them, or is out of range; if value is a bool or does not fit in 8 bytes; or if gen_bytes is given and is not 33 bytes or not a valid generator.

  • RuntimeError – if libsecp256k1-zkp refuses the blinding factor, which is a ~2**-127 event for a random one and cannot happen for a factor already verified.

btclib_secp256k1.zkp.generator.pedersen_commitment_parse(commitment_bytes: bytes | bytearray | memoryview, name: str = 'Pedersen commitment') → Any[source]

Parse a 33-byte Pedersen commitment into its internal representation.

Parameters:
  • commitment_bytes – the commitment, 33 bytes, as pedersen_commitment_serialize or pedersen_commit answered it.

  • name – what the commitment is, as the exception should call it.

Returns:

The libsecp256k1-zkp Pedersen commitment object.

Raises:

ValueError – if it is not 33 bytes, or not a valid commitment.

btclib_secp256k1.zkp.generator.pedersen_commitment_serialize(commit: Any) → bytes[source]

Serialize a parsed Pedersen commitment.

Parameters:

commit – the libsecp256k1-zkp commitment object, as pedersen_commitment_parse returns.

Returns:

Its 33 bytes.

btclib_secp256k1.zkp.generator.pedersen_verify_tally(commits_bytes: Sequence[bytes | bytearray | memoryview], ncommits_bytes: Sequence[bytes | bytearray | memoryview]) → bool[source]

Verify commits sums to ncommits, factors and values alike.

sum(commits) - sum(ncommits) == 0, for the generator each was committed under: every commitment mixing more than one generator must sum to zero for each of them separately, which is the caller’s to arrange by grouping commitments per generator before calling this.

Parameters:
  • commits_bytes – the positive commitments, 33 bytes each.

  • ncommits_bytes – the negative commitments, 33 bytes each.

Returns:

True if they sum to zero.

Raises:

ValueError – if one of the commitments is not 33 bytes or not a valid one – named by its position in whichever sequence it came from.

btclib_secp256k1.zkp.generator.serialize(gen: Any) → bytes[source]

Serialize a parsed generator.

Parameters:

gen – the libsecp256k1-zkp generator object, as parse returns.

Returns:

Its 33 bytes.

btclib_secp256k1.zkp.musig module

zkp’s MuSig2: BIP327 plus adaptor signatures, the whole header wrapped.

btclib_secp256k1.musig wraps every entry point of mainline libsecp256k1’s secp256k1_musig.h. This wraps secp256k1-zkp’s own header instead, which adds nonce_parity, adapt and extract_adaptor beyond mainline’s, and gives nonce_process itself a sixth argument, an optional adaptor point. #283’s structural finding is why this is a second module rather than a few extra functions bolted onto the first: zkp’s nonce_process cannot be called with mainline’s five-argument signature, adapt needs the nonce_parity a session built by this nonce_process reports, and secp256k1_musig_session, secp256k1_musig_keyagg_cache and secp256k1_musig_secnonce are opaque structs of a different build from mainline’s – so a caller starting a session on btclib_secp256k1.musig has nothing to adapt, and there is no shared session to hand between the two modules. KeyAggCache, SecretNonce and Session below are that module’s own classes, shaped the same way and for the reasons its docstring gives – a keyagg cache and a session hold nothing secret and have no serialization, so they are held rather than round-tripped through octets, and a secret nonce is wiped on the way out of partial_sign and by the with block that holds a SecretNonce.

Nothing here is `btclib_secp256k1.musig`’s code reused. Two statically linked cores share no cffi type identity: a secp256k1_pubkey context._pubkey_parse builds and one btclib_secp256k1.keys.parse builds are both named secp256k1_pubkey * and are not interchangeable – passing one to a call compiled against the other’s ffi raises TypeError: … the types are different (check that you are not e.g. mixing up different ffi instances), measured directly before this module was written this way. So every struct this needs – a public key, an x-only public key, a keypair, in addition to the five secp256k1_musig_* structs the header already declares – is parsed and serialized locally, through zkp’s own ffi and lib, rather than through btclib_secp256k1.keys or btclib_secp256k1.xonly. What is shared, and safely, is _scalar.py’s octets, scalar and in_range: they take and return plain bytes and ints, or a bare unsigned char[32]-shaped cdata view that cffi treats structurally rather than nominally, measured to cross the same boundary without complaint – and _secret.wipe and _secret.take, both of which overwrite through ffi.buffer(buffer) and ask that buffer for its own length rather than typing it, so neither cares which ffi built what it is reading or wiping. _secret.keypair is not shared for the same reason keys.parse is not: it returns a secp256k1_keypair * built through mainline’s own lib.secp256k1_keypair_create, and this module’s _keypair is the local equivalent, over zkp’s own ctx and lib.

Every entry point here defers `ffi`, `lib` and `ctx` to its own call, through `context._bindings()`, rather than importing any of the three at module scope. btclib_secp256k1.musig imports its ffi and lib eagerly and may, because mainline’s extension is never flag-gated; this module’s is, and docs/source/btclib_secp256k1.rst documents every class and function below with :members: in a build that never sets BTCLIB_LIBSECP256K1_ZKP – so importing this module, which Sphinx’s autodoc does to read the docstrings and signatures, must not itself reach for the extension. btclib_secp256k1.zkp.context’s own __getattr__ already makes this trade for ctx alone; _bindings is the same trade for ffi and lib too, shared by every module wrapping a zkp-only entry point so that none of them repeats the two lines that make it.

Validation: BIP327’s own vectors – tests/bip327_*_vectors.json, the files tests/vectors_test.py already reads for btclib_secp256k1.musig – run unchanged against this module in tests/zkp_musig_vectors_test.py, which is the API-compatibility check BlockstreamResearch/secp256k1-zkp#330 was opened to make possible and this package can run without waiting for that split. The adaptor path has no published vector – zkp’s own tests draw the adaptor secret with testrand256, and a downstream caller checked for one to lift and found none – so it is checked by round trip instead, in tests/zkp_musig_test.py: pre-sign, adapt with a known secret, extract the secret back out of the two signatures.

class btclib_secp256k1.zkp.musig.KeyAggCache(pubkeys_bytes: Sequence[bytes | bytearray | memoryview])[source]

Bases: object

The aggregate of a set of signers’ public keys, and its tweaks.

The zkp-native btclib_secp256k1.musig.KeyAggCache: what secp256k1_musig_keyagg_cache holds, and why it is held rather than round-tripped through octets, is that class’s own docstring, which applies unchanged. What differs is only that every call below goes through zkp’s own ffi, lib and ctx – the module docstring explains why the two cannot share a cache object.

Building one is secp256k1_musig_pubkey_agg, which orders the keys exactly as given: keys.pubkey_sort first is BIP327’s own rule for reaching one aggregate key independently of the order the signers’ public keys arrived in, and is the caller’s to apply – that function is a pure re-serialization after a real sort, so btclib_secp256k1.keys.pubkey_sort is safe to call for this module too, unlike parse and serialize.

Parameters:

pubkeys_bytes – the signers’ public keys, 33 or 65 bytes each, in aggregation order. At least one is required.

Raises:
  • ValueError – if the sequence is empty, or if one of the keys is not a valid point – named by its position in the sequence, pubkeys_bytes[0] being “public key at index 0”.

  • RuntimeError – if libsecp256k1-zkp fails to aggregate or serialize the result, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> from btclib_secp256k1.zkp import musig
>>> pubkeys = [keys.pubkey_from_prvkey(1), keys.pubkey_from_prvkey(2)]
>>> cache = musig.KeyAggCache(pubkeys)
>>> len(cache.agg_pubkey)
32
pubkey_ec_tweak_add(tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add a plain EC tweak to the aggregate key, e.g. a BIP32 step.

The MuSig2 counterpart of keys.pubkey_tweak_add: the tweaking method is the same, and what differs is that it is applied to this cache’s running aggregate rather than to an argument, so that signing for the tweaked key is SecretNonce.partial_sign against the cache afterwards rather than against a key nobody holds the private share of directly.

Parameters:
  • tweak – the tweak, 32 bytes or an int below 2**256. Callers are responsible for deriving it in a way that does not reduce MuSig2’s security, BIP32 being such a way.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized tweaked aggregate public key.

Raises:
  • ValueError – if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1-zkp fails to serialize the result, which no valid input can make it do.

pubkey_get(compressed: bool = True) → bytes[source]

Return the current aggregate public key, tweaks included.

Useful for the non-xonly form of the aggregate key – plain tweaking, or batch-verifying more than one key aggregation, which libsecp256k1-zkp does not implement – where agg_pubkey is the x-only one and BIP341 taproot tweaking wants the full point.

Parameters:

compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized aggregate public key, as it stands after every pubkey_ec_tweak_add and pubkey_xonly_tweak_add made so far.

Raises:

RuntimeError – if libsecp256k1-zkp fails to read or serialize it, which a cache this built cannot make it do.

pubkey_xonly_tweak_add(tweak: bytes | bytearray | memoryview | int, compressed: bool = True) → bytes[source]

Add a BIP341 x-only tweak to the aggregate key, for a taproot output.

xonly.tweak_add’s equation, applied to this cache instead of to a bare argument – the module docstring’s reasoning about xonly not being callable from here does not reach this: what is asked of the caller is the same BIP341 equation, not a call into btclib_secp256k1.xonly. This is required, rather than tweaking the x-only key directly, wherever a signature is wanted for the tweaked key: that would leave this cache untweaked, so a later SecretNonce.partial_sign would sign for the untweaked aggregate instead.

Parameters:
  • tweak – the tweak, 32 bytes or an int below 2**256. Callers are responsible for deriving it in a way that does not reduce MuSig2’s security, BIP341 being such a way.

  • compressed – whether to return 33 bytes rather than 65.

Returns:

The serialized tweaked aggregate public key.

Raises:
  • ValueError – if the tweak is not 32 bytes or does not fit in them, or if the tweak or the resulting key is invalid.

  • RuntimeError – if libsecp256k1-zkp fails to serialize the result, which no valid input can make it do.

class btclib_secp256k1.zkp.musig.SecretNonce(secnonce: Any, pubnonce: Any, pubkey: Any)[source]

Bases: object

A signer’s secret nonce, held between nonce_gen and partial_sign.

The zkp-native btclib_secp256k1.musig.SecretNonce: that class’s own docstring has the reasoning – the lock guarding _take against two threads racing partial_sign on one object, wipe’s cases, and why a SecretNonce told neither wipe nor entered as a with block is dropped holding the secret. All of it applies unchanged; only the ctx, ffi and lib this reaches for, through context._bindings(), differ.

Parameters:
  • secnonce – the libsecp256k1-zkp secret nonce object nonce_gen or nonce_gen_counter built.

  • pubnonce – the matching public nonce object, already parsed.

  • pubkey – the signer’s already-parsed public key, which partial_sign compares with the key it is given and keeps for its own verify.

Example

>>> from btclib_secp256k1 import keys
>>> from btclib_secp256k1.zkp import musig
>>> prvkey, pubkey = 1, keys.pubkey_from_prvkey(1)
>>> with musig.nonce_gen(pubkey, prvkey) as secnonce:
...     len(secnonce.pubnonce)
66
partial_sign(prvkey: bytes | bytearray | memoryview | int, keyagg_cache: KeyAggCache, session: Session, *, verify: bool = True) → bytes[source]

Produce this signer’s partial signature, and wipe the secret nonce.

Whatever the outcome – a signature, a refusal or an exception – the secret nonce this holds does not survive the call.

Parameters:
  • prvkey – this signer’s private key, 32 bytes or an int below 2**256, matching the public key nonce_gen or nonce_gen_counter built this nonce for.

  • keyagg_cache – the key aggregation this session signs under, the same one the nonce was generated against if one was.

  • session – the session Session.__init__ built from the aggregate nonce this signer’s public nonce contributed to.

  • verify – whether to check the partial signature against this signer’s public nonce and public key before returning it, as btclib_secp256k1.musig’s own module docstring explains and BIP327’s own header recommends.

Returns:

The 32-byte partial signature.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache, or session is not a Session.

  • ValueError – if this secret nonce has already been spent or wiped, if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], or if it does not match this secret nonce, or keyagg_cache or session do not.

  • RuntimeError – if libsecp256k1-zkp fails to extract the public key of the private key given, which a private key already verified cannot make it do, or if verify asks and the partial signature does not verify, which no input reaching this far can make happen.

wipe() → None[source]

Overwrite the secret nonce, ending what this can sign.

Signing afterwards raises rather than signing with the zeros left behind, and there is no reviving it: nothing here keeps the nonce anywhere else, so a session abandoned this way is restarted with a fresh nonce_gen rather than resumed. Takes the same lock partial_sign does, so a wipe racing a partial_sign call on another thread cannot see the buffer between the two.

class btclib_secp256k1.zkp.musig.Session(aggnonce_bytes: bytes | bytearray | memoryview, msg32: bytes | bytearray | memoryview, keyagg_cache: KeyAggCache, adaptor_bytes: bytes | bytearray | memoryview | None = None)[source]

Bases: object

A MuSig2 signing session, opened from the signers’ aggregate nonce.

What secp256k1_musig_nonce_process produces, over zkp’s own ctx, ffi and lib: the message, the aggregate nonce and the key aggregation are fixed into it, as btclib_secp256k1.musig.Session’s own docstring explains for the 17-argument call it wraps. This wraps the 18-argument one instead – zkp’s own nonce_process takes a sixth argument, an optional adaptor point – so the session this builds is a pre-signature session where adaptor_bytes is given, and an ordinary one where it is not; nonce_parity, adapt and extract_adaptor are what tell the two apart afterwards.

Parameters:
  • aggnonce_bytes – the aggregate of the signers’ public nonces, 66 bytes, as nonce_agg answered it.

  • msg32 – the 32-byte message this session signs.

  • keyagg_cache – the key aggregation this session signs under.

  • adaptor_bytes – an adaptor point, encoded as a public key (33 or 65 bytes), if this session is part of an adaptor signature protocol. Session.partial_sig_agg then answers a pre-signature rather than a valid one, and adapt is what turns it into one, given the secret this point commits to.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if aggnonce_bytes is not 66 bytes or not one BIP327 defines, if the message is not 32 bytes, or if adaptor_bytes is given and is not a valid point.

  • RuntimeError – if libsecp256k1-zkp fails to build the session, which no valid input can make it do.

Example

>>> from btclib_secp256k1 import keys
>>> from btclib_secp256k1.zkp import musig
>>> pubkeys = [keys.pubkey_from_prvkey(1), keys.pubkey_from_prvkey(2)]
>>> cache = musig.KeyAggCache(pubkeys)
>>> secnonces = [
...     musig.nonce_gen(pk, i + 1) for i, pk in enumerate(pubkeys)
... ]
>>> pubnonces = [s.pubnonce for s in secnonces]
>>> aggnonce = musig.nonce_agg(pubnonces)
>>> session = musig.Session(aggnonce, bytes(32), cache)
nonce_parity() → int[source]

Return this session’s aggregate nonce parity, for the adaptor calls.

adapt and extract_adaptor both need it, and neither takes this session as an argument to read it from directly – the header’s own secp256k1_musig_adapt and secp256k1_musig_extract_adaptor do not, so this is the one call that connects a session back to either of them.

Returns:

0 or 1.

Raises:

RuntimeError – if libsecp256k1-zkp fails to extract it, which a session this built cannot make it do.

partial_sig_agg(partial_sigs_bytes: Sequence[bytes | bytearray | memoryview]) → bytes[source]

Aggregate the signers’ partial signatures into the final one.

The result is a plain BIP340 signature over the aggregate (possibly tweaked) key, unless this session was built with an adaptor point, in which case it is a pre-signature: adapt is what turns it into a valid one, given the secret the adaptor point commits to. Either way this answers 1 “which does NOT mean the resulting signature verifies”, in the header’s own words – ssa.verify, or adapt and then ssa.verify, is how a caller checks it.

Parameters:

partial_sigs_bytes – the signers’ partial signatures, 32 bytes each, one per signer of this session. At least one is required.

Returns:

The 64-byte aggregate signature, or pre-signature.

Raises:
  • ValueError – if the sequence is empty, or if one of the partial signatures is not 32 bytes or not one below the curve order – named by its position in the sequence, partial_sigs_bytes[0] being “partial signature at index 0”.

  • RuntimeError – if libsecp256k1-zkp fails to aggregate the result, which no valid input can make it do.

partial_sig_verify(partial_sig_bytes: bytes | bytearray | memoryview, pubnonce_bytes: bytes | bytearray | memoryview, pubkey_bytes: bytes | bytearray | memoryview, keyagg_cache: KeyAggCache) → bool[source]

Verify one signer’s partial signature, within this session.

Not required in a regular session: an aggregate signature that fails ssa.verify (or, for a pre-signature, adapt followed by it) was made from at least one bad partial signature, but does not say which. This is the call that finds out, at the cost of one verification per signer instead of one for the aggregate.

Parameters:
  • partial_sig_bytes – the partial signature to verify, 32 bytes.

  • pubnonce_bytes – the public nonce of the signer it is attributed to, 66 bytes, exactly as it went into nonce_agg.

  • pubkey_bytes – the public key of that signer, 33 or 65 bytes, exactly as it went into the KeyAggCache this session’s key aggregation came from.

  • keyagg_cache – that key aggregation.

Returns:

True if the partial signature is valid for that signer’s nonce and key, within this session.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if any of the three byte arguments is not the right length or not one libsecp256k1-zkp will read.

btclib_secp256k1.zkp.musig.adapt(pre_sig64: bytes | bytearray | memoryview, sec_adaptor: bytes | bytearray | memoryview | int, nonce_parity: int) → bytes[source]

Turn a pre-signature into a valid signature, given the adaptor secret.

The other half of the adaptor signature protocol from the secret holder’s side: Session.partial_sig_agg, over a session built with an adaptor point, answers the pre-signature this takes. extract_adaptor is the inverse, run by whoever holds both the pre-signature and the completed signature instead of the secret.

Parameters:
  • pre_sig64 – the 64-byte pre-signature, from Session.partial_sig_agg.

  • sec_adaptor – the 32-byte secret adaptor, or an int below 2**256, that the adaptor point given to Session.__init__ commits to.

  • nonce_parity – the output of Session.nonce_parity, called on the session that produced pre_sig64.

Returns:

The 64-byte signature. An incorrect sec_adaptor makes it invalid rather than makes this call fail – ssa.verify is what a caller checks it with, this function not verifying it.

Raises:
  • TypeError – if nonce_parity is not an int.

  • ValueError – if pre_sig64 is not 64 bytes, if sec_adaptor is not 32 bytes or does not fit in them, if nonce_parity is not 0 or 1, or if pre_sig64 or sec_adaptor overflow.

btclib_secp256k1.zkp.musig.aggnonce_parse(aggnonce_bytes: bytes | bytearray | memoryview, name: str = 'aggregate nonce') → Any[source]

Parse an aggregate public nonce into its internal representation.

Parameters:
  • aggnonce_bytes – the aggregate nonce, 66 bytes, as nonce_agg answered it.

  • name – what the nonce is, as the exception should call it.

Returns:

The libsecp256k1-zkp aggregate nonce object.

Raises:

ValueError – if it is not 66 bytes, or not one BIP327 defines.

btclib_secp256k1.zkp.musig.aggnonce_serialize(aggnonce: Any) → bytes[source]

Serialize a parsed aggregate public nonce.

Parameters:

aggnonce – the libsecp256k1-zkp aggregate nonce object, as aggnonce_parse returns.

Returns:

Its 66 bytes.

Raises:

RuntimeError – if libsecp256k1-zkp refuses the object, which one it produced cannot make it do.

btclib_secp256k1.zkp.musig.extract_adaptor(sig64: bytes | bytearray | memoryview, pre_sig64: bytes | bytearray | memoryview, nonce_parity: int) → bytes[source]
btclib_secp256k1.zkp.musig.extract_adaptor(sig64: bytes | bytearray | memoryview, pre_sig64: bytes | bytearray | memoryview, nonce_parity: int, *, into: bytearray | memoryview) → None
btclib_secp256k1.zkp.musig.extract_adaptor(sig64: bytes | bytearray | memoryview, pre_sig64: bytes | bytearray | memoryview, nonce_parity: int, *, into: bytearray | memoryview | None) → bytes | None

Extract the secret adaptor from a signature and its pre-signature.

The inverse of adapt, and the reason an adaptor signature protocol works: whoever completes the pre-signature into sig64 – by whatever means, not necessarily adapt – has thereby revealed the secret to anyone holding both signatures. Neither input is verified here: “if it is merely given signatures that do not verify, the returned value will be nonsense”, the header’s own words, and a caller is expected to have verified both already.

Parameters:
  • sig64 – the 64-byte completed signature.

  • pre_sig64 – the 64-byte pre-signature it was completed from, from Session.partial_sig_agg.

  • nonce_parity – the output of Session.nonce_parity, called on the session that produced pre_sig64.

  • into – a writable 32-byte buffer to receive the adaptor, instead of the bytes this otherwise returns. See _secret.take and SECURITY.md for what that does and does not buy.

Returns:

The 32-byte secret adaptor – or None where into was given and holds it.

Raises:
  • TypeError – if nonce_parity is not an int, or if into is not a writable buffer of contiguous one-dimensional octets.

  • ValueError – if sig64 or pre_sig64 is not 64 bytes, if nonce_parity is not 0 or 1, if sig64 or pre_sig64 grossly overflow, or if into is not 32 bytes.

btclib_secp256k1.zkp.musig.nonce_agg(pubnonces_bytes: Sequence[bytes | bytearray | memoryview]) → bytes[source]

Aggregate the signers’ public nonces into one, for Session.

Any party can do this, trusted or not: an aggregate nonce computed wrong makes the final signature invalid rather than makes it forge, so BIP327 lets an untrusted coordinator collect the round-one nonces and hand every signer this one value back instead of every other signer’s.

Parameters:

pubnonces_bytes – the signers’ public nonces, 66 bytes each, in the order Session.partial_sig_agg will later take their partial signatures. At least one is required.

Returns:

The 66-byte aggregate nonce.

Raises:
  • ValueError – if the sequence is empty, or if one of the nonces is not 66 bytes or not one BIP327 defines – named by its position in the sequence, pubnonces_bytes[0] being “public nonce at index 0”.

  • RuntimeError – if libsecp256k1-zkp fails to aggregate or serialize the result, which no valid input can make it do.

btclib_secp256k1.zkp.musig.nonce_gen(pubkey_bytes: bytes | bytearray | memoryview, prvkey: bytes | bytearray | memoryview | int | None = None, msg32: bytes | bytearray | memoryview | None = None, keyagg_cache: KeyAggCache | None = None, extra_input32: bytes | bytearray | memoryview | None = None) → SecretNonce[source]

Start a signing session by generating a nonce.

Round one of BIP327: the public nonce this answers is sent to every other signer, or to whoever aggregates them, and the secret nonce is kept – held by the SecretNonce this returns, until a matching Session exists to call its partial_sign.

The 32 bytes of session randomness BIP327 requires, unique to this call and never reused, is not a parameter: it is generated here with secrets.token_bytes, the same source btclib_secp256k1.musig’s own nonce_gen uses, because the one thing a caller could do with it – supply their own – is the one thing the header warns against doing twice.

Parameters:
  • pubkey_bytes – the public key of the signer this nonce is for, 33 or 65 bytes. The secnonce this answers can only sign for a keypair matching it, which SecretNonce.partial_sign checks before it asks libsecp256k1-zkp to sign.

  • prvkey – this signer’s private key, 32 bytes or an int below 2**256, if already known. Optional, and folded into the nonce derivation to increase misuse-resistance where given; using the same private key across several MuSig2 sessions is fine.

  • msg32 – the 32-byte message this session will sign, if already known. Optional, for the same reason.

  • keyagg_cache – the key aggregation this session signs under, if already known. Optional, for the same reason.

  • extra_input32 – 32 bytes that do not repeat in normal use, such as the current time, folded into the derivation alongside the above. Optional, and no substitute for the session randomness above, which is what carries the uniqueness requirement.

Returns:

The secret nonce, held rather than returned as octets – the module docstring says why, and SecretNonce is what a caller wipes if this session is abandoned before partial_sign.

Raises:
  • TypeError – if keyagg_cache is not a KeyAggCache.

  • ValueError – if the public key is not a valid point, if the private key is given and is not 32 bytes, does not fit in them, or is not in [1, n-1], or if msg32 or extra_input32 is given and is not 32 bytes.

btclib_secp256k1.zkp.musig.nonce_gen_counter(prvkey: bytes | bytearray | memoryview | int, nonrepeating_cnt: int, msg32: bytes | bytearray | memoryview | None = None, keyagg_cache: KeyAggCache | None = None, extra_input32: bytes | bytearray | memoryview | None = None) → SecretNonce[source]

Start a signing session, deriving the nonce from a counter instead.

The alternative to nonce_gen for a signer with a non-repeating counter rather than good randomness available – a hardware signer keeping one in place of an RNG it does not trust, which is what this exists for. The counter must never repeat for this private key: two calls with the same keypair and the same nonrepeating_cnt, on any device signing with that key, reuse the nonce.

Parameters:
  • prvkey – this signer’s private key, 32 bytes or an int below 2**256. Mandatory here, where nonce_gen’s is optional: the counter’s uniqueness is a property of the key it counts for.

  • nonrepeating_cnt – the counter value, an int in [0, 2**64 - 1], unique to this call for this private key.

  • msg32 – the 32-byte message this session will sign, if already known.

  • keyagg_cache – the key aggregation this session signs under, if already known.

  • extra_input32 – 32 bytes that do not repeat in normal use, folded into the derivation alongside the above.

Returns:

The secret nonce, as nonce_gen returns it.

Raises:
  • TypeError – if nonrepeating_cnt is not an int, or if keyagg_cache is not a KeyAggCache.

  • ValueError – if the private key is not 32 bytes, does not fit in them, or is not in [1, n-1], if nonrepeating_cnt is out of range, if extra_input32 is given and is not 32 bytes, or if msg32 is given and is not 32 bytes.

  • RuntimeError – if libsecp256k1-zkp fails to extract the public key or to generate the nonce, which a private key already verified cannot make it do.

btclib_secp256k1.zkp.musig.partial_sig_parse(partial_sig_bytes: bytes | bytearray | memoryview, name: str = 'partial signature') → Any[source]

Parse a partial signature into its internal representation.

Parameters:
  • partial_sig_bytes – the partial signature, 32 bytes, as SecretNonce.partial_sign answered it.

  • name – what the signature is, as the exception should call it.

Returns:

The libsecp256k1-zkp partial signature object.

Raises:

ValueError – if it is not 32 bytes, or not one below the curve order.

btclib_secp256k1.zkp.musig.partial_sig_serialize(partial_sig: Any) → bytes[source]

Serialize a parsed partial signature.

Parameters:

partial_sig – the libsecp256k1-zkp partial signature object, as partial_sig_parse returns.

Returns:

Its 32 bytes.

Raises:

RuntimeError – if libsecp256k1-zkp refuses the object, which one it produced cannot make it do.

btclib_secp256k1.zkp.musig.pubnonce_parse(pubnonce_bytes: bytes | bytearray | memoryview, name: str = 'public nonce') → Any[source]

Parse a signer’s public nonce into its internal representation.

Parameters:
  • pubnonce_bytes – the public nonce, 66 bytes, as nonce_gen or nonce_gen_counter answered it through the SecretNonce they built.

  • name – what the nonce is, as the exception should call it, for a caller parsing more than one array of them.

Returns:

The libsecp256k1-zkp public nonce object.

Raises:

ValueError – if it is not 66 bytes, or not one BIP327 defines.

btclib_secp256k1.zkp.musig.pubnonce_serialize(pubnonce: Any) → bytes[source]

Serialize a parsed public nonce.

Parameters:

pubnonce – the libsecp256k1-zkp public nonce object, as pubnonce_parse returns.

Returns:

Its 66 bytes.

Raises:

RuntimeError – if libsecp256k1-zkp refuses the object, which one it produced cannot make it do.

btclib_secp256k1.zkp.rangeproof module

secp256k1_rangeproof.h: proving a commitment’s value lies in a range.

A Borromean-ring-signature variant of the Back-Maxwell range proofs from the Confidential Assets paper, over a zkp.generator.pedersen_commit commitment rather than the value itself: sign authors a proof that the committed value lies in [min_value, min_value + 2**mantissa), verify checks one, and rewind recovers what sign’s own author embedded – the value, the blinding factor and up to MAX_MESSAGE_LEN bytes of message – from a proof made with a matching nonce. info reads the public part of a proof without either.

MAX_MESSAGE_LEN is the one constant the header itself fixes (SECP256K1_RANGEPROOF_MAX_MESSAGE_LEN); the other size this module needs, the worst-case proof buffer, has no such #define – it is what secp256k1_rangeproof_max_size answers, and sign asks the library for it rather than carrying a copy of the 5134 the header’s own comment and zkp’s tests_impl.h state as today’s answer, which is not a promise about tomorrow’s. MAX_MESSAGE_LEN cannot be asked of the library the same way: nothing in secp256k1_rangeproof.h exposes it as a function, and cffi’s own cdef generation (generate_def’s gcc -P -E) preprocesses every #define away before lib is built from what remains, so no runtime call could read it even if this module wanted one to. The literal is what stands in its place, and a submodule bump that moved it would go unnoticed by anything here.

Every function here needs BTCLIB_LIBSECP256K1_ZKP’s extension, and none of them reaches for it at import time, for the reason btclib_secp256k1.zkp.generator’s own module docstring gives.

rewind takes its blinding factor out through btclib_secp256k1._secret.take, the same cross-ffi-safe function zkp.generator’s own module docstring names, but offers no into: the blinding factor is one member of the 5-tuple rewind answers, where an argument could not say which – SECURITY.md gives the same reason for the two silentpayments secrets that offer none either.

borromean_verify wraps a different function of the same header, secp256k1_borromean_verify, over the ring signature the rangeproof itself is built from rather than over a proof or a commitment – verify-only, secp256k1_borromean_sign staying internal to secp256k1-zkp. btclib-org/btclib-secp256k1#828’s own issue body has the serialization it takes and why the submodule now tracks a fork.

btclib_secp256k1.zkp.rangeproof.borromean_verify(e0: bytes | bytearray | memoryview, s: bytes | bytearray | memoryview, m: bytes | bytearray | memoryview, pubkeys: Sequence[bytes | bytearray | memoryview], rsizes: Sequence[int]) → bool[source]

Verify a Borromean ring signature, over serialized arguments.

secp256k1_borromean_sign – the signer – stays internal to secp256k1-zkp, so this is verify-only, one direction of an interoperation claim a downstream consumer’s own module docstring makes rather than checks. btclib-org/btclib-secp256k1#828 is the issue that asked for it, and its body has the serialization this wraps: e0 is the 32-octet initial challenge, s is n 32-octet scalars in ring-major order (one per pubkeys entry), m is the message of any length, pubkeys is one ordinary SEC compressed public key per ring member in that same ring-major order – not the quadratic-residue encoding zkp.generator and zkp.generator.pedersen_commit use – and rsizes is the size of each ring, summing to len(pubkeys).

Parameters:
  • e0 – the initial challenge, 32 bytes.

  • s – the ring signature’s scalars, ring-major, 32 bytes per pubkeys entry.

  • m – the message the signature covers, any length.

  • pubkeys – one public key per ring member, ring-major, ordinary SEC compressed or uncompressed.

  • rsizes – the size of each ring, each a non-bool int in [0, 128]; must sum to len(pubkeys).

Returns:

Whether the signature verifies – False covers both a well-formed signature that does not verify and a scalar in s at or above the curve order, neither being misuse.

Raises:
  • TypeError – if an rsizes element is not an int.

  • ValueError – if e0 is not 32 bytes, if s is not exactly 32 bytes per pubkeys entry, if any of pubkeys is not a valid public key, if pubkeys is empty or holds more than 128 entries, if rsizes is empty or holds more than 32 entries, if an rsizes element is a bool or outside [0, 128] – the largest a single ring can be, pubkeys holding at most 128 entries in all – or if sum(rsizes) does not equal len(pubkeys): the same ring-shape ARG_CHECK conditions secp256k1_borromean_verify itself enforces, matched here rather than left to its illegal callback or to cffi’s own refusal of a negative size_t.

btclib_secp256k1.zkp.rangeproof.info(proof: bytes | bytearray | memoryview) → tuple[int, int, int, int][source]

Extract the public parameters of a rangeproof, without verifying it.

Parameters:

proof – the proof.

Returns:

the exponent sign was called with (-1 meaning the value is public), the number of bits the proof covers, and the range it states – the same min_value/max_value verify answers, without checking that the commitment matches.

Return type:

(exp, mantissa, min_value, max_value)

Raises:

ValueError – if the proof cannot be decoded.

btclib_secp256k1.zkp.rangeproof.max_size(max_value: int, min_bits: int) → int[source]

Return an upper bound on the size of a rangeproof with these parameters.

An actual proof may be smaller: in particular this always overestimates the size of a single-value proof (exp=-1), and a min_value of 0 usually, but not always, gives a proof 8 bytes smaller than a nonzero one would.

Parameters:
  • max_value – the largest value that might be passed as value to sign. 2**64 - 1 gives the size of the largest possible proof, which is what sign itself asks for.

  • min_bits – the min_bits that will be passed to sign.

Returns:

The upper bound, in bytes.

Raises:
  • TypeError – if max_value or min_bits is not an int.

  • ValueError – if max_value is a bool or does not fit in 8 bytes, or if min_bits is out of [0, 64].

btclib_secp256k1.zkp.rangeproof.rewind(commit_bytes: bytes | bytearray | memoryview, proof: bytes | bytearray | memoryview, nonce: bytes | bytearray | memoryview, extra_commit: bytes | bytearray | memoryview = b'', gen_bytes: bytes | bytearray | memoryview | None = None) → tuple[bytes, int, bytes, int, int][source]

Verify a proof and recover what its author embedded in it.

Parameters:
  • commit_bytes – the Pedersen commitment being proved, 33 bytes.

  • proof – the proof.

  • nonce – the 32-byte secret nonce sign was called with – knowing it is what makes this call, rather than verify, possible.

  • extra_commit – additional data the proof’s signature covers, as it was passed to sign.

  • gen_bytes – the generator the commitment was made under, 33 bytes, or None for zkp.generator.h().

Returns:

the 32-byte blinding factor and the exact value sign committed to, the message bytes it embedded (trailing zeros beyond what it wrote included, up to MAX_MESSAGE_LEN), and the range the proof establishes – the same min_value/max_value verify answers.

Return type:

(blind, value, message, min_value, max_value)

Raises:

ValueError – if commit_bytes or gen_bytes is not a valid 33-byte generator or commitment, if nonce is not 32 bytes, or if the proof does not verify or the rewind fails – libsecp256k1-zkp reports both through the same return code.

btclib_secp256k1.zkp.rangeproof.sign(commit_bytes: bytes | bytearray | memoryview, blind: bytes | bytearray | memoryview | int, nonce: bytes | bytearray | memoryview, value: int, *, min_value: int = 0, exp: int = 0, min_bits: int = 0, message: bytes | bytearray | memoryview = b'', extra_commit: bytes | bytearray | memoryview = b'', gen_bytes: bytes | bytearray | memoryview | None = None) → bytes[source]

Prove that a committed value lies within a range.

Parameters:
  • commit_bytes – the Pedersen commitment being proved, as zkp.generator.pedersen_commit built it from blind, value and gen_bytes.

  • blind – the 32-byte blinding factor pedersen_commit used, or an int below 2**256. May be all-zeros only where min_bits is at least 3, a side effect of the underlying construction rather than a deliberate choice.

  • nonce – 32 bytes that must be unique to this call and never reused, and kept secret except from parties allowed to rewind the proof: anyone who learns it recovers value and blind, and reusing it across two proofs may expose blind even to a party that never learns it.

  • value – the value being committed to, an int in [0, 2**64). If min_value or exp is nonzero, value must be in [0, 2**63) so the proven range does not wrap past 2**64.

  • min_value – the minimum value the proof states the commitment can have. If nonzero, value must be in [0, 2**63) for the same reason given above.

  • exp – the base-10 exponent below which digits are made public, in [-1, 18] – a larger value makes a smaller proof at the cost of revealing more of it; -1 reveals value itself, and 0 is the most private. If nonzero, value must be in [0, 2**63) for the same reason given above.

  • min_bits – how many bits of the value to keep private, in [0, 64] – 0 asks for the library’s own minimal choice.

  • message – up to MAX_MESSAGE_LEN bytes to embed, recoverable by rewind with the same nonce.

  • extra_commit – additional data for the proof’s signature to cover; verify and rewind need the same bytes back.

  • gen_bytes – the generator commit_bytes was committed under, 33 bytes, or None for zkp.generator.h().

Returns:

The proof.

Raises:
  • TypeError – if value, min_value, exp or min_bits is not an int.

  • ValueError – if commit_bytes or gen_bytes is not a valid 33-byte generator or commitment, if blind or nonce is not 32 bytes or blind does not fit in them, if value or min_value is a bool or does not fit in 8 bytes, if exp or min_bits is out of range, if message is longer than MAX_MESSAGE_LEN, or if libsecp256k1-zkp refuses the call – which also reports the “one in 2**100” failure the header’s own docstring names, indistinguishably from a bad argument that reached this far.

btclib_secp256k1.zkp.rangeproof.verify(commit_bytes: bytes | bytearray | memoryview, proof: bytes | bytearray | memoryview, extra_commit: bytes | bytearray | memoryview = b'', gen_bytes: bytes | bytearray | memoryview | None = None) → tuple[int, int] | None[source]

Verify that a committed value lies within the range the proof states.

Parameters:
  • commit_bytes – the Pedersen commitment being proved, 33 bytes.

  • proof – the proof.

  • extra_commit – additional data the proof’s signature covers, as it was passed to sign.

  • gen_bytes – the generator the commitment was made under, 33 bytes, or None for zkp.generator.h().

Returns:

(min_value, max_value), the range the proof establishes for the committed value, or None where the proof does not verify – a verdict rather than an exception, (0, 2**64 - 1) being a range a proof can legitimately establish and so not usable as a failure sentinel.

Raises:

ValueError – if commit_bytes or gen_bytes is not a valid 33-byte generator or commitment.

Module contents

Pure python cffi bindings to libsecp256k1: https://github.com/bitcoin-core/secp256k1.

Every entry point of these bindings takes octets and answers octets. A libsecp256k1 object crosses the boundary only where a caller already holds one, and there are two ways it can. The first is a parse: keys.parse and keys.serialize, and the pairs beside them in xonly, dsa, recovery and silentpayments, are that bridge, and a MuSig2 session driven through the raw lib is who is on the other side of it.

The second is a keypair, which has no bridge because it has no serialization to be one: secp256k1_keypair holds a private key in libsecp256k1’s own layout, and the C API creates one and never writes it out. A caller that built one – through lib, as a MuSig2 signer does – holds an object nothing here could have handed it as octets, and xonly.from_keypair is what reads the public key off it. ssa.Signer.pubkey is that same call for a caller that let this package build the keypair instead.

Under each of those entry points is the half of it that speaks in those objects, spelled _foo_. The leading underscore says private, because an object is a promise no argument check can hold a caller to: what can be proved of a bare pointer’s contents is nothing, and what answers for it is libsecp256k1 itself, through the illegal callback – which most of these wrappers do not read, so a private half handed an object libsecp256k1 cannot use answers whatever that call answers: its own exception where a return code allowed one, and otherwise a False or an ordering that mean nothing. context.check immediately after the call is what says so, and the public entry point above is the one with no such case, having parsed the octets itself. The trailing one says which kind of private, _verify_ taking a parsed key where _parse_der is an ordinary helper. foo is _foo_ with a parse in front of it, a serialize behind it, or both, which is the equality tests/parsed_keys_test.py holds every pair to; what the private half saves is what composing two public ones pays between them, a serialization of a point that was already in hand and a parse of what was just serialized – and for a compressed key that parse is a field square root. The README’s Parsing the key once names the private halves that read the callback and raise what it reports.

btclib_secp256k1.CData

alias of Any