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.
This commit is contained in:
parent
f497b73eaa
commit
f6ea97a42d
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user