From f6ea97a42d84e6fa051e63d1514cc85c8f5b33dd Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Thu, 11 Mar 2021 14:18:20 -0600 Subject: [PATCH] txscript: Accept raw public keys in MultiSigScript. This modifies MultiSigScript to accept raw serialized variadic pubkeys instead of a fixed slice of addresses and updates the tests to be more consistent with the rest of the code. It also now takes the required number of signatures (threshold) as the first parameter so it more closely matches the typical way threshold signatures are referred to. Namely, m-of-n where m is the threshold and n is the number of pubkeys. These style of multisig scripts involve public keys as opposed to addresses, and callers typically have the public key as opposed to an address already anyway, so the new API is more convenient, logical, and flexible. Finally, in order to keep the semantics the same such that only compressed secp256k1 public keys are used, an additional check is added to reject the provided serialized public keys when they do not adhere to the strict compressed public key encoding requirements. --- txscript/sign_test.go | 15 ++--- txscript/standard.go | 31 ++++++--- txscript/standard_test.go | 131 +++++++++++++++----------------------- 3 files changed, 78 insertions(+), 99 deletions(-) diff --git a/txscript/sign_test.go b/txscript/sign_test.go index cb30b933..d733c06b 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -2228,9 +2228,8 @@ func TestSignTxOutput(t *testing.T) { break } - pkScript, err := MultiSigScript( - []*dcrutil.AddressSecpPubKey{address1, address2}, - 2) + pkScript, err := MultiSigScript(2, pk1.SerializeCompressed(), + pk2.SerializeCompressed()) if err != nil { t.Errorf("failed to make pkscript "+ "for %s: %v", msg, err) @@ -2336,9 +2335,8 @@ func TestSignTxOutput(t *testing.T) { break } - pkScript, err := MultiSigScript( - []*dcrutil.AddressSecpPubKey{address1, address2}, - 2) + pkScript, err := MultiSigScript(2, pk1.SerializeCompressed(), + pk2.SerializeCompressed()) if err != nil { t.Errorf("failed to make pkscript "+ "for %s: %v", msg, err) @@ -2484,9 +2482,8 @@ func TestSignTxOutput(t *testing.T) { break } - pkScript, err := MultiSigScript( - []*dcrutil.AddressSecpPubKey{address1, address2}, - 2) + pkScript, err := MultiSigScript(2, pk1.SerializeCompressed(), + pk2.SerializeCompressed()) if err != nil { t.Errorf("failed to make pkscript "+ "for %s: %v", msg, err) diff --git a/txscript/standard.go b/txscript/standard.go index 9acf8f51..4b15e1f8 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -1150,22 +1150,33 @@ func PayToAddrScript(addr dcrutil.Address) ([]byte, error) { } // MultiSigScript returns a valid script for a multisignature redemption where -// nrequired of the keys in pubkeys are required to have signed the transaction -// for success. An Error with kind ErrTooManyRequiredSigs will be returned if -// nrequired is larger than the number of keys provided. -func MultiSigScript(pubkeys []*dcrutil.AddressSecpPubKey, nrequired int) ([]byte, error) { - if len(pubkeys) < nrequired { +// the specified threshold number of the keys in the given public keys are +// required to have signed the transaction for success. +// +// The provided public keys must be serialized in the compressed format or an +// error with kind ErrPubKeyType will be returned. +// +// An Error with kind ErrTooManyRequiredSigs will be returned if the threshold +// is larger than the number of keys provided. +func MultiSigScript(threshold int, pubKeys ...[]byte) ([]byte, error) { + if len(pubKeys) < threshold { str := fmt.Sprintf("unable to generate multisig script with "+ "%d required signatures when there are only %d public "+ - "keys available", nrequired, len(pubkeys)) + "keys available", threshold, len(pubKeys)) return nil, scriptError(ErrTooManyRequiredSigs, str) } - builder := NewScriptBuilder().AddInt64(int64(nrequired)) - for _, key := range pubkeys { - builder.AddData(key.ScriptAddress()) + builder := NewScriptBuilder().AddInt64(int64(threshold)) + for _, pubKey := range pubKeys { + if !IsStrictCompressedPubKeyEncoding(pubKey) { + str := fmt.Sprintf("unable to generate multisig script with "+ + "unsupported public key %x", pubKey) + return nil, scriptError(ErrPubKeyType, str) + } + + builder.AddData(pubKey) } - builder.AddInt64(int64(len(pubkeys))) + builder.AddInt64(int64(len(pubKeys))) builder.AddOp(OP_CHECKMULTISIG) return builder.Script() diff --git a/txscript/standard_test.go b/txscript/standard_test.go index a14daf9a..6a801a67 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -537,98 +537,69 @@ func TestMultiSigScript(t *testing.T) { t.Parallel() // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg - p2pkCompressedMain, err := dcrutil.NewAddressSecpPubKey(hexToBytes("02192d"+ - "74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), - mainNetParams) - if err != nil { - t.Fatalf("Unable to create pubkey address (compressed): %v", - err) - } - p2pkCompressed2Main, err := dcrutil.NewAddressSecpPubKey(hexToBytes("03b0b"+ - "d634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"), - mainNetParams) - if err != nil { - t.Fatalf("Unable to create pubkey address (compressed 2): %v", - err) - } - - p2pkUncompressedMain := newAddressPubKey(hexToBytes("0411d" + - "b93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + - "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b41" + - "2a3")) + p2pkCompressedMain := hexToBytes("02192d74d0cb94344c9569c2e77901573d8d790" + + "3c3ebec3a957724895dca52c6b4") + p2pkCompressed2Main := hexToBytes("03b0bd634234abbb1ba1e986e884185c61cf43" + + "e001f9137f23c2c409273eb16e65") + p2pkUncompressedMain := hexToBytes("0411db93e1dcdb8a016b49840f8c53bc1eb68" + + "a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f" + + "9d4c03f999b8643f656b412a3") tests := []struct { - keys []*dcrutil.AddressSecpPubKey - nrequired int + name string + threshold int + pubKeys [][]byte expected string err error - }{ - { - []*dcrutil.AddressSecpPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 1, - "1 DATA_33 0x02192d74d0cb94344c9569c2e77901573d8d7903c" + - "3ebec3a957724895dca52c6b4 DATA_33 0x03b0bd634" + - "234abbb1ba1e986e884185c61cf43e001f9137f23c2c4" + - "09273eb16e65 2 CHECKMULTISIG", - nil, - }, - { - []*dcrutil.AddressSecpPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 2, - "2 DATA_33 0x02192d74d0cb94344c9569c2e77901573d8d7903c" + - "3ebec3a957724895dca52c6b4 DATA_33 0x03b0bd634" + - "234abbb1ba1e986e884185c61cf43e001f9137f23c2c4" + - "09273eb16e65 2 CHECKMULTISIG", - nil, - }, - { - []*dcrutil.AddressSecpPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 3, - "", - ErrTooManyRequiredSigs, - }, - { - // By default compressed pubkeys are used in Decred. - []*dcrutil.AddressSecpPubKey{ - p2pkUncompressedMain.(*dcrutil.AddressSecpPubKey), - }, - 1, - "1 DATA_33 0x0311db93e1dcdb8a016b49840f8c53bc1eb68a3" + - "82e97b1482ecad7b148a6909a5c 1 CHECKMULTISIG", - nil, - }, - { - []*dcrutil.AddressSecpPubKey{ - p2pkUncompressedMain.(*dcrutil.AddressSecpPubKey), - }, - 2, - "", - ErrTooManyRequiredSigs, - }, - } + }{{ + name: "normal 1-of-2", + threshold: 1, + pubKeys: [][]byte{p2pkCompressedMain, p2pkCompressed2Main}, + expected: "1 DATA_33 0x02192d74d0cb94344c9569c2e77901573d8d7903c" + + "3ebec3a957724895dca52c6b4 DATA_33 0x03b0bd634" + + "234abbb1ba1e986e884185c61cf43e001f9137f23c2c4" + + "09273eb16e65 2 CHECKMULTISIG", + }, { + name: "normal 2-of-2", + threshold: 2, + pubKeys: [][]byte{p2pkCompressedMain, p2pkCompressed2Main}, + expected: "2 DATA_33 0x02192d74d0cb94344c9569c2e77901573d8d7903c" + + "3ebec3a957724895dca52c6b4 DATA_33 0x03b0bd634" + + "234abbb1ba1e986e884185c61cf43e001f9137f23c2c4" + + "09273eb16e65 2 CHECKMULTISIG", + }, { + name: "threshold 3 > 2 pubkeys", + pubKeys: [][]byte{p2pkCompressedMain, p2pkCompressed2Main}, + threshold: 3, + expected: "", + err: ErrTooManyRequiredSigs, + }, { + name: "threshold 2 > 1 pubkey", + pubKeys: [][]byte{p2pkCompressedMain}, + threshold: 2, + expected: "", + err: ErrTooManyRequiredSigs, + }, { + name: "reject uncompressed pubkeys", + pubKeys: [][]byte{p2pkUncompressedMain}, + threshold: 1, + expected: "", + err: ErrPubKeyType, + }} t.Logf("Running %d tests", len(tests)) - for i, test := range tests { - script, err := MultiSigScript(test.keys, test.nrequired) + for _, test := range tests { + script, err := MultiSigScript(test.threshold, test.pubKeys...) if !errors.Is(err, test.err) { - t.Errorf("MultiSigScript #%d: unexpected error - got %v, want %v", - i, err, test.err) + t.Errorf("%q: unexpected error - got %v, want %v", test.name, err, + test.err) continue } expected := mustParseShortForm(test.expected) if !bytes.Equal(script, expected) { - t.Errorf("MultiSigScript #%d got: %x\nwant: %x", - i, script, expected) + t.Errorf("%q: unexpected result -- got: %x\nwant: %x", test.name, + script, expected) continue } }