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:
Dave Collins 2021-03-11 14:18:20 -06:00
parent f497b73eaa
commit f6ea97a42d
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
3 changed files with 78 additions and 99 deletions

View File

@ -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)

View File

@ -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()

View File

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