Standardness policy rules for legacy Multisig script is incoherent #33882

issue roconnor-blockstream openend this issue on November 16, 2025
  1. roconnor-blockstream commented at 2:09 am on November 16, 2025: contributor

    Over the years policy surrounding multisig scripts, particularly for legacy multisig scripts, has evolved in a haphazard way with various PRs affecting legacy multisig script policy without considering the ramifications.

    The current status of legacy multisig script policy is as follows:

    • Legacy multisig outputs in transactions can be at most m-of-3. All pubkeys in such outputs must be of the form of a compressed pubkey, or uncompressed pubkey, or a hybrid pubkey.
    • Legacy multisig inputs being redeemed in transaction can be m-of-n with m and n 20 or less. Any public keys processed by OP_CHECKMULTISIG must be compressed or uncompressed pubkeys. hybrid or other data blobs are only allowed in the unprocessed part of the public key list.

    While it is acceptable for legacy multisig output creation policy to be stricter than redemption policy, it makes no sense to allow hybrid keys in outputs by policy, but then to disallow them as inputs by policy.

    However, there are further issues. Legacy multisig policy has evolved over time. In the beginning when legacy multisig outputs were introduced as standard by BIP 11, “pubkeys” were allowed by policy to be any value between 33 and 120 bytes. Some Bitcoin users have taken advantage of those liberal policies and created legacy multisig script UTXOs that were redeemable by policy at the time they were created, but are no longer redeemable by policy today. The UTXO 4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6:1 from 2014-01-12 is but one example. @ajtowns’s analysis in #33755 suggests there are at least 147,470 such UTXOs that have become soft-confiscated over time, meaning that when they were created they were redeemable by policy, but become no longer redeemable by policy and would require direct miner assistance to be spent.

    Based on my research, I believe there are 4 tasks that need to be done to make legacy Multisig policy coherent again.

    1. Ban hybrid keys in legacy outputs

    We should strengthen the rules surrounding creating legacy multisig (and legacy single sig) to ban the use of hybrid keys. Such keys are nearly irredeemable. However, we should only strengthen policy around the creating new legacy script outputs without further restricting policy around redeeming legacy script UTXOs.

    2. Bring legacy script redemption policy in line with P2SH redeem script policy.

    P2SH redeem script policy has also evolved over time. Current policy for P2SH redemption allow any scripts so long as they have a limited number of CHECKSIG related operations. I see no reason why legacy script should be subject to significantly more stringent constraints than P2SH redeem scripts are.

    3. Relax SCRIPT_VERIFY_STRICTENC policy to only ban hybrid keys.

    PR #5247 went unnecessarily overboard in its quest to stomp out uses of hybrid keys and made UTXOs that already existed at the time unspendable. PR #33755 partially reverts this change so that only hybrid keys are excluded from processing in CHECKMULTISIG.

    4. Bring SCRIPT_VERIFY_WITNESS_PUBKEYTYPE policy in line with BIP 143 requirements.

    This task is technically unrelated to legacy multisig policy, being instead about Segwit V0 multisig policy. However the policy implemented in Bitcoin Core does not match the policy stated in BIP 143, and they ought to be brought into agreement. Either by implementing the policy correctly (as #33759 does), or by amending BIP 143 to describe the current policy as implemented.

    In particular, both tasks 2 and 3 need to be completed in order to make stuck UTXOs, such as 4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6:1 spendable by policy again.

    I want to make it clear that UTXOs such as the above are not particularly abusive. They were simply caught up incidentally in the policy changes of PR #5247, which was only about banning the use of hybrid keys. 4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6:1 has no hybrid nor hybrid looking pubkeys in it.

    0OP_PUSHNUM_1
    1OP_PUSHBYTES_33 035bceeb417f25beaa28d133ee7b28faa1e4f5c2f76b8daf12c3fab18261718790
    2OP_PUSHBYTES_33 1c434e545250525459000000000000000000000001000000004190ab0000000000
    3OP_PUSHNUM_2
    4OP_CHECKMULTISIG
    

    The script from UTXO 4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6:1

  2. roconnor-blockstream commented at 2:12 am on November 16, 2025: contributor

    How did we get here

    I’ve compiled a list of what I believe are all the relevant PRs related to policy surrounding multisig scripts, listed in chronological order. This work is based on previous work done by @ajtowns

    PR #669; merged 2011-12-20

    This PR introduces m-of-n checkmultisig as a solvable script for the first time. Solvability requires m and n to be at most 16. Per BIP 11, standardness requires solvable MULTISIG tx outputs to be at most m-of-3 and m to be less than less than or equal to n.

    Any data between 33 and 120 bytes is considered a “pubkey” for solvability purposes.

    This PR also introduces a new standardness rule requiring redeemed legacy scripts to be solvable. This is done because the solver needs to be run to determine if the UTXO’s scriptpubkey being redeemed is an OP_EVAL (later P2SH) script.

    Both legacy scripts redeemed and OP_EVAL/P2SH redeem scripts are required to be solvable at redemption time. Additionally EVAL/P2SH redeem scripts are required to be standard.

    Be aware of the difference between a solvable script and a standard script. Standard scripts are limited to m-of-3 multisig, but solvability allows up to 16. Standardness is stronger than solvability.

    Because the scriptpubkey for legacy UTXOs are now checked for solvability, any subsequent strengthening of standardness rules via Solver becomes a vector for soft-confiscation of UTXOs.

    PR #769; merged 2012-02-07

    Unexpected stack items are banned using ScriptSigArgsExpected counting when redeeming legacy script and P2SH script. This seems to be done to prevent data stuffing in scriptsigs. It is unclear if the data stuffing is a concern or the malleability is a concern. (Later code comments in PR #4365 suggests malleability isn’t actually a concern, but PR comments in PR #7387 suggests it was.)

    The relevance here is that Solver’s output is now also being used to help count the number of expected stack items. Later ScriptSigArgsExpected will be removed.

    Unless I’m mistaken, this PR also no longer requires P2SH scripts to be standard and now merely requires them to be solvable. This enables larger P2SH multisig scripts beyond m-of-3. This change seems to be a violation of BIP 16 standardness rules, but it passes without comment.

    PR #1742; merged 2012-10-20

    CHECKSIG and CHECKMULTISIG operations are modified to check pubkeys to see if they are in canonical form, which must begin with 0x02, 0x03 or 0x04, and be of suitable lengths.

    At this point non-canonical pubkey stack items are not yet banned from OP_CHECKMULTISIG. Instead, mempool policy diverges from consensus policy as all non-canonical pubkey stack items immediately count as a signature failure, with OP_CHECKSIG pushing a 0 in these cases and OP_CHECKMULISIG moving to the next pubkey. While non-pubkey would always fail the CHECKSIG anyways, this is a problem for valid hybrid keys where script execution for mempool policy could cause execution to diverge from consensus rule execution.

    This counts as a slight tightening of policy around the redemption (but not creation) of scripts using hybrid keys and applies both to P2SH and legacy scripts.

    PR #3718; merged 2014-02-14

    Solvability (and hence standardness) for script is tightened so that for script standardness purposes pubkey data must be between 33 and 65 bytes rather than 33 and 120 bytes.

    Due to the fact that solvability rules are checked upon UTXO redemption means that some funds, both P2SH and legacy, could have been soft-confiscated here.

    ::TODO:: see if any such UTXOs exist.

    PR #4365; merged 2014-06-27;

    P2SH rules are relaxed so non-solvable redeem scripts are allowed if sigop count is less than 15(?). Clean stack not required for redeeming such non-standard P2SH redeem scripts. I don’t know why this is considered okay.

    This is the beginning of the divergence of policy between redeeming legacy script and redeeming P2SH script.

    PR #5247; merged 2014-11-21

    The inconsistency between mempool policy execution of script and consensus execution of script is resolved by failing SCRIPT_VERIFY_STRICTENC standardness whenever a pubkeys processed by CHECK*SIG is non-canonical.

    This accidently causes in-use pre-counterparty-1.0-like legacy multiscript UTXO’s to be soft-confiscated. e.g. 4dacd03d73cb497229dbfe2e7209adc4221540efe0e4c57f408b09b2fd36ece6:1 from 2014-01-12. The effect of soft-confiscating existing UTXO seems to have been inadvertent and wasn’t contemplated in this PR.

    This policy only applies to redemption, not creation of outputs. Standardness rules still allow creating transactions with legacy script outputs with non-canonical keys.

    PR #7387; merged 2016-01-22;

    ScriptSigArgsExpected is removed since now SCRIPT_VERIFY_CLEANSTACK is robustly checking for a clean stack.

    Further divergence between redeeming legacy script and redeeming P2SH occurs as now as P2SH redeem scripts are themselves no longer checked for solvability. So long as the P2SH redeem scripts have a low enough GetSigOpCount they are allowed here. Legacy script redemptions still must be solvable scripts.

    In particular, P2SH multisig redeem scripts with 66-120 byte “pubkeys” occurring at the end of their list that may have been soft-confiscated in PR #3718 are now, once again, redeemable.

    PR #8499; merged 2016-10-17;

    For the new P2WSH script, this PR attempts to implement the policy mandated by BIP 143:

    As a default policy, only compressed public keys are accepted in P2WPKH and P2WSH. Each public key passed to a sigop inside version 0 witness program must be a compressed key: the first byte MUST be either 0x02 or 0x03, and the size MUST be 33 bytes. Transactions that break this rule will not be relayed or mined by default.

    However, the implementation fails to adhere to the policy written above and only checks that, for P2WSH, the pubkeys processed by CHECKMULTISIG are compressed public key, rather than checking that all keys passed to CHECKMULTISIG are compressed public keys.

    PR #12460; merged 2018-04-04;

    Solvability rules for scripts are tightened to require pubkeys in them to be well-formed, but not-necessarily canonical.

    Now the creation of legacy script UTXOs are, by standardness, required to be canonical or hybrid pubkeys. It is still the case that if script evaluation encounters a hybrid pubkey it is a violation of SCRIPT_VERIFY_STRICTENC standardness rules to redeem.

    However because scriptpubkeys still are checked during redemption for solvability for legacy script (but not P2SH script), this PR likely soft-confiscates even more UTXOs.

    Pre-counterparty-1.0-like legacy multiscript UTXO are now in violation of two redemption rules: They are neither solvable, and if they were executed the OP_CHECKMULTISIG would fail when trying to process non-canonical keys.

    ::TODO:: Find examples of UTXOs newly soft-confiscated by this PR.

    EDIT: PR #13194; merged 2018-05-30

    This PR refactors the Solver. The solver now fails when m > n in an m-of-n multisig. This is not a soft confiscation because such outputs are (a) unspendable anyways, and (b) were never standard to begin with.

    PR #20867; merged 2021-05-03

    This PR lifts the solvability of m-of-n multisig up from 16 to 20, increasing the types of legacy multisig scripts that are now redeemable. Transactions creating legacy multisig continue to be limited to 1 ≤ m ≤ n ≤ 3.

  3. ajtowns commented at 1:54 pm on November 17, 2025: contributor

    standardness requires solvable MULTISIG tx outputs to be at most m-of-3 and m to be less than n.

    The requirement is for m not to be greater than n, as far as I can see? (via both IsStandard() and Solver())

    Solvability (and hence standardness) for script is tightened so that for script standardness purposes pubkey data must be between 33 and 65 bytes rather than 33 and 120 bytes.

    ::TODO:: see if any such UTXOs exist.

    I believe there are four utxos that match this for bare multisig (ie, have a pubkey push of more than 65 bytes):

    However, they’re all 1-of-1 with 66 byte keys (that look like two 33 byte compressed keys concatenated?) so are presumably unspendable anyway.

    PR #12460; merged 2018-04-04;

    Solvability rules for scripts are tightened to require pubkeys in them to be well-formed, but not-necessarily canonical.

    ::TODO:: Find examples of UTXOs newly soft-confiscated by this PR.

    I believe this means that while we previously allowed spending 1 [data] [pubkey] 2 CHECKMULTISIG via a signature from pubkey, but not 1 [pubkey] [data] 2 CHECKMULTISIG (because we’d see data was not a compressed-or-uncompressed pubkey, and abort), we now would not, because data is not a valid-sized-pubkey. (This would fail AreInputsStandard() due to Solver() failing due to CPubKey::ValidSize() being strict)

    I think there are 405 utxos that fit that pattern, though they’re all very small. The ones with more than 786 sats are:

    Here’s a CSV of what I think are all the baremultisigs that aren’t just all-compressed-keys (ie every pubkey push is 33 bytes beginning with 02 or 03, m is less than n, n is 1 through 16): baremult-923997.csv.bz2 sha256 is 9951903516def05402ca4a6d8f64773f87e5f6611324e8ee86e44ce2a34982e7. It’s 21M compressed, 76M uncompressed, 266k utxos. spktype is BMU if it just has some uncompressed keys, BMD if it has hybrid/invalid keys, BMSZ if it has keys outside the 33-65 byte range, and BMWEIRD if it didn’t parse as expected. Doesn’t include anything that didn’t match OP_m ... OP_n OP_CHECKMULTISIG with 1<=m<=n<=16.

    For interest, looking at all utxos by type (with a little marker for ones that expose pubkeys to quantum attackers):

    type num utxos total value qv type desc
    WPKH 48,099,317 7,054,948.7201_8170 p2wpkh (segwit v0 single sig)
    PKH 45,291,937 5,721,757.7725_2479 p2pkh
    SH 12,946,965 4,015,214.8756_0840 p2sh
    PKU 34,376 1,711,833.0160_1304 :atom: pay to bare pubkey, uncompressed
    WSH 2,421,892 1,254,033.7434_0198 p2wsh (segwit v0 script/multisig)
    TR 55,861,027 182,751.7906_9625 :atom: taproot
    PK 10,276 6,536.6290_0123 :atom: pay to bare pubkey, compressed
    UNK 20,752 2,617.5862_4609 weird stuff (eg “sign with a pubkey that hashes to 0”)
    BMC 2,175,608 52.2136_0141 :atom: bare multisig, compressed keys
    BMD 176,159 9.9984_3912 :atom: bare multisig, data in keys
    BMU 89,796 6.9910_7231 :atom: bare multisig, some uncompressed keys
    SWUPG 54,957 0.3051_4917 insecure, future segwit versions
    BMWEIRD 42 0.0207_3932 weird things that look like a multisig, generally unspendable
    BMSZ 4 0.0004_8000 listed above

    FWIW, all the BMC, BMU, BMD and BMSZ utxos have n in [1,2,3].

    The BMWEIRD ones are mostly 0 or 1 sat stuff, most of which is unspendable anyway; the remaining four are:

    So I believe only the BMD utxos are interesting here (BMC and BMU should already be both spendable and, depending on permitbaremultisig, creatable).

    Personally, my take is that we should:

    • allow spending anything that (a) was previously spendable, (b) doesn’t make validation slow, and (c) doesn’t prohibit upgrade hooks/similar. ie, we should make it easy to spend bare multisigs with embedded data, or with hybrid pubkeys; but shouldn’t change how restricted segwitv0 p2wsh scripts are
    • by default, disallow creating obsolete scriptPubKeys, including both bare multisig and bare un/compressed pubkey
    • if the permitbaremultisig=0 default is overridden, allow creating bare multisig outputs that include hybrid pubkeys or encoded data up to 65 bytes (in particular, so that allowing spending of such outputs involves fewer special cases in the code)
  4. roconnor-blockstream commented at 2:26 pm on November 17, 2025: contributor

    The requirement is for m not to be greater than n, as far as I can see? (via both IsStandard() and Solver())

    Yeah, I meant to say “less than or equal to”. However, AFAICT this inequality is only enforced in standardness, not solvability.

  5. roconnor-blockstream commented at 3:06 pm on November 17, 2025: contributor

    we should make it easy to spend bare multisigs with embedded data, or with hybrid pubkeys

    Just to be clear, we cannot allow hybrid pubkeys to be processed by CHECKMULTISIG without undermining the purpose of the hybrid pubkey ban. (However we can allow hybrid pubkeys that are unprocessed by CHECKMULTISIG.)

    My understanding of the hybrid pubkey ban is that it is in place in case we wish to, one day, introduce a soft-fork that would allow implementations of Bitcoin Core to use a normal ECDSA validation routine that only succeeds on SEC 1 formatted public keys (i.e. canonical keys). This requires failing script in CHECKSIG and CHECKMULTISIG as soon as a hybrid pubkey is encountered for processing.

    I was previously thinking that we could only ban successful hybrid pubkey signatures. In other words, CHECKSIG and CHECKMULTISIG could process they hybrid pubkey and only fail the script in case the signature was successful. Alas, this would fail to meet the above purpose of the hybrid pubkey ban, for if this rule were to become part of consensus, then consensus implementation would still have to run the ECDSA validation engine on hybrid pubkeys in order to determine if they need to abort the script or not.

    That said we could simply abandon our hybrid pubkey policy entirely and give up on this soft-fork. I’m not advocating this, but it is a possibility.

    It might be worth dividing your BMD category into those with some hybrid pubkeys and those without any hybrid pubkeys.

  6. instagibbs commented at 4:22 pm on November 17, 2025: member

    The requirement is for m not to be greater than n, as far as I can see? (via both IsStandard() and Solver())

    circa e679ec969c8b22c676ebb10bea1038f6c8f13b33 creation was constrained to m-of-3

  7. ajtowns commented at 3:16 am on November 18, 2025: contributor

    Yeah, I meant to say “less than or equal to”. However, AFAICT this inequality is only enforced in standardness, not solvability.

    Solver() calls MatchMultisig() which invokes num_keys = GetScriptNuymber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG) which returns nullopt if the CScriptNum isn’t minimal and between required_sigs and MAX_PUBKEYS_.., so I believe it’s enforced in both places.

    Just to be clear, we cannot allow hybrid pubkeys to be processed by CHECKMULTISIG without undermining the purpose of the hybrid pubkey ban

    Yes; I don’t think a hybrid pubkey ban is worth maintaining. By switching from openssl to libsecp256k1 we’ve avoided the possibility of new formats being unexpectedly introduced (ref), and the validation cost (and data carrying ability if faked) of a hybrid pubkey is essentially the same as that of an uncompressed pubkey which we’ll continue to support for spending. So I’m not seeing the value in treating hybrid keys differently from uncompressed keys.

    Not intending to strongly advocate that view vs other alternatives at this point; just putting it out there to ensure that if we stick with the current direction, that it’s for a good reason, not just because it’s the status quo.

    introduce a soft-fork that would allow implementations of Bitcoin Core to use a normal ECDSA validation routine that only succeeds on SEC 1 formatted public keys

    What we support by consensus matches the (earlier) ANSI X9.62-1998 standard; I don’t see why we’d go to the trouble of a soft fork just to switch to a different standard/library. (ref)

    It might be worth dividing your BMD category into those with some hybrid pubkeys and those without any hybrid pubkeys.

    I think there are 384 BMD outputs with a hybrid-ish pubkey (65 bytes beginning with 06/07), but all but one are 1-sat. Here are the 5 most recent such utxos:

    I think there’s five bare hybrid(looking) pubkey utxos:

    Obviously there could be zero to many hybrid pubkeys in either p2pkh or p2sh outputs as well.

    Python code:

     0def split_spk(spk):
     1    ops = []
     2    while len(spk) > 0:
     3        if spk[0] <= 75 and len(spk) > spk[0]:
     4            ops.append((spk[0], spk[1:spk[0]+1].hex()))
     5            spk = spk[spk[0]+1:]
     6        elif spk[0] >= 0x51 and spk[0] <= 0x60:
     7            ops.append(spk[0] - 0x50)
     8            spk = spk[1:]
     9        else:
    10            ops.append("OP_x%02X" % (spk[0]))
    11            spk = spk[1:]
    12    return ops
    13
    14def find_hybrid(sspk):
    15    for a in sspk:
    16        if isinstance(a, tuple):
    17            l,x = a
    18            if l == 65 and x[0] == '0' and x[1] in "67": return True
    19    return False
    20
    21x = [a for a in [a.strip().split(",") for a in open("baremulti-923997.csv", "r").readlines()]][1:] # if you unbz2'ed the datafile above
    22y = [tuple(a + [split_spk(bytes.fromhex(a[6]))]) for a in x]
    23z = [yy for yy in y if find_hybrid(yy[7])]
    
  8. roconnor-blockstream commented at 3:08 pm on November 18, 2025: contributor

    Solver() calls MatchMultisig() which invokes num_keys = GetScriptNuymber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG).

    This checking m ≤ n behaviour in the Solver seems to have been added in #13194. However this wouldn’t count as soft-confiscations because multisigs with m > n are presumably unspendable anways.

    What we support by consensus matches the (earlier) ANSI X9.62-1998 standard

    Ah, I was not aware of this.

    I wouldn’t object to dropping the hybrid key protections. I probably wouldn’t object even if I didn’t know about ANSI X9.62-1998.

  9. willcl-ark added the label Brainstorming on Nov 18, 2025
  10. willcl-ark added the label TX fees and policy on Nov 18, 2025
  11. ajtowns commented at 2:38 am on November 19, 2025: contributor

    This checking m ≤ n behaviour in the Solver seems to have been added in #13194. However this wouldn’t count as soft-confiscations because multisigs with m > n are presumably unspendable anways.

    Yeah, see https://github.com/bitcoin/bitcoin/blob/4405b78d6059e536c36974088a8ed4d9f0f29898/script.cpp#L745 from the start of git history.

  12. 151henry151 commented at 4:39 am on November 20, 2025: contributor

    P2SH allows non-solvable redeem scripts with low sigop counts (PR #4365), while legacy scripts still require solvability. This inconsistency is hard to justify, especially since both rely on sigop limits for security.

    Aligning legacy script redemption with P2SH policy would remove the asymmetry and fix the Counterparty UTXOs without special-casing, while preserving security through existing sigop limits.

    If hybrid key bans are reconsidered (as ajtowns suggested), that could simplify further, but fixing the policy asymmetry seems like the right first step.

  13. roconnor-blockstream commented at 6:04 pm on November 20, 2025: contributor

    I agree. I think my next step would be to PR a change to AreInputsStandard to bring P2SH and legacy script policy into alignment.

    I’m not quite sure when I’ll get to this, so if someone else wants to take a crack at it, I wouldn’t be surprised if they can do it better / sooner than me.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-11-27 00:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me