// MakeSecp256k1MultiSigScript creates a multi-signature script that can be // redeemed with nRequired signatures of the passed keys and addresses. If the // address is a P2PKH address, the associated pubkey is looked up by the wallet // if possible, otherwise an error is returned for a missing pubkey. // // This function only works with secp256k1 pubkeys and P2PKH addresses derived // from them. func (w *Wallet) MakeSecp256k1MultiSigScript(secp256k1Addrs []dcrutil.Address, nRequired int) ([]byte, error) { secp256k1PubKeys := make([]*dcrutil.AddressSecpPubKey, len(secp256k1Addrs)) var dbtx walletdb.ReadTx var addrmgrNs walletdb.ReadBucket defer func() { if dbtx != nil { dbtx.Rollback() } }() // The address list will made up either of addreseses (pubkey hash), for // which we need to look up the keys in wallet, straight pubkeys, or a // mixture of the two. for i, addr := range secp256k1Addrs { switch addr := addr.(type) { default: return nil, errors.New("cannot make multisig script for " + "a non-secp256k1 public key or P2PKH address") case *dcrutil.AddressSecpPubKey: secp256k1PubKeys[i] = addr case *dcrutil.AddressPubKeyHash: if addr.DSA(w.chainParams) != chainec.ECTypeSecp256k1 { return nil, errors.New("cannot make multisig " + "script for a non-secp256k1 P2PKH address") } if dbtx == nil { var err error dbtx, err = w.db.BeginReadTx() if err != nil { return nil, err } addrmgrNs = dbtx.ReadBucket(waddrmgrNamespaceKey) } addrInfo, err := w.Manager.Address(addrmgrNs, addr) if err != nil { return nil, err } serializedPubKey := addrInfo.(waddrmgr.ManagedPubKeyAddress). PubKey().Serialize() pubKeyAddr, err := dcrutil.NewAddressSecpPubKey( serializedPubKey, w.chainParams) if err != nil { return nil, err } secp256k1PubKeys[i] = pubKeyAddr } } return txscript.MultiSigScript(secp256k1PubKeys, nRequired) }
// DepositScript constructs and returns a multi-signature redemption script where // a certain number (Series.reqSigs) of the public keys belonging to the series // with the given ID are required to sign the transaction for it to be successful. func (p *Pool) DepositScript(seriesID uint32, branch Branch, index Index) ([]byte, error) { series := p.Series(seriesID) if series == nil { str := fmt.Sprintf("series #%d does not exist", seriesID) return nil, newError(ErrSeriesNotExists, str, nil) } pubKeys, err := branchOrder(series.publicKeys, branch) if err != nil { return nil, err } pks := make([]*dcrutil.AddressSecpPubKey, len(pubKeys)) for i, key := range pubKeys { child, err := key.Child(uint32(index)) // TODO: implement getting the next index until we find a valid one, // in case there is a hdkeychain.ErrInvalidChild. if err != nil { str := fmt.Sprintf("child #%d for this pubkey %d does not exist", index, i) return nil, newError(ErrKeyChain, str, err) } pubkey, err := child.ECPubKey() if err != nil { str := fmt.Sprintf("child #%d for this pubkey %d does not exist", index, i) return nil, newError(ErrKeyChain, str, err) } pks[i], err = dcrutil.NewAddressSecpPubKey(pubkey.SerializeCompressed(), p.manager.ChainParams()) if err != nil { str := fmt.Sprintf( "child #%d for this pubkey %d could not be converted to an address", index, i) return nil, newError(ErrKeyChain, str, err) } } script, err := txscript.MultiSigScript(pks, int(series.reqSigs)) if err != nil { str := fmt.Sprintf("error while making multisig script hash, %d", len(pks)) return nil, newError(ErrScriptCreation, str, err) } return script, nil }
// TestMultiSigScript ensures the MultiSigScript function returns the expected // scripts and errors. func TestMultiSigScript(t *testing.T) { t.Parallel() // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg p2pkCompressedMain, err := dcrutil.NewAddressSecpPubKey(decodeHex("02192d7"+ "4d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), &chaincfg.MainNetParams) if err != nil { t.Errorf("Unable to create pubkey address (compressed): %v", err) return } p2pkCompressed2Main, err := dcrutil.NewAddressSecpPubKey(decodeHex("03b0bd"+ "634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"), &chaincfg.MainNetParams) if err != nil { t.Errorf("Unable to create pubkey address (compressed 2): %v", err) return } p2pkUncompressedMain, err := dcrutil.NewAddressSecpPubKey(decodeHex("0411d"+ "b93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"+ "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b41"+ "2a3"), &chaincfg.MainNetParams) if err != nil { t.Errorf("Unable to create pubkey address (uncompressed): %v", err) return } tests := []struct { keys []*dcrutil.AddressSecpPubKey nrequired int 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, "", txscript.ErrBadNumRequired, }, { // By default compressed pubkeys are used in Decred. []*dcrutil.AddressSecpPubKey{ p2pkUncompressedMain, }, 1, "1 DATA_33 0x0311db93e1dcdb8a016b49840f8c53bc1eb68a3" + "82e97b1482ecad7b148a6909a5c 1 CHECKMULTISIG", nil, }, { []*dcrutil.AddressSecpPubKey{ p2pkUncompressedMain, }, 2, "", txscript.ErrBadNumRequired, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { script, err := txscript.MultiSigScript(test.keys, test.nrequired) if err != test.err { t.Errorf("MultiSigScript #%d unexpected error - "+ "got %v, want %v", i, err, test.err) continue } expected := mustParseShortForm(test.expected) if !bytes.Equal(script, expected) { t.Errorf("MultiSigScript #%d got: %x\nwant: %x", i, script, expected) continue } } }