diff --git a/btcjson/walletsvrcmds.go b/btcjson/walletsvrcmds.go index b21fb187..c3ecc0c8 100644 --- a/btcjson/walletsvrcmds.go +++ b/btcjson/walletsvrcmds.go @@ -668,6 +668,39 @@ func NewSignRawTransactionCmd(hexEncodedTx string, inputs *[]RawTxInput, privKey } } +// RawTxWitnessInput models the data needed for raw transaction input that is used in +// the SignRawTransactionWithWalletCmd struct. The RedeemScript is required for P2SH inputs, +// the WitnessScript is required for P2WSH or P2SH-P2WSH witness scripts, and the Amount is +// required for Segwit inputs. Otherwise, those fields can be left blank. +type RawTxWitnessInput struct { + Txid string `json:"txid"` + Vout uint32 `json:"vout"` + ScriptPubKey string `json:"scriptPubKey"` + RedeemScript *string `json:"redeemScript,omitempty"` + WitnessScript *string `json:"witnessScript,omitempty"` + Amount *float64 `json:"amount,omitempty"` // In BTC +} + +// SignRawTransactionWithWalletCmd defines the signrawtransactionwithwallet JSON-RPC command. +type SignRawTransactionWithWalletCmd struct { + RawTx string + Inputs *[]RawTxWitnessInput + SigHashType *string `jsonrpcdefault:"\"ALL\""` +} + +// NewSignRawTransactionWithWalletCmd returns a new instance which can be used to issue a +// signrawtransactionwithwallet JSON-RPC command. +// +// The parameters which are pointers indicate they are optional. Passing nil +// for optional parameters will use the default value. +func NewSignRawTransactionWithWalletCmd(hexEncodedTx string, inputs *[]RawTxWitnessInput, sigHashType *string) *SignRawTransactionWithWalletCmd { + return &SignRawTransactionWithWalletCmd{ + RawTx: hexEncodedTx, + Inputs: inputs, + SigHashType: sigHashType, + } +} + // WalletLockCmd defines the walletlock JSON-RPC command. type WalletLockCmd struct{} @@ -1035,6 +1068,7 @@ func init() { MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags) MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags) MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags) + MustRegisterCmd("signrawtransactionwithwallet", (*SignRawTransactionWithWalletCmd)(nil), flags) MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags) MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags) MustRegisterCmd("walletpassphrasechange", (*WalletPassphraseChangeCmd)(nil), flags) diff --git a/btcjson/walletsvrcmds_test.go b/btcjson/walletsvrcmds_test.go index d19aa32a..38c7c3bd 100644 --- a/btcjson/walletsvrcmds_test.go +++ b/btcjson/walletsvrcmds_test.go @@ -1233,6 +1233,103 @@ func TestWalletSvrCmds(t *testing.T) { Flags: btcjson.String("ALL"), }, }, + { + name: "signrawtransactionwithwallet", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithwallet", "001122") + }, + staticCmd: func() interface{} { + return btcjson.NewSignRawTransactionWithWalletCmd("001122", nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithwallet","params":["001122"],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithWalletCmd{ + RawTx: "001122", + Inputs: nil, + SigHashType: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransactionwithwallet optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithwallet", "001122", `[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01","witnessScript":"02","amount":1.5}]`) + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + WitnessScript: btcjson.String("02"), + Amount: btcjson.Float64(1.5), + }, + } + + return btcjson.NewSignRawTransactionWithWalletCmd("001122", &txInputs, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithwallet","params":["001122",[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01","witnessScript":"02","amount":1.5}]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithWalletCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + WitnessScript: btcjson.String("02"), + Amount: btcjson.Float64(1.5), + }, + }, + SigHashType: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransactionwithwallet optional1 with blank fields in input", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithwallet", "001122", `[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]`) + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + }, + } + + return btcjson.NewSignRawTransactionWithWalletCmd("001122", &txInputs, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithwallet","params":["001122",[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithWalletCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + }, + }, + SigHashType: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransactionwithwallet optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithwallet", "001122", `[]`, "ALL") + }, + staticCmd: func() interface{} { + txInputs := []btcjson.RawTxWitnessInput{} + return btcjson.NewSignRawTransactionWithWalletCmd("001122", &txInputs, btcjson.String("ALL")) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithwallet","params":["001122",[],"ALL"],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithWalletCmd{ + RawTx: "001122", + Inputs: &[]btcjson.RawTxWitnessInput{}, + SigHashType: btcjson.String("ALL"), + }, + }, { name: "walletlock", newCmd: func() (interface{}, error) { diff --git a/btcjson/walletsvrresults.go b/btcjson/walletsvrresults.go index 2569dde4..e2ba201d 100644 --- a/btcjson/walletsvrresults.go +++ b/btcjson/walletsvrresults.go @@ -308,6 +308,14 @@ type SignRawTransactionResult struct { Errors []SignRawTransactionError `json:"errors,omitempty"` } +// SignRawTransactionWithWalletResult models the data from the +// signrawtransactionwithwallet command. +type SignRawTransactionWithWalletResult struct { + Hex string `json:"hex"` + Complete bool `json:"complete"` + Errors []SignRawTransactionError `json:"errors,omitempty"` +} + // ValidateAddressWalletResult models the data returned by the wallet server // validateaddress command. type ValidateAddressWalletResult struct { diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index 4e8d4e4d..35038ed9 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -555,7 +555,7 @@ func (c *Client) SignRawTransaction4Async(tx *wire.MsgTx, return c.sendCmd(cmd) } -// SignRawTransaction4 signs inputs for the passed transaction using the +// SignRawTransaction4 signs inputs for the passed transaction using // the specified signature hash type given the list of information about extra // input transactions and a potential list of private keys needed to perform // the signing process. The private keys, if specified, must be in wallet @@ -582,6 +582,149 @@ func (c *Client) SignRawTransaction4(tx *wire.MsgTx, hashType).Receive() } +// FutureSignRawTransactionWithWalletResult is a future promise to deliver +// the result of the SignRawTransactionWithWalletAsync RPC invocation (or +// an applicable error). +type FutureSignRawTransactionWithWalletResult chan *response + +// Receive waits for the response promised by the future and returns the +// signed transaction as well as whether or not all inputs are now signed. +func (r FutureSignRawTransactionWithWalletResult) Receive() (*wire.MsgTx, bool, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, false, err + } + + // Unmarshal as a signtransactionwithwallet result. + var signRawTxWithWalletResult btcjson.SignRawTransactionWithWalletResult + err = json.Unmarshal(res, &signRawTxWithWalletResult) + if err != nil { + return nil, false, err + } + + // Decode the serialized transaction hex to raw bytes. + serializedTx, err := hex.DecodeString(signRawTxWithWalletResult.Hex) + if err != nil { + return nil, false, err + } + + // Deserialize the transaction and return it. + var msgTx wire.MsgTx + if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil { + return nil, false, err + } + + return &msgTx, signRawTxWithWalletResult.Complete, nil +} + +// SignRawTransactionWithWalletAsync returns an instance of a type that can be used +// to get the result of the RPC at some future time by invoking the Receive function +// on the returned instance. +// +// See SignRawTransactionWithWallet for the blocking version and more details. +func (c *Client) SignRawTransactionWithWalletAsync(tx *wire.MsgTx) FutureSignRawTransactionWithWalletResult { + txHex := "" + if tx != nil { + // Serialize the transaction and convert to hex string. + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) + if err := tx.Serialize(buf); err != nil { + return newFutureError(err) + } + txHex = hex.EncodeToString(buf.Bytes()) + } + + cmd := btcjson.NewSignRawTransactionWithWalletCmd(txHex, nil, nil) + return c.sendCmd(cmd) +} + +// SignRawTransactionWithWallet signs inputs for the passed transaction and returns +// the signed transaction as well as whether or not all inputs are now signed. +// +// This function assumes the RPC server already knows the input transactions for the +// passed transaction which needs to be signed and uses the default signature hash +// type. Use one of the SignRawTransactionWithWallet# variants to specify that +// information if needed. +func (c *Client) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { + return c.SignRawTransactionWithWalletAsync(tx).Receive() +} + +// SignRawTransactionWithWallet2Async returns an instance of a type that can be +// used to get the result of the RPC at some future time by invoking the Receive +// function on the returned instance. +// +// See SignRawTransactionWithWallet2 for the blocking version and more details. +func (c *Client) SignRawTransactionWithWallet2Async(tx *wire.MsgTx, + inputs []btcjson.RawTxWitnessInput) FutureSignRawTransactionWithWalletResult { + + txHex := "" + if tx != nil { + // Serialize the transaction and convert to hex string. + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) + if err := tx.Serialize(buf); err != nil { + return newFutureError(err) + } + txHex = hex.EncodeToString(buf.Bytes()) + } + + cmd := btcjson.NewSignRawTransactionWithWalletCmd(txHex, &inputs, nil) + return c.sendCmd(cmd) +} + +// SignRawTransactionWithWallet2 signs inputs for the passed transaction given the +// list of information about the input transactions needed to perform the signing +// process. +// +// This only input transactions that need to be specified are ones the +// RPC server does not already know. Already known input transactions will be +// merged with the specified transactions. +// +// See SignRawTransactionWithWallet if the RPC server already knows the input +// transactions. +func (c *Client) SignRawTransactionWithWallet2(tx *wire.MsgTx, + inputs []btcjson.RawTxWitnessInput) (*wire.MsgTx, bool, error) { + + return c.SignRawTransactionWithWallet2Async(tx, inputs).Receive() +} + +// SignRawTransactionWithWallet3Async returns an instance of a type that can +// be used to get the result of the RPC at some future time by invoking the +// Receive function on the returned instance. +// +// See SignRawTransactionWithWallet3 for the blocking version and more details. +func (c *Client) SignRawTransactionWithWallet3Async(tx *wire.MsgTx, + inputs []btcjson.RawTxWitnessInput, hashType SigHashType) FutureSignRawTransactionWithWalletResult { + + txHex := "" + if tx != nil { + // Serialize the transaction and convert to hex string. + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) + if err := tx.Serialize(buf); err != nil { + return newFutureError(err) + } + txHex = hex.EncodeToString(buf.Bytes()) + } + + cmd := btcjson.NewSignRawTransactionWithWalletCmd(txHex, &inputs, btcjson.String(string(hashType))) + return c.sendCmd(cmd) +} + +// SignRawTransactionWithWallet3 signs inputs for the passed transaction using +// the specified signature hash type given the list of information about extra +// input transactions. +// +// The only input transactions that need to be specified are ones the RPC server +// does not already know. This means the list of transaction inputs can be nil +// if the RPC server already knows them all. +// +// This function should only used if a non-default signature hash type is +// desired. Otherwise, see SignRawTransactionWithWallet if the RPC server already +// knows the input transactions, or SignRawTransactionWihWallet2 if it does not. +func (c *Client) SignRawTransactionWithWallet3(tx *wire.MsgTx, + inputs []btcjson.RawTxWitnessInput, hashType SigHashType) (*wire.MsgTx, bool, error) { + + return c.SignRawTransactionWithWallet3Async(tx, inputs, hashType).Receive() +} + // FutureSearchRawTransactionsResult is a future promise to deliver the result // of the SearchRawTransactionsAsync RPC invocation (or an applicable error). type FutureSearchRawTransactionsResult chan *response