stdscript: Add ecdsa multisig creation script.
This adds a convenience method to create a standard version 0 ecdsa multisignature redemption script that requires a threshold of signatures from a set of public keys. Full test coverage is included. This is part of a series of commits to fully implement the stdscript package.
This commit is contained in:
parent
2b099ba339
commit
bf5e5b4040
@ -12,6 +12,14 @@ const (
|
||||
// ErrUnsupportedScriptVersion indicates that a given script version is not
|
||||
// supported.
|
||||
ErrUnsupportedScriptVersion = ErrorKind("ErrUnsupportedScriptVersion")
|
||||
|
||||
// ErrTooManyRequiredSigs is returned from MultiSigScript when the
|
||||
// specified number of required signatures is larger than the number of
|
||||
// provided public keys.
|
||||
ErrTooManyRequiredSigs = ErrorKind("ErrTooManyRequiredSigs")
|
||||
|
||||
// ErrPubKeyType is returned when a script contains invalid public keys.
|
||||
ErrPubKeyType = ErrorKind("ErrPubKeyType")
|
||||
)
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
|
||||
@ -17,6 +17,8 @@ func TestErrorKindStringer(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"},
|
||||
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
|
||||
{ErrPubKeyType, "ErrPubKeyType"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
@ -73,6 +75,30 @@ func TestErrorKindIsAs(t *testing.T) {
|
||||
target: ErrUnsupportedScriptVersion,
|
||||
wantMatch: true,
|
||||
wantAs: ErrUnsupportedScriptVersion,
|
||||
}, {
|
||||
name: "ErrTooManyRequiredSigs != ErrPubKeyType",
|
||||
err: ErrTooManyRequiredSigs,
|
||||
target: ErrPubKeyType,
|
||||
wantMatch: false,
|
||||
wantAs: ErrTooManyRequiredSigs,
|
||||
}, {
|
||||
name: "Error.ErrTooManyRequiredSigs != ErrPubKeyType",
|
||||
err: makeError(ErrTooManyRequiredSigs, ""),
|
||||
target: ErrPubKeyType,
|
||||
wantMatch: false,
|
||||
wantAs: ErrTooManyRequiredSigs,
|
||||
}, {
|
||||
name: "ErrTooManyRequiredSigs != Error.ErrPubKeyType",
|
||||
err: ErrTooManyRequiredSigs,
|
||||
target: makeError(ErrPubKeyType, ""),
|
||||
wantMatch: false,
|
||||
wantAs: ErrTooManyRequiredSigs,
|
||||
}, {
|
||||
name: "Error.ErrTooManyRequiredSigs != Error.ErrPubKeyType",
|
||||
err: makeError(ErrTooManyRequiredSigs, ""),
|
||||
target: makeError(ErrPubKeyType, ""),
|
||||
wantMatch: false,
|
||||
wantAs: ErrTooManyRequiredSigs,
|
||||
}, {
|
||||
name: "Error.ErrUnsupportedScriptVersion != io.EOF",
|
||||
err: makeError(ErrUnsupportedScriptVersion, ""),
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
package stdscript
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/decred/dcrd/dcrec"
|
||||
"github.com/decred/dcrd/txscript/v4"
|
||||
)
|
||||
@ -679,3 +681,36 @@ func DetermineScriptTypeV0(script []byte) ScriptType {
|
||||
|
||||
return STNonStandard
|
||||
}
|
||||
|
||||
// MultiSigScriptV0 returns a valid version 0 script for a multisignature
|
||||
// redemption where 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 MultiSigScriptV0(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",
|
||||
threshold, len(pubKeys))
|
||||
return nil, makeError(ErrTooManyRequiredSigs, str)
|
||||
}
|
||||
|
||||
builder := txscript.NewScriptBuilder().AddInt64(int64(threshold))
|
||||
for _, pubKey := range pubKeys {
|
||||
if !txscript.IsStrictCompressedPubKeyEncoding(pubKey) {
|
||||
str := fmt.Sprintf("unable to generate multisig script with "+
|
||||
"unsupported public key %x", pubKey)
|
||||
return nil, makeError(ErrPubKeyType, str)
|
||||
}
|
||||
|
||||
builder.AddData(pubKey)
|
||||
}
|
||||
builder.AddInt64(int64(len(pubKeys)))
|
||||
builder.AddOp(txscript.OP_CHECKMULTISIG)
|
||||
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ package stdscript
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -952,6 +953,78 @@ func TestMultiSigRedeemScriptFromScriptSigV0(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiSigScriptV0 ensures the version 0 ECDSA multisignature script
|
||||
// creation function returns the expected scripts and errors.
|
||||
func TestMultiSigScriptV0(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg
|
||||
p2pkCompressedMain := hexToBytes("02192d74d0cb94344c9569c2e77901573d8d790" +
|
||||
"3c3ebec3a957724895dca52c6b4")
|
||||
p2pkCompressed2Main := hexToBytes("03b0bd634234abbb1ba1e986e884185c61cf43" +
|
||||
"e001f9137f23c2c409273eb16e65")
|
||||
p2pkUncompressedMain := hexToBytes("0411db93e1dcdb8a016b49840f8c53bc1eb68" +
|
||||
"a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f" +
|
||||
"9d4c03f999b8643f656b412a3")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
threshold int
|
||||
pubKeys [][]byte
|
||||
expected string
|
||||
err error
|
||||
}{{
|
||||
name: "normal 1-of-2",
|
||||
threshold: 1,
|
||||
pubKeys: [][]byte{p2pkCompressedMain, p2pkCompressed2Main},
|
||||
expected: fmt.Sprintf("1 DATA_%d 0x%x DATA_%d 0x%x 2 CHECKMULTISIG",
|
||||
len(p2pkCompressedMain), p2pkCompressedMain,
|
||||
len(p2pkCompressed2Main), p2pkCompressed2Main),
|
||||
}, {
|
||||
name: "normal 2-of-2",
|
||||
threshold: 2,
|
||||
pubKeys: [][]byte{p2pkCompressedMain, p2pkCompressed2Main},
|
||||
expected: fmt.Sprintf("2 DATA_%d 0x%x DATA_%d 0x%x 2 CHECKMULTISIG",
|
||||
len(p2pkCompressedMain), p2pkCompressedMain,
|
||||
len(p2pkCompressed2Main), p2pkCompressed2Main),
|
||||
}, {
|
||||
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,
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
script, err := MultiSigScriptV0(test.threshold, test.pubKeys...)
|
||||
if !errors.Is(err, test.err) {
|
||||
t.Errorf("%q: unexpected error - got %v, want %v", test.name, err,
|
||||
test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
const scriptVer = 0
|
||||
expected := mustParseShortForm(scriptVer, test.expected)
|
||||
if !bytes.Equal(script, expected) {
|
||||
t.Errorf("%q: unexpected result -- got: %x\nwant: %x", test.name,
|
||||
script, expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractStakeSubmissionPubKeyHashV0 ensures that extracting a public key
|
||||
// hash from a version 0 stake submission pay-to-pubkey-hash script works as
|
||||
// intended for all of the version 0 test scripts.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user