diff --git a/btcjson/walletsvrcmds.go b/btcjson/walletsvrcmds.go index 5983d3f783..1d5259d4f5 100644 --- a/btcjson/walletsvrcmds.go +++ b/btcjson/walletsvrcmds.go @@ -760,6 +760,28 @@ func NewSignRawTransactionWithWalletCmd(hexEncodedTx string, inputs *[]RawTxWitn } } +// SignRawTransactionWithKeyCmd defines the signrawtransactionwithkey JSON-RPC command. +type SignRawTransactionWithKeyCmd struct { + RawTx string + PrivKeys *[]string + Inputs *[]RawTxWitnessInput + SigHashType *string `jsonrpcdefault:"\"ALL\""` +} + +// NewSignRawTransactionWithKeyCmd returns a new instance which can be used to issue a +// signrawtransactionwithkey JSON-RPC command. +// +// The parameters which are pointers indicate they are optional. Passing nil +// for optional parameters will use the default value. +func NewSignRawTransactionWithKeyCmd(hexEncodedTx string, privKeys []string, inputs *[]RawTxWitnessInput, sigHashType *string) *SignRawTransactionWithKeyCmd { + return &SignRawTransactionWithKeyCmd{ + RawTx: hexEncodedTx, + PrivKeys: &privKeys, + Inputs: inputs, + SigHashType: sigHashType, + } +} + // WalletLockCmd defines the walletlock JSON-RPC command. type WalletLockCmd struct{} @@ -1131,6 +1153,7 @@ func init() { MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags) MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags) MustRegisterCmd("signrawtransactionwithwallet", (*SignRawTransactionWithWalletCmd)(nil), flags) + MustRegisterCmd("signrawtransactionwithkey", (*SignRawTransactionWithKeyCmd)(nil), flags) MustRegisterCmd("unloadwallet", (*UnloadWalletCmd)(nil), flags) MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags) MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags) diff --git a/btcjson/walletsvrcmds_test.go b/btcjson/walletsvrcmds_test.go index 0b5355e511..719aaf3800 100644 --- a/btcjson/walletsvrcmds_test.go +++ b/btcjson/walletsvrcmds_test.go @@ -1460,6 +1460,112 @@ func TestWalletSvrCmds(t *testing.T) { SigHashType: btcjson.String("ALL"), }, }, + { + name: "signrawtransactionwithkey", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithkey", "001122", `["abc"]`) + }, + staticCmd: func() interface{} { + privKeys := []string{"abc"} + return btcjson.NewSignRawTransactionWithKeyCmd("001122", privKeys, nil, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithkey","params":["001122",["abc"]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithKeyCmd{ + RawTx: "001122", + PrivKeys: &[]string{"abc"}, + Inputs: nil, + SigHashType: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransactionwithkey optional1", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithkey", "001122", `["abc"]`, `[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01","witnessScript":"02","amount":1.5}]`) + }, + staticCmd: func() interface{} { + privKeys := []string{"abc"} + txInputs := []btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + WitnessScript: btcjson.String("02"), + Amount: btcjson.Float64(1.5), + }, + } + + return btcjson.NewSignRawTransactionWithKeyCmd("001122", privKeys, &txInputs, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithkey","params":["001122",["abc"],[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01","witnessScript":"02","amount":1.5}]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithKeyCmd{ + RawTx: "001122", + PrivKeys: &[]string{"abc"}, + 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: "signrawtransactionwithkey optional1 with blank fields in input", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithkey", "001122", `["abc"]`, `[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]`) + }, + staticCmd: func() interface{} { + privKeys := []string{"abc"} + txInputs := []btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + }, + } + + return btcjson.NewSignRawTransactionWithKeyCmd("001122", privKeys, &txInputs, nil) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithkey","params":["001122",["abc"],[{"txid":"123","vout":1,"scriptPubKey":"00","redeemScript":"01"}]],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithKeyCmd{ + RawTx: "001122", + PrivKeys: &[]string{"abc"}, + Inputs: &[]btcjson.RawTxWitnessInput{ + { + Txid: "123", + Vout: 1, + ScriptPubKey: "00", + RedeemScript: btcjson.String("01"), + }, + }, + SigHashType: btcjson.String("ALL"), + }, + }, + { + name: "signrawtransactionwithkey optional2", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd("signrawtransactionwithkey", "001122", `["abc"]`, `[]`, "ALL") + }, + staticCmd: func() interface{} { + privKeys := []string{"abc"} + txInputs := []btcjson.RawTxWitnessInput{} + + return btcjson.NewSignRawTransactionWithKeyCmd("001122", privKeys, &txInputs, btcjson.String("ALL")) + }, + marshalled: `{"jsonrpc":"1.0","method":"signrawtransactionwithkey","params":["001122",["abc"],[],"ALL"],"id":1}`, + unmarshalled: &btcjson.SignRawTransactionWithKeyCmd{ + RawTx: "001122", + PrivKeys: &[]string{"abc"}, + Inputs: &[]btcjson.RawTxWitnessInput{}, + SigHashType: btcjson.String("ALL"), + }, + }, { name: "walletlock", newCmd: func() (interface{}, error) { diff --git a/btcjson/walletsvrresults.go b/btcjson/walletsvrresults.go index 78a6e647f5..ffba5b668e 100644 --- a/btcjson/walletsvrresults.go +++ b/btcjson/walletsvrresults.go @@ -322,6 +322,14 @@ type SignRawTransactionWithWalletResult struct { Errors []SignRawTransactionError `json:"errors,omitempty"` } +// SignRawTransactionWithKeyResult models the data from the +// signrawtransactionwithkey command. +type SignRawTransactionWithKeyResult 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 3643f2b0ca..e329f36567 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -10,9 +10,9 @@ import ( "encoding/json" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcd/btcutil" ) const ( @@ -725,6 +725,149 @@ func (c *Client) SignRawTransactionWithWallet3(tx *wire.MsgTx, return c.SignRawTransactionWithWallet3Async(tx, inputs, hashType).Receive() } +// FutureSignRawTransactionWithKeyResult is a future promise to deliver +// the result of the SignRawTransactionWithKeyAsync RPC invocation (or +// an applicable error). +type FutureSignRawTransactionWithKeyResult 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 FutureSignRawTransactionWithKeyResult) Receive() (*wire.MsgTx, bool, error) { + res, err := ReceiveFuture(r) + if err != nil { + return nil, false, err + } + + // Unmarshal as a signtransactionwithkey result. + var signRawTxWithKeyResult btcjson.SignRawTransactionWithKeyResult + err = json.Unmarshal(res, &signRawTxWithKeyResult) + if err != nil { + return nil, false, err + } + + // Decode the serialized transaction hex to raw bytes. + serializedTx, err := hex.DecodeString(signRawTxWithKeyResult.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, signRawTxWithKeyResult.Complete, nil +} + +// SignRawTransactionWithKeyAsync 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 SignRawTransactionWithKey for the blocking version and more details. +func (c *Client) SignRawTransactionWithKeyAsync(tx *wire.MsgTx, privKeysWIF []string) FutureSignRawTransactionWithKeyResult { + 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.NewSignRawTransactionWithKeyCmd(txHex, privKeysWIF, nil, nil) + return c.SendCmd(cmd) +} + +// SignRawTransactionWithKey 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 SignRawTransactionWithKey# variants to specify that +// information, if needed. +func (c *Client) SignRawTransactionWithKey(tx *wire.MsgTx, privKeysWIF []string) (*wire.MsgTx, bool, error) { + return c.SignRawTransactionWithKeyAsync(tx, privKeysWIF).Receive() +} + +// SignRawTransactionWithKey2Async 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 SignRawTransactionWithKey2 for the blocking version and more details. +func (c *Client) SignRawTransactionWithKey2Async(tx *wire.MsgTx, privKeysWIF []string, + inputs []btcjson.RawTxWitnessInput) FutureSignRawTransactionWithKeyResult { + + 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.NewSignRawTransactionWithKeyCmd(txHex, privKeysWIF, &inputs, nil) + return c.SendCmd(cmd) +} + +// SignRawTransactionWithKey2 signs inputs for the passed transaction given the +// list of information about the input transactions needed to perform the signing +// process. +// +// The only input transactions that need to be specified are the ones the +// RPC server does not already know. Already known input transactions will be +// merged with the specified transactions. +// +// See SignRawTransactionWithKey if the RPC server already knows the input +// transactions. +func (c *Client) SignRawTransactionWithKey2(tx *wire.MsgTx, privKeysWIF []string, + inputs []btcjson.RawTxWitnessInput) (*wire.MsgTx, bool, error) { + + return c.SignRawTransactionWithKey2Async(tx, privKeysWIF, inputs).Receive() +} + +// SignRawTransactionWithKey3Async 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 SignRawTransactionWithKey3 for the blocking version and more details. +func (c *Client) SignRawTransactionWithKey3Async(tx *wire.MsgTx, privKeysWIF []string, + inputs []btcjson.RawTxWitnessInput, hashType SigHashType) FutureSignRawTransactionWithKeyResult { + + 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.NewSignRawTransactionWithKeyCmd(txHex, privKeysWIF, &inputs, btcjson.String(string(hashType))) + return c.SendCmd(cmd) +} + +// SignRawTransactionWithKey3 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 the 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 be used if a non-default signature hash type is +// desired. Otherwise, see SignRawTransactionWithKey if the RPC server already +// knows the input transactions, or SignRawTransactionWithKey2 if it does not. +func (c *Client) SignRawTransactionWithKey3(tx *wire.MsgTx, privKeysWIF []string, + inputs []btcjson.RawTxWitnessInput, hashType SigHashType) (*wire.MsgTx, bool, error) { + + return c.SignRawTransactionWithKey3Async(tx, privKeysWIF, 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