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 } }