Skip to content

Instantly share code, notes, and snippets.

@groffer
Forked from gavinandresen/multisig.md
Created September 9, 2011 16:56
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save groffer/dba89537d352d591eb36 to your computer and use it in GitHub Desktop.
Save groffer/dba89537d352d591eb36 to your computer and use it in GitHub Desktop.
Multi-signature proposal

PROPOSAL:

Enable m-of-n signatures as standard transaction type(s).

This is only a proposal for expansion of IsStandard. This will allow experimentation with and implementation of multi-signature transactions.

Acknowledgements

@coblee @gavinandresen

See parent gist.

USE CASES:

  • Wallet backup: master offline/secure key in case 'working' wallet is lost (1-of-2 signatures)

  • Wallet security: require off-device confirmation and signing (2-of-2 signatures)

  • Escrow/dispute resolution (2-of-3 signatures)

  • Long term lock funds, requiring signatures from 2 officers of a company to disburse (2 of n)

  • Short term lock funds, with 2-factor authentication to disburse and an additional 2 of n backup in case one of the factors is lost (2-of-2 OR 2-of-n) * * "Escrow" with the the two parties having to agree or one of two backup observers helping in case of disagreement (2-of-2 OR 2-of-3 OR 2-of-3)

  • An options contract where the outcome is triggered by a key being broadcast: (Ks AND K1) OR (Kr AND K2) - s gets control of funds if K1 is broadcast, r gets control if K2 is broadcast.

NOTE:

I'm assuming deep knowledge of how bitcoin creates and verifies transaction outputs and inputs.

See:

TERMINOLOGY:

"Funder" : the funder creates the ScriptPubKey that requires m-of-n signatures to spend

"Redeemer" : the redeemer gathers the necessary signatures and spends the m-of-n ScriptPubKey by filling in a matching ScriptSig

CONSTRAINTS

Must not split the blockchain-- so must use existing, enabled opcodes.

Must not rely on unproven cryptographic algorithms or techniques

OUT OF SCOPE FOR THIS PROPOSAL

  • Protocol/method for acquiring signatures

  • RPC calls to support above use cases

  • Wallet format

  • New bitcoin address format(s) for funders, if any

I'd like to address all of these in separate proposals, to keep discussion focused on just the very-low-level mechanism. To roll these features out smoothly we need to enable them as standard transactions a release or two BEFORE we make them available to end-users or web services so they will get relayed and included into blocks.

DISCUSSION

Bitcoin currently supports two standard transaction script templates. This transaction type is used for coin-generation and the obsolete send-via-ip:

ScriptSig: sig
ScriptPubKey: public_key CHECKSIG

This is the more common "send to bitcoin address":

ScriptSig: sig public_key
ScriptPubKey: DUP OP_HASH160 160-bit-bitcoin-address EQUALVERIFY CHECKSIG

The advantage to the second template is that funders only need the 20-byte bitcoin address, and not the 65-byte full public key.

The new multisign transaction type will have an m-of-n ScriptPubKey of the following form (2-of-3 example):

0
OVER 2SWAP CHECKSIG SWAP HASH160 {pk1hash} EQUAL BOOLAND ADD
OVER 2SWAP CHECKSIG SWAP HASH160 {pk2hash} EQUAL BOOLAND ADD
OVER 2SWAP CHECKSIG SWAP HASH160 {pk3hash} EQUAL BOOLAND ADD
2 GREATERTHANOREQUAL

ScriptSig in this case would be sig1 pk1 sig2 pk2 sig3 pk3 where some of the signatures and the matching pubkey can be zero.

Each branch may specify a pubkey instead of a pubkey hash (second pubkey specified explicitly in this example):

0
OVER 2SWAP CHECKSIG SWAP HASH160 {pk1hash} EQUAL BOOLAND ADD
SWAP {pk2} CHECKSIG ADD
OVER 2SWAP CHECKSIG SWAP HASH160 {pk3hash} EQUAL BOOLAND ADD
2 GREATERTHANOREQUAL

In this case ScriptSig would be sig1 pk1 sig2 sig3 pk3.

m-of-n scripts can be combined into m-of-n OR p-of-q:

NOTIF {m-of-n script}
ELSE {p-of-q script}
ENDIF

In this case ScriptSig would be 0 [m-of-n script] or 1 [p-of-q script].

For sanity purposes, limit n to 5 and the number of combined m-of-n scripts to 4. This implies a maximum number of sigops per output to 20, although the maximum actual number of sigops executed will be 5.

  • The HASH160 version saves on transaction size for the m-of-n OR p-of-q version because the pubkey for one of the branches will never have to be recorded in the blockchain.

Objections

Denial of Service

It has been proposed that allowing multiple sigops per script opens the network to a denial of service attack through malicious transactions that use up more CPU. However, the density of sigops per transaction KB is less than one in 28 bytes. This is only 20% more than the existing transaction types. If the 20% amount is a concern, we could require a padding of 6 bytes per sigop.

Efficiency

The user may supply more signatures than the minimum required to evaluate the script to true. However, this is not a new issue because the users are already able to create more inputs and outputs than they really need. If we want to discourage overuse of sigops, we will need to charge transactions fees per sigop, including for existing transaction types.

Complexity

Aren't the scripts above complex? I believe most people familiar with the bitcoin scripting engine will be able to quickly evaluate the script format and review the corresponding IsStandard code for correctness. It seems better to spend some extra effort now for a general solution rather than creating multiple special cases that will have to be maintained indefinitely.

CHECKMULTISIG

Why not use CHECKMULTISIG?

  • It counts as 20 sigops for the DOS check. That brings the maximum txouts using CHECKMULTISIG per block to 1000. With a change output, that's only a max of 500 transactions.
  • If we want to allow the HASH160 form, it doesn't make the script any shorter

Updates

Most recent update is last.

  • Switch to IF/ELSE version, explain why CHECKMULTISIG is not helpful

  • Clarified that this is an IsStandard expansion proposal

  • Spell out ScriptSigs

@gavinandresen
Copy link

Why not use CHECKMULTISIG for the m-of-n ?

The farthest I'd want to generalize to start is:
(m-of-n) OR (p-of-q)
... with p and q <= 3.

I'm thinking exactly 2 new standard transactions will take care of the vast majority of multisig use cases:

m of n : just the plain-old CHECKMULTISIG

(m of n) OR (p of q)

And I'd rather implement the second using a plain IF / ELSE instead of the more generic TOALTSTACK/1SUB sequence, just because it means fewer opcodes used (which I could imagine possibly being important if a cryptographer ever tackles proving that bitcoin scripts are secure).

@groffer
Copy link
Author

groffer commented Sep 14, 2011

A couple of issues with CHECKMULTISIG:

  • It counts as 20 sigops for the DOS check. That brings the maximum txouts using CHECKMULTISIG per block to 1000. With a change output, that's only a max of 500 transactions.
  • If you want to allow the HASH160 form, it doesn't make the script any shorter

Personally I can't imagine that the number of ops in a script matters when doing a security review. I see the number of different forms as increasing risk, since it increases the complexity and number of edge cases.

@groffer
Copy link
Author

groffer commented Sep 14, 2011

Did you mean that you would like to minimize the total number of types of ops? I'm not opposed to using IF/ELSE to get rid of ALTSTACK usage.

@gavinandresen
Copy link

Yes, I mean I want to minimize types of ops used.

RE: CHECKMULTISIG DoS check: I'd forgotten about that, good point.

If you think that the number of different forms increases risk, then why are you proposing HASH160 forms? I'd REALLY like to keep this as simple as possible to start.

@groffer
Copy link
Author

groffer commented Sep 14, 2011

The HASH160 variation follows exactly the existing pubkey/hash160 variation, so it doesn't seem to increase risk.

Would it help set minds at ease if I wrote unit tests that cover all of script.cpp?

@gavinandresen
Copy link

Unit tests for all opcodes would be great; really great unit tests for any opcodes used in 'standard' transactions would be even better.

I'd also like to hear from Mike and Amir and any other people working on alternative implementations on whether or not using a generic form is any less work for them.

Worst-case, simple "a or b" written out in full using your current proposal looks like:

  0 TOALTSTACK
  DUP NOTIF DROP
  0
  OVER 2SWAP CHECKSIG SWAP HASH160 {pk1hash} EQUAL BOOLAND ADD
  1 GREATERTHANOREQUAL
  TOALTSTACK 0 ENDIF 1SUB
  DUP NOTIF DROP
  0
  OVER 2SWAP CHECKSIG SWAP HASH160 {pk2hash} EQUAL BOOLAND ADD
  1 GREATERTHANOREQUAL
  TOALTSTACK 0 ENDIF 1SUB
  FROMALTSTACK

... or 39 opcodes.

Optimized it would be:

 IF
   DUP OP_HASH160 pkhash1 EQUALVERIFY CHECKSIG
 ELSE
   DUP OP_HASH160 pkhash2 EQUALVERIFY CHECKSIG
 ENDIF

... or 12 opcodes. Support only full public keys and it is 5 opcodes versus 29 for the generic version.

@gavinandresen
Copy link

Meta-level thought: optimizing for ease of implementation at the expense of everybody-pays-more-in-transaction-fees seems like the wrong thing to do. Implementors are a fixed cost, so it doesn't (or shouldn't) matter if they have to do a little extra work. But if it costs users an extra 20 bytes in every transaction, they'll be paying for those bytes in increased transaction fees forever.

@groffer
Copy link
Author

groffer commented Sep 15, 2011

I switched the proposal to IF/ELSE.

When thinking about tx fees, it makes sense to look at the entire tx size, including the txout of the input and the txin of the output. Also, I think we should look at a AND b OR c because a OR b can be implemented by count(a,b) > 1 - i.e. one branch with 1 GREATERTHANOREQUAL.

The optimized version of scriptSig is:

IF
DUP OP_HASH160 pkhash1 EQUALVERIFY CHECKSIG
DUP OP_HASH160 pkhash2 EQUALVERIFY CHECKSIG BOOLAND
ELSE
DUP OP_HASH160 pkhash3 EQUALVERIFY CHECKSIG
ENDIF

and the corresponding scriptPubKey is either 0 sig1 pubkey1 sig2 pubkey2 or 1 sig3 pubkey3. The script length is 19 ops + 60 hash bytes = 79 bytes. The total scriptPubKey length for the first case is 1 + 61 * 2 + 71 * 2 = 265. The total script length for input + output is 344.

The generalized version of scriptSig for a AND b OR c is:

IF 0
OVER 2SWAP CHECKSIG SWAP HASH160 pkhash1 EQUAL BOOLAND ADD
OVER 2SWAP CHECKSIG SWAP HASH160 pkhash2 EQUAL BOOLAND ADD
2 GREATERTHANOREQUAL
ELSE 0
OVER 2SWAP CHECKSIG SWAP HASH160 pkhash3 EQUAL BOOLAND ADD
1 GREATERTHANOREQUAL
ENDIF

and the length is 36 + 60 hash bytes = 96, which brings the total length for input and output to 361 bytes. This is an increase of just 5%.

Another thing I just thought of - for multiple branches, you save bytes by using HASH160 instead of a bare pubkey because one branch pubkeys need never be included in the blockchain.

@gavinandresen
Copy link

RE: saving bytes using HASH160: good point.

How are you imagining this being implemented in bitcoin? A little state machine in Solver() that tries to pattern match all the sub-snippets (that makes me very nervous-- seems like a likely place for a critical bug to creep in or to be exploitable by somebody mounting a CPU denial-of-service attack)?

I'm not terribly worried about getting the original bitcoin's implementation correct, I am more worried of making it more complicated for Block Explorer or other tools/sites/implementations that interpret bitcoin scripts to get it right.

I'm imagining a really fast brute-force matcher that has templates for all the variations, which is why I'm pushing for for n <= 3. I benchmarked block-chain downloads and a good chunk of time was spent in Solver() extracting and checking keys and hashes; transaction validation and processing will eventually be our bottleneck.

I think I could live with:

  • super-optimized two-signature transactions with raw public keys: a AND b / a OR b
    (because I think they will be, by far, the most common cases)
  • and generic (m-of-n) OR (p-of-q) with m/n/p/q between 1 and 3 and hashed public keys.

That also gives just (p-of-q) because you can always do (p-of-q) OR OP_0-as-a-placeholder-hash.

It also gives (a and b) with hashes (that is (2-of-2) OR OP_0_placeholder) and (a or b) with hashes (either (1-of-1) OR (1-of-1) or (1-of-2) OR (OP_0_placeholder) ).

If I'm counting correctly, that'd be 8 more templates in Solver(), eliminating functionally identical cases. And that seems like plenty of rope for us to hang ourselves with.

@gavinandresen
Copy link

Also: are you writing your ScriptSigs top-of-stack first? Comments in the bitcoin code always have top-of-stack on the right....

@Mesmer
Copy link

Mesmer commented Sep 20, 2011

@gavinandresen
meta-level food: I don't think paying slightly more for these advance multi-sign feature is a bad thing, after all in the future if bitcoin becomes widely adopted, lower end nodes won't able to function properly to begin with simply due to block size and sheer tx count. another approach is to keep the complexity low while we add more sanity checks on the stack to prevent possible DoS from happening, which is a different aspect e.g. DUP NOTIF DROP which tights into the increase opcode count and increased size, see point above. tl;dr more complex transactions calls for more fees, this is the correct approach imo.

EDIT: although now I think it means that future spending of these coins will carry these txsize and fees indefinitely, which is a drawback for people who was never part of this "advanced tx/contract"...

I too advocate the simple a AND b / a OR b common cases,and to extend that you can just really make m-of-n OR p-of-q / m-of-n AND p-of-q which is the same thing with more creative uses.
Also the more I look at the concept of Master Key I mentioned earlier the more I dislike it, since it doesn't offer additional security in any form and the usability is really limited to the signers own laziness, I do not think this is a human element we should try to caress in this proposal.

@maaku
Copy link

maaku commented Sep 21, 2011

You should re-structure the scripts to do hash comparisons first, then sigops. Signature verification is very expensive vs. a hash & compare, so it should be checked for first in the case of a DoS attack.

@nanotube
Copy link

Though I'm not a pro at bitcoin scripts, fwiw I have to say I am a fan of doing a generic solution once, than making modifications for every kind of specific new transaction many times.

@groffer
Copy link
Author

groffer commented Sep 29, 2011

@gavinandresen - check out the PartialSolver refactor of PartialSolver out of Solver in script.cpp in my pull request: groffer/bitcoin@b2f9366

It's a simple refactor that allows script snippets to be matched against.

@gavinandresen
Copy link

You might want to get involved in the OP_EVAL discussion: https://bitcointalk.org/index.php?topic=46538.msg553689#msg553689

@groffer
Copy link
Author

groffer commented Oct 3, 2011

Thanks for the heads-up. I'm a "newbie" on that forum. I posted here maybe someone can link to it in the original thread?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment