Add support for the signrawtransaction command.
This commit is contained in:
parent
58ecb31710
commit
2762d58a83
2 changed files with 376 additions and 6 deletions
27
rpcclient.go
27
rpcclient.go
|
@ -392,13 +392,22 @@ func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjs
|
||||||
return resultData, nil
|
return resultData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawTransaction sends the non-verbose version of a getrawtransaction
|
// GetRawTransaction returns a future representing a pending GetRawTransaction
|
||||||
// request to receive the serialized transaction referenced by txsha. If
|
// command for txsha.. When the result of the request is required it may be
|
||||||
// successful, the transaction is decoded and returned as a btcutil.Tx.
|
// collected with GetRawTRansactionAsyncResult.
|
||||||
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
|
func GetRawTransactionAsync(rpc ServerConn, txsha *btcwire.ShaHash) chan RawRPCResponse {
|
||||||
// NewGetRawTransactionCmd cannot fail with no optargs.
|
// NewGetRawTransactionCmd cannot fail with no optargs.
|
||||||
cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
|
cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
|
||||||
response := <-rpc.SendRequest(NewServerRequest(cmd))
|
|
||||||
|
return rpc.SendRequest(NewServerRequest(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawTransactionAsyncResult waits for the pending command in request -
|
||||||
|
// the reqsult of a previous GetRawTransactionAsync() call - and returns either
|
||||||
|
// the requested transaction, or an error.
|
||||||
|
func GetRawTransactionAsyncResult(request chan RawRPCResponse) (*btcutil.Tx,
|
||||||
|
*btcjson.Error) {
|
||||||
|
response := <-request
|
||||||
|
|
||||||
var resultData string
|
var resultData string
|
||||||
_, jsonErr := response.FinishUnmarshal(&resultData)
|
_, jsonErr := response.FinishUnmarshal(&resultData)
|
||||||
|
@ -416,6 +425,14 @@ func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *bt
|
||||||
return utx, nil
|
return utx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRawTransaction sends the non-verbose version of a getrawtransaction
|
||||||
|
// request to receive the serialized transaction referenced by txsha. If
|
||||||
|
// successful, the transaction is decoded and returned as a btcutil.Tx.
|
||||||
|
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
|
||||||
|
resp := GetRawTransactionAsync(rpc, txsha)
|
||||||
|
return GetRawTransactionAsyncResult(resp)
|
||||||
|
}
|
||||||
|
|
||||||
// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
|
// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
|
||||||
// request to receive details about a transaction.
|
// request to receive details about a transaction.
|
||||||
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
|
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
|
||||||
|
|
355
rpcserver.go
355
rpcserver.go
|
@ -18,9 +18,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/conformal/btcec"
|
"github.com/conformal/btcec"
|
||||||
"github.com/conformal/btcjson"
|
"github.com/conformal/btcjson"
|
||||||
"github.com/conformal/btcscript"
|
"github.com/conformal/btcscript"
|
||||||
|
@ -60,6 +63,7 @@ var rpcHandlers = map[string]cmdHandler{
|
||||||
"sendtoaddress": SendToAddress,
|
"sendtoaddress": SendToAddress,
|
||||||
"settxfee": SetTxFee,
|
"settxfee": SetTxFee,
|
||||||
"signmessage": SignMessage,
|
"signmessage": SignMessage,
|
||||||
|
"signrawtransaction": SignRawTransaction,
|
||||||
"validateaddress": ValidateAddress,
|
"validateaddress": ValidateAddress,
|
||||||
"verifymessage": VerifyMessage,
|
"verifymessage": VerifyMessage,
|
||||||
"walletlock": WalletLock,
|
"walletlock": WalletLock,
|
||||||
|
@ -82,7 +86,6 @@ var rpcHandlers = map[string]cmdHandler{
|
||||||
"lockunspent": Unimplemented,
|
"lockunspent": Unimplemented,
|
||||||
"move": Unimplemented,
|
"move": Unimplemented,
|
||||||
"setaccount": Unimplemented,
|
"setaccount": Unimplemented,
|
||||||
"signrawtransaction": Unimplemented,
|
|
||||||
"stop": Unimplemented,
|
"stop": Unimplemented,
|
||||||
|
|
||||||
// Standard bitcoind methods which won't be implemented by btcwallet.
|
// Standard bitcoind methods which won't be implemented by btcwallet.
|
||||||
|
@ -358,6 +361,12 @@ func AddMultiSigAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
|
|
||||||
// TODO(oga) blockstamp current block?
|
// TODO(oga) blockstamp current block?
|
||||||
address, err := acct.ImportScript(script, &wallet.BlockStamp{})
|
address, err := acct.ImportScript(script, &wallet.BlockStamp{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrWallet.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return address.EncodeAddress(), nil
|
return address.EncodeAddress(), nil
|
||||||
}
|
}
|
||||||
|
@ -1726,6 +1735,350 @@ func RecoverAddresses(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pendingTx is used for async fetching of transaction dependancies in
|
||||||
|
// SignRawTransaction.
|
||||||
|
type pendingTx struct {
|
||||||
|
resp chan RawRPCResponse
|
||||||
|
inputs []uint32 // list of inputs that care about this tx.
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyInfo is used to store provided keys in SignRawTransaction.
|
||||||
|
type keyInfo struct {
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
compressed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRawTransaction handles the signrawtransaction command.
|
||||||
|
func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
|
cmd, ok := icmd.(*btcjson.SignRawTransactionCmd)
|
||||||
|
if !ok {
|
||||||
|
return nil, &btcjson.ErrInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedTx, err := hex.DecodeString(cmd.RawTx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.ErrDecodeHexString
|
||||||
|
}
|
||||||
|
msgTx := btcwire.NewMsgTx()
|
||||||
|
err = msgTx.Deserialize(bytes.NewBuffer(serializedTx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: "TX decode failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First we add the stuff we have been given.
|
||||||
|
// TODO(oga) really we probably should look these up with btcd anyway
|
||||||
|
// to make sure that they match the blockchain if present.
|
||||||
|
inputs := make(map[btcwire.OutPoint][]byte)
|
||||||
|
scripts := make(map[string][]byte)
|
||||||
|
for _, rti := range cmd.Inputs {
|
||||||
|
inputSha, err := btcwire.NewShaHashFromStr(rti.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := hex.DecodeString(rti.ScriptPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// redeemScript is only actually used iff the user provided
|
||||||
|
// private keys. In which case, it is used to get the scripts
|
||||||
|
// for signing. If the user did not provide keys then we always
|
||||||
|
// get scripts from the wallet.
|
||||||
|
// Empty strings are ok for this one and hex.DecodeString will
|
||||||
|
// DTRT.
|
||||||
|
if len(cmd.PrivKeys) != 0 {
|
||||||
|
redeemScript, err := hex.DecodeString(rti.RedeemScript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := btcutil.NewAddressScriptHash(redeemScript,
|
||||||
|
cfg.Net())
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scripts[addr.String()] = redeemScript
|
||||||
|
}
|
||||||
|
inputs[btcwire.OutPoint{
|
||||||
|
Hash: *inputSha,
|
||||||
|
Index: uint32(rti.Vout),
|
||||||
|
}] = script
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we go and look for any inputs that we were not provided by
|
||||||
|
// querying btcd with getrawtransaction. We queue up a bunch of async
|
||||||
|
// requests and will wait for replies after we have checked the rest of
|
||||||
|
// the arguments.
|
||||||
|
requested := make(map[btcwire.ShaHash]*pendingTx)
|
||||||
|
for _, txIn := range msgTx.TxIn {
|
||||||
|
// Did we get this txin from the arguments?
|
||||||
|
if _, ok := inputs[txIn.PreviousOutpoint]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we already fetching this tx? If so mark us as interested
|
||||||
|
// in this outpoint. (N.B. that any *sane* tx will only
|
||||||
|
// reference each outpoint once, since anything else is a double
|
||||||
|
// spend. We don't check this ourselves to save having to scan
|
||||||
|
// the array, it will fail later if so).
|
||||||
|
if ptx, ok := requested[txIn.PreviousOutpoint.Hash]; ok {
|
||||||
|
ptx.inputs = append(ptx.inputs,
|
||||||
|
txIn.PreviousOutpoint.Index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never heard of this one before, request it.
|
||||||
|
requested[txIn.PreviousOutpoint.Hash] = &pendingTx{
|
||||||
|
resp: GetRawTransactionAsync(CurrentServerConn(),
|
||||||
|
&txIn.PreviousOutpoint.Hash),
|
||||||
|
inputs: []uint32{txIn.PreviousOutpoint.Index},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse list of private keys, if present. If there are any keys here
|
||||||
|
// they are the keys that we may use for signing. If empty we will
|
||||||
|
// use any keys known to us already.
|
||||||
|
var keys map[string]keyInfo
|
||||||
|
if len(cmd.PrivKeys) != 0 {
|
||||||
|
keys = make(map[string]keyInfo)
|
||||||
|
|
||||||
|
for _, key := range cmd.PrivKeys {
|
||||||
|
key, net, compressed, err :=
|
||||||
|
btcutil.DecodePrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if net != cfg.Net() {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: "key network doesn't match " +
|
||||||
|
"wallet's",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
privk, pubk := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||||
|
key)
|
||||||
|
|
||||||
|
var addr btcutil.Address
|
||||||
|
if compressed {
|
||||||
|
pkc := pubk.SerializeCompressed()
|
||||||
|
addr, err = btcutil.NewAddressPubKey(pkc,
|
||||||
|
cfg.Net())
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pku := pubk.SerializeUncompressed()
|
||||||
|
addr, err = btcutil.NewAddressPubKey(pku,
|
||||||
|
cfg.Net())
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrDeserialization.Code,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys[addr.EncodeAddress()] = keyInfo{
|
||||||
|
key: privk,
|
||||||
|
compressed: compressed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hashType := btcscript.SigHashAll
|
||||||
|
if cmd.Flags != "" {
|
||||||
|
switch cmd.Flags {
|
||||||
|
case "ALL":
|
||||||
|
hashType = btcscript.SigHashAll
|
||||||
|
case "NONE":
|
||||||
|
hashType = btcscript.SigHashNone
|
||||||
|
case "SINGLE":
|
||||||
|
hashType = btcscript.SigHashSingle
|
||||||
|
case "ALL|ANYONECANPAY":
|
||||||
|
hashType = btcscript.SigHashAll |
|
||||||
|
btcscript.SigHashAnyOneCanPay
|
||||||
|
case "NONE|ANYONECANPAY":
|
||||||
|
hashType = btcscript.SigHashNone |
|
||||||
|
btcscript.SigHashAnyOneCanPay
|
||||||
|
case "SINGLE|ANYONECANPAY":
|
||||||
|
hashType = btcscript.SigHashSingle |
|
||||||
|
btcscript.SigHashAnyOneCanPay
|
||||||
|
default:
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrInvalidParameter.Code,
|
||||||
|
Message: "Invalid sighash parameter",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have checked the rest of the args. now we can collect the async
|
||||||
|
// txs. TODO(oga) If we don't mind the possibility of wasting work we
|
||||||
|
// could move waiting to the following loop and be slightly more
|
||||||
|
// asynchronous.
|
||||||
|
for txid, ptx := range requested {
|
||||||
|
tx, err := GetRawTransactionAsyncResult(ptx.resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, input := range ptx.inputs {
|
||||||
|
if input >= uint32(len(tx.MsgTx().TxOut)) {
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrInvalidParameter.Code,
|
||||||
|
Message: fmt.Sprintf("input %s:%d "+
|
||||||
|
"is not in tx", txid.String(),
|
||||||
|
input),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs[btcwire.OutPoint{
|
||||||
|
Hash: txid,
|
||||||
|
Index: input,
|
||||||
|
}] = tx.MsgTx().TxOut[input].PkScript
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All args collected. Now we can sign all the inputs that we can.
|
||||||
|
// `complete' denotes that we successfully signed all outputs and that
|
||||||
|
// all scripts will run to completion. This is returned as part of the
|
||||||
|
// reply.
|
||||||
|
complete := true
|
||||||
|
for i, txIn := range msgTx.TxIn {
|
||||||
|
input, ok := inputs[txIn.PreviousOutpoint]
|
||||||
|
if !ok {
|
||||||
|
// failure to find previous is actually an error since
|
||||||
|
// we failed above if we don't have all the inputs.
|
||||||
|
return nil, &btcjson.Error{
|
||||||
|
Code: btcjson.ErrWallet.Code,
|
||||||
|
Message: fmt.Sprintf("%s:%d not found",
|
||||||
|
txIn.PreviousOutpoint.Hash,
|
||||||
|
txIn.PreviousOutpoint.Index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up our callbacks that we pass to btcscript so it can
|
||||||
|
// look up the appropriate keys and scripts by address.
|
||||||
|
getKey := btcscript.KeyClosure(func(addr btcutil.Address) (
|
||||||
|
*ecdsa.PrivateKey, bool, error) {
|
||||||
|
if len(keys) != 0 {
|
||||||
|
info, ok := keys[addr.EncodeAddress()]
|
||||||
|
if !ok {
|
||||||
|
return nil, false,
|
||||||
|
errors.New("no key for address")
|
||||||
|
}
|
||||||
|
return info.key, info.compressed, nil
|
||||||
|
}
|
||||||
|
address, err := AcctMgr.Address(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pka, ok := address.(wallet.PubKeyAddress)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, errors.New("address is not " +
|
||||||
|
"a pubkey address")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := pka.PrivKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, pka.Compressed(), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
getScript := btcscript.ScriptClosure(func(
|
||||||
|
addr btcutil.Address) ([]byte, error) {
|
||||||
|
// If keys were provided then we can only use the
|
||||||
|
// scripts provided with our inputs, too.
|
||||||
|
if len(keys) != 0 {
|
||||||
|
script, ok := scripts[addr.EncodeAddress()]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no script for " +
|
||||||
|
"address")
|
||||||
|
}
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
address, err := AcctMgr.Address(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sa, ok := address.(wallet.ScriptAddress)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("addres is not a script" +
|
||||||
|
" address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(oga) we could possible speed things up further
|
||||||
|
// by returning the addresses, class and nrequired here
|
||||||
|
// thus avoiding recomputing them.
|
||||||
|
return sa.Script(), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// SigHashSingle inputs can only be signed if there's a
|
||||||
|
// corresponding output. However this could be already signed,
|
||||||
|
// so we always verify the output.
|
||||||
|
if (hashType & btcscript.SigHashSingle) == 0 ||
|
||||||
|
i < len(msgTx.TxOut) {
|
||||||
|
|
||||||
|
script, err := btcscript.SignTxOutput(cfg.Net(),
|
||||||
|
msgTx, i, input, byte(hashType), getKey,
|
||||||
|
getScript, txIn.SignatureScript)
|
||||||
|
// Failure to sign isn't an error, it just means that
|
||||||
|
// the tx isn't complete.
|
||||||
|
if err != nil {
|
||||||
|
complete = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txIn.SignatureScript = script
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either it was already signed or we just signed it.
|
||||||
|
// Find out if it is completely satisfied or still needs more.
|
||||||
|
engine, err := btcscript.NewScript(txIn.SignatureScript, input,
|
||||||
|
i, msgTx, btcscript.ScriptBip16|
|
||||||
|
btcscript.ScriptCanonicalSignatures)
|
||||||
|
if err != nil || engine.Execute() != nil {
|
||||||
|
complete = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Grow(msgTx.SerializeSize())
|
||||||
|
|
||||||
|
// Buffer is the right size, this should never fail so no need to
|
||||||
|
// come up with some synthetic error code for it.
|
||||||
|
_ = msgTx.Serialize(buf)
|
||||||
|
|
||||||
|
return btcjson.SignRawTransactionResult{
|
||||||
|
Hex: hex.EncodeToString(buf.Bytes()),
|
||||||
|
Complete: complete,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAddress handles the validateaddress command.
|
// ValidateAddress handles the validateaddress command.
|
||||||
func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
||||||
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
|
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
|
||||||
|
|
Loading…
Reference in a new issue