Add support for the signrawtransaction command.

This commit is contained in:
Owain G. Ainsworth 2014-04-17 23:13:53 +01:00
parent 58ecb31710
commit 2762d58a83
2 changed files with 376 additions and 6 deletions

View file

@ -392,13 +392,22 @@ func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjs
return resultData, 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) {
// GetRawTransaction returns a future representing a pending GetRawTransaction
// command for txsha.. When the result of the request is required it may be
// collected with GetRawTRansactionAsyncResult.
func GetRawTransactionAsync(rpc ServerConn, txsha *btcwire.ShaHash) chan RawRPCResponse {
// NewGetRawTransactionCmd cannot fail with no optargs.
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
_, jsonErr := response.FinishUnmarshal(&resultData)
@ -416,6 +425,14 @@ func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *bt
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
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {

View file

@ -18,9 +18,12 @@ package main
import (
"bytes"
"crypto/ecdsa"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/conformal/btcec"
"github.com/conformal/btcjson"
"github.com/conformal/btcscript"
@ -60,6 +63,7 @@ var rpcHandlers = map[string]cmdHandler{
"sendtoaddress": SendToAddress,
"settxfee": SetTxFee,
"signmessage": SignMessage,
"signrawtransaction": SignRawTransaction,
"validateaddress": ValidateAddress,
"verifymessage": VerifyMessage,
"walletlock": WalletLock,
@ -82,7 +86,6 @@ var rpcHandlers = map[string]cmdHandler{
"lockunspent": Unimplemented,
"move": Unimplemented,
"setaccount": Unimplemented,
"signrawtransaction": Unimplemented,
"stop": Unimplemented,
// 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?
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
}
@ -1726,6 +1735,350 @@ func RecoverAddresses(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
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.
func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)