# Where the threshold lives A bitcoin spend can require more than one party in two ways. They are separate code paths, in separate packages: script multisig is btclib's, MuSig2 and FROST are this package's, and the psbt transport of all of them is btclib-wallet's. The numbers *k*, *t* and *n* do not tell the two ways apart. What tells them apart is where the threshold is enforced: in consensus, by every node, or in cryptography, settled off-chain before the transaction exists. ## The discriminant In script multisig the threshold is script policy, enforced by consensus. The script holds *n* public keys, the input supplies *k* signatures, and every node runs `OP_CHECKMULTISIG` — `OP_CHECKSIGADD` in tapscript — and counts. Those *k* signatures are ordinary independent signatures, each under its own key: the signers never talk to one another, each signs alone, in any order, months apart. In MuSig2 and in FROST the threshold is cryptography, off-chain. Consensus knows nothing of it: it sees one key and one 64-byte BIP340 signature, exactly what a single-key spend presents. The group structure is gone before the transaction exists. ## Where MuSig2 and FROST separate They are two mechanisms, not two settings of one. **MuSig2 aggregates independent keys.** Each signer holds its own secret key and never shares it; the *n* public keys combine into one aggregate key. All *n* have to take part, because there is nothing to interpolate: the keys are not points of a polynomial, and a missing partial signature is a sum that does not close. **FROST shares one key.** There is a single threshold secret key, Shamir-split into *n* shares, and nobody holds it whole. Any *t* of the shares reconstruct, by Lagrange interpolation, not the key but the signature. So MuSig2 with a *t* smaller than *n* is not a configuration. It is a different algorithm. ## What changes in practice | | script multisig | MuSig2 | FROST | | --- | --- | --- | --- | | threshold enforced by | consensus | cryptography | cryptography | | on chain | *n* keys, *k* sigs | one key, one sig | one key, one sig | | signer interaction | none | two rounds | two rounds | | secret keys | *n* independent | *n* independent | one, in *n* shares | | who signed | in the input | not derivable | not derivable | | membership change | new address | new address | same address | | bound on *n* | consensus | serialization | security | ### Cost and privacy A 3-of-5 script spend reveals five public keys and three signatures, and tells anyone watching that the output was a 3-of-5. MuSig2 and FROST produce a spend indistinguishable from a single-key one, of constant size, whatever *n* is. ### No consensus ceiling `OP_CHECKMULTISIG` stops at `MAX_PUBKEYS_PER_MULTISIG` keys, which btclib's `btclib.script.engine.script` enforces. A `multi_a()` leaf is bounded by the stack instead, one element per key, which is what `_MAX_PUBKEYS_PER_MULTI_A` in bip32-descriptors' `bip32_descriptors.descriptors.miniscript` states; and a tapscript spend charges its sigops budget 50 for every non-empty signature, whether or not that signature verifies. MuSig2 and FROST have no ceiling the chain imposes, the chain seeing one key. What bounds them is the specification, and the two bounds are not the same kind of thing. BIP327's is serialization: the number of individual public keys is an unsigned 32-bit count. BIP445's is security: it refuses an *n* past 128, and so does {py:mod}`ecc.ecc.frost`, whose module docstring gives the reason. ### The price is state and simultaneity Script multisig is stateless and has no shared nonce: a signer that is compromised or offline does not touch the others. MuSig2 and FROST need a nonce round before the partial signatures — so, signers available at the same time — and above all a secret nonce that is never reused. Reusing one does not degrade security, it hands out the key. {py:func}`musig2.sign ` and {py:func}`frost.sign ` refuse a secret nonce they have already consumed; their docstrings say how. ### Attribution A script multisig input says which *k* of the *n* signed: it is an audit trail. A MuSig2 or a FROST signature does not — privacy for some uses, a gap for others, custody and internal delegation among them. ### Membership change In script multisig, adding or removing a signer means a new script, a new address, and moving the funds. MuSig2 costs the same: changing the set changes the aggregate key, so the output key and the address change too. In FROST the group public key is a property of the secret and not of how it is shared, so the same secret can be shared again over a different set and the funds stay where they are. ### Who has seen the secret Script multisig has no phase in which anybody holds everything. FROST has one wherever a trusted dealer distributes the shares: that dealer transiently knows the whole key. Avoiding it takes a distributed key generation, and {py:mod}`ecc.ecc.frost` takes key material already split rather than splitting any; btclib's [ISS 2200](https://github.com/btclib-org/btclib/issues/2200) tracks key generation. ## *t* = *n* FROST is not MuSig2 BIP445's own vectors include a 3-of-3 group. That group is still not MuSig2: one shared key against *n* independent keys aggregated, with everything that follows from it about who has seen the secret and about what a departing member costs. ## MuSig2 with *t* < *n* Not natively, for the reason above. What works is composition rather than configuration: a taproot tree with one leaf per admitted subset. Each leaf carries a MuSig2 key over that subset alone, or a `multi_a()`. The cost is combinatorial, C(*n*, *t*) leaves: three for a 2-of-3, 126 for a 5-of-9. Only the leaf used is revealed, and the key path can stay the *n*-of-*n* "everybody agrees", which is the cheap case. Workable for a small *n*, senseless past it. ## A FROST spend's psbt transport is btclib-wallet's own BIP373 assigns MuSig2 psbt field types of its own — participant public keys, a public nonce and a partial signature on an input, participant public keys on an output — which `btclib_wallet.psbt.psbt_in` and `btclib_wallet.psbt.psbt_out` carry and `btclib_wallet.psbt.musig2` gives meaning to. They are MuSig2-specific and do not extend to a threshold: a participants field would have to map a group key to its *t*, its *n*, the identifiers and the public shares, rather than an aggregate key to its participants; nonces and partial signatures would be indexed by identifier rather than by a participant's public key; and a session needs a field naming the subset that is signing it, for which BIP373 has no counterpart, MuSig2's subset always being everybody. The taproot tweaks are the same and carry over. BIP445 specifies no psbt transport of its own, and no type bytes are assigned for FROST: choosing bytes in the space BIP174 reserves for future assignment is how two implementations collide. So `btclib_wallet.psbt.frost` writes the session into the key space BIP174 reserves for proprietary use instead, under a btclib identifier. No other wallet reads those records. That module's docstring says what becomes of them when a psbt BIP assigns FROST type bytes. ## Where each of them lives The two routes are both implemented, and they do not touch. The script threshold is btclib's: the op codes `OP_CHECKMULTISIG` and `OP_CHECKSIGADD` under `btclib.script`. bip32-descriptors' `bip32_descriptors.descriptors.descriptors` holds the descriptor grammar that writes one: `multi()` and `sortedmulti()` (BIP383), `multi_a()` and `sortedmulti_a()` (BIP386, BIP387). The cryptographic threshold is this package's: {py:mod}`ecc.ecc.musig2` (BIP327) and {py:mod}`ecc.ecc.frost` (BIP445). btclib-wallet's `btclib_wallet.psbt.musig2` (BIP373) and `btclib_wallet.psbt.frost` carry a session of each through a psbt: the first in the fields BIP373 assigns, the second in proprietary records of btclib's own. MuSig2 also has a spelling in the descriptor grammar, BIP390's `musig()` key expression, inside a `tr()` or a `rawtr()`; FROST has none. What they produce is not one object for all three. {py:func}`musig2.partial_sig_agg ` and {py:func}`frost.partial_sig_agg ` each answer an {py:class}`ecc.ecc.ssa.Sig`, the BIP340 signature {py:mod}`ecc.ecc.ssa` verifies and so does every other BIP340 verifier. A `multi_a()` leaf is checked against BIP340 signatures too, btclib's tapscript engine calling {py:mod}`ecc.ecc.ssa` for them. `OP_CHECKMULTISIG` is the one that differs: it verifies ECDSA, through {py:mod}`ecc.ecc.dsa`.