Skip to content

Commit

Permalink
rpc: add signrawtransactionwithwallet interface
Browse files Browse the repository at this point in the history
Adds interface for issuing a signrawtransactionwithwallet command.
Note that this does not add functionality for the btcd rpc server
itself, it simply assumes that the RPC client has this ability and gives
an API for interacting with the RPC client.

rpc: add signrawtransactionwithwallet interface
  • Loading branch information
henryperson authored and jcvernaleo committed Oct 5, 2020
1 parent 0bf42f4 commit 584c382
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 1 deletion.
34 changes: 34 additions & 0 deletions btcjson/walletsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand Down Expand Up @@ -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)
Expand Down
97 changes: 97 additions & 0 deletions btcjson/walletsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions btcjson/walletsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
145 changes: 144 additions & 1 deletion rpcclient/rawtransactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 584c382

Please sign in to comment.