From 4495a523d8d5f1241f5cb5a6741e48b6e2edd85b Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Tue, 27 May 2014 12:50:51 -0500 Subject: [PATCH] Updates for btcutil and btcscript's btcnet conversion. --- createtx.go | 2 +- ntfns.go | 3 +- rpcserver.go | 34 +++++++++++----------- txstore/json.go | 8 ++--- txstore/tx.go | 2 +- txstore/tx_test.go | 23 ++++++++------- wallet/wallet.go | 68 ++++++++++++++++++++++++++++++++++++------- wallet/wallet_test.go | 28 +++++++----------- 8 files changed, 106 insertions(+), 62 deletions(-) diff --git a/createtx.go b/createtx.go index f04dab9..8b4d6bf 100644 --- a/createtx.go +++ b/createtx.go @@ -154,7 +154,7 @@ func (a *Account) txToPairs(pairs map[string]btcutil.Amount, // Add outputs to new tx. for addrStr, amt := range pairs { - addr, err := btcutil.DecodeAddress(addrStr, activeNet.Net) + addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } diff --git a/ntfns.go b/ntfns.go index a0e6acf..874f0e5 100644 --- a/ntfns.go +++ b/ntfns.go @@ -108,7 +108,8 @@ func NtfnRecvTx(n btcjson.Cmd) error { // and record the received txout. for outIdx, txout := range tx.MsgTx().TxOut { var accounts []*Account - _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, activeNet.Net) + _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, + activeNet.Params) for _, addr := range addrs { a, err := AcctMgr.AccountByAddress(addr) if err != nil { diff --git a/rpcserver.go b/rpcserver.go index ee4e133..7cd8306 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -277,7 +277,7 @@ func makeMultiSigScript(keys []string, nRequired int) ([]byte, *btcjson.Error) { // mixture of the two. for i, a := range keys { // try to parse as pubkey address - a, err := btcutil.DecodeAddress(a, activeNet.Net) + a, err := btcutil.DecodeAddress(a, activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrParse.Code, @@ -301,7 +301,7 @@ func makeMultiSigScript(keys []string, nRequired int) ([]byte, *btcjson.Error) { // This will be an addresspubkey a, err := btcutil.DecodeAddress(apkinfo.ExportPubKey(), - activeNet.Net) + activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrParse.Code, @@ -384,7 +384,7 @@ func CreateMultiSig(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, jsonerr } - address, err := btcutil.NewAddressScriptHash(script, activeNet.Net) + address, err := btcutil.NewAddressScriptHash(script, activeNet.Params) if err != nil { // above is a valid script, shouldn't happen. return nil, &btcjson.Error{ @@ -409,7 +409,7 @@ func DumpPrivKey(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &btcjson.ErrInternal } - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } @@ -616,11 +616,11 @@ func GetAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } // Is address valid? - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } - if !addr.IsForNet(activeNet.Net) { + if !addr.IsForNet(activeNet.Params) { return nil, &btcjson.ErrInvalidAddressOrKey } @@ -694,7 +694,7 @@ func GetAddressBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } // Is address valid? - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } @@ -1220,12 +1220,12 @@ func ListAddressTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Decode addresses. pkHashMap := make(map[string]struct{}) for _, addrStr := range cmd.Addresses { - addr, err := btcutil.DecodeAddress(addrStr, activeNet.Net) + addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } apkh, ok := addr.(*btcutil.AddressPubKeyHash) - if !ok || !apkh.IsForNet(activeNet.Net) { + if !ok || !apkh.IsForNet(activeNet.Params) { return nil, &btcjson.ErrInvalidAddressOrKey } pkHashMap[string(addr.ScriptAddress())] = struct{}{} @@ -1300,7 +1300,7 @@ func ListUnspent(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { if len(cmd.Addresses) != 0 { // confirm that all of them are good: for _, as := range cmd.Addresses { - a, err := btcutil.DecodeAddress(as, activeNet.Net) + a, err := btcutil.DecodeAddress(as, activeNet.Params) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } @@ -1635,7 +1635,7 @@ func SignMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &btcjson.ErrInternal } - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrParse.Code, @@ -1800,7 +1800,7 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } addr, err := btcutil.NewAddressScriptHash(redeemScript, - activeNet.Net) + activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrDeserialization.Code, @@ -1861,7 +1861,7 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } } - if !wif.IsForNet(activeNet.Net) { + if !wif.IsForNet(activeNet.Params) { return nil, &btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "key network doesn't match " + @@ -1870,7 +1870,7 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } addr, err := btcutil.NewAddressPubKey(wif.SerializePubKey(), - activeNet.Net) + activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrDeserialization.Code, @@ -2017,7 +2017,7 @@ func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { if (hashType&btcscript.SigHashSingle) != btcscript.SigHashSingle || i < len(msgTx.TxOut) { - script, err := btcscript.SignTxOutput(activeNet.Net, + script, err := btcscript.SignTxOutput(activeNet.Params, msgTx, i, input, byte(hashType), getKey, getScript, txIn.SignatureScript) // Failure to sign isn't an error, it just means that @@ -2060,7 +2060,7 @@ func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { } result := btcjson.ValidateAddressResult{} - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return result, nil } @@ -2116,7 +2116,7 @@ func VerifyMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { return nil, &btcjson.ErrInternal } - addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Net) + addr, err := btcutil.DecodeAddress(cmd.Address, activeNet.Params) if err != nil { return nil, &btcjson.Error{ Code: btcjson.ErrParse.Code, diff --git a/txstore/json.go b/txstore/json.go index 179a8e1..666b8ac 100644 --- a/txstore/json.go +++ b/txstore/json.go @@ -19,15 +19,15 @@ package txstore import ( "github.com/conformal/btcchain" "github.com/conformal/btcjson" + "github.com/conformal/btcnet" "github.com/conformal/btcscript" "github.com/conformal/btcutil" - "github.com/conformal/btcwire" ) // ToJSON returns a slice of btcjson listtransaction result types for all credits // and debits of this transaction. func (t *TxRecord) ToJSON(account string, chainHeight int32, - net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) { + net *btcnet.Params) ([]btcjson.ListTransactionsResult, error) { var results []btcjson.ListTransactionsResult if d := t.Debits(); d != nil { @@ -50,7 +50,7 @@ func (t *TxRecord) ToJSON(account string, chainHeight int32, // ToJSON returns a slice of objects that may be marshaled as a JSON array // of JSON objects for a listtransactions RPC reply. func (d *Debits) ToJSON(account string, chainHeight int32, - net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) { + net *btcnet.Params) ([]btcjson.ListTransactionsResult, error) { msgTx := d.Tx().MsgTx() reply := make([]btcjson.ListTransactionsResult, 0, len(msgTx.TxOut)) @@ -93,7 +93,7 @@ func (d *Debits) ToJSON(account string, chainHeight int32, // ToJSON returns a slice of objects that may be marshaled as a JSON array // of JSON objects for a listtransactions RPC reply. func (c *Credit) ToJSON(account string, chainHeight int32, - net btcwire.BitcoinNet) (btcjson.ListTransactionsResult, error) { + net *btcnet.Params) (btcjson.ListTransactionsResult, error) { msgTx := c.Tx().MsgTx() txout := msgTx.TxOut[c.OutputIndex] diff --git a/txstore/tx.go b/txstore/tx.go index 43e6ccb..c2e9447 100644 --- a/txstore/tx.go +++ b/txstore/tx.go @@ -1395,7 +1395,7 @@ func (c *Credit) Addresses(net *btcnet.Params) (btcscript.ScriptClass, msgTx := c.Tx().MsgTx() pkScript := msgTx.TxOut[c.OutputIndex].PkScript - return btcscript.ExtractPkScriptAddrs(pkScript, net.Net) + return btcscript.ExtractPkScriptAddrs(pkScript, net) } // Change returns whether the credit is the result of a change output. diff --git a/txstore/tx_test.go b/txstore/tx_test.go index 1623aff..cc4ede5 100644 --- a/txstore/tx_test.go +++ b/txstore/tx_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + "github.com/conformal/btcnet" "github.com/conformal/btcutil" . "github.com/conformal/btcwallet/txstore" "github.com/conformal/btcwire" @@ -108,7 +109,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { } // Verify that we can create the JSON output without any // errors. - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -134,7 +135,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -161,7 +162,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -188,7 +189,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -231,7 +232,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err @@ -263,7 +264,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -294,7 +295,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -320,7 +321,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -348,7 +349,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -373,7 +374,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } @@ -456,7 +457,7 @@ func TestInsertsCreditsDebitsRollbacks(t *testing.T) { return nil, err } - _, err = r.ToJSON("", 100, btcwire.MainNet) + _, err = r.ToJSON("", 100, &btcnet.MainNetParams) if err != nil { return nil, err } diff --git a/wallet/wallet.go b/wallet/wallet.go index 265bd71..e3de5a2 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -485,6 +485,45 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) { } } +// Wallet uses a custom network parameters type so it can be an io.ReaderFrom. +// Due to the way and order that wallets are currently serialized and how +// address reading requires the wallet's network parameters, setting and +// erroring on unknown wallet networks must happen on the read itself and not +// after the fact. This is admitidly a hack, but with a bip32 keystore on the +// horizon I'm not too motivated to clean this up. +type netParams btcnet.Params + +func (net *netParams) ReadFrom(r io.Reader) (int64, error) { + var buf [4]byte + uint32Bytes := buf[:4] + + n, err := io.ReadFull(r, uint32Bytes) + n64 := int64(n) + if err != nil { + return n64, err + } + + switch btcwire.BitcoinNet(binary.LittleEndian.Uint32(uint32Bytes)) { + case btcwire.MainNet: + *net = *(*netParams)(&btcnet.MainNetParams) + case btcwire.TestNet3: + *net = *(*netParams)(&btcnet.TestNet3Params) + default: + return n64, errors.New("unknown network") + } + return n64, nil +} + +func (net *netParams) WriteTo(w io.Writer) (int64, error) { + var buf [4]byte + uint32Bytes := buf[:4] + + binary.LittleEndian.PutUint32(uint32Bytes, uint32(net.Net)) + n, err := w.Write(uint32Bytes) + n64 := int64(n) + return n64, err +} + // Stringified byte slices for use as map lookup keys. type addressKey string type transactionHashKey string @@ -500,7 +539,7 @@ func getAddressKey(addr btcutil.Address) addressKey { // write to any type of byte streams, including files. type Wallet struct { vers version - net btcwire.BitcoinNet + net *netParams flags walletFlags createDate int64 name [32]byte @@ -560,7 +599,7 @@ func NewWallet(name, desc string, passphrase []byte, net *btcnet.Params, // Create and fill wallet. w := &Wallet{ vers: VersCurrent, - net: net.Net, + net: (*netParams)(net), flags: walletFlags{ useEncryption: true, watchingOnly: false, @@ -638,6 +677,7 @@ func (w *Wallet) Name() string { func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { var read int64 + w.net = &netParams{} w.addrMap = make(map[addressKey]walletAddress) w.addrCommentMap = make(map[addressKey]comment) w.chainIdxMap = make(map[int64]btcutil.Address) @@ -653,7 +693,7 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { datas := []interface{}{ &id, &w.vers, - &w.net, + w.net, &w.flags, make([]byte, 6), // Bytes for Armory unique ID &w.createDate, @@ -727,7 +767,7 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) { w.importedAddrs = append(w.importedAddrs, &e.script) case *addrCommentEntry: - addr, err := e.address(w.net) + addr, err := e.address(w.Net()) if err != nil { return 0, err } @@ -802,7 +842,7 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) { datas := []interface{}{ &fileID, &VersCurrent, - &w.net, + w.net, &w.flags, make([]byte, 6), // Bytes for Armory unique ID &w.createDate, @@ -1195,9 +1235,9 @@ func (w *Wallet) Address(a btcutil.Address) (WalletAddress, error) { return btcaddr, nil } -// Net returns the bitcoin network identifier for this wallet. -func (w *Wallet) Net() btcwire.BitcoinNet { - return w.net +// Net returns the bitcoin network parameters for this wallet. +func (w *Wallet) Net() *btcnet.Params { + return (*btcnet.Params)(w.net) } // SetSyncStatus sets the sync status for a single wallet address. This @@ -2530,7 +2570,15 @@ func (a *btcAddress) ExportPrivKey() (*btcutil.WIF, error) { if err != nil { return nil, err } - return btcutil.NewWIF((*btcec.PrivateKey)(pk), a.wallet.Net(), a.Compressed()) + // NewWIF only errors if the network is nil. In this case, panic, + // as our program's assumptions are so broken that this needs to be + // caught immediately, and a stack trace here is more useful than + // elsewhere. + wif, err := btcutil.NewWIF((*btcec.PrivateKey)(pk), a.wallet.Net(), a.Compressed()) + if err != nil { + panic(err) + } + return wif, nil } // watchingCopy creates a copy of an address without a private key. @@ -3168,7 +3216,7 @@ type addrCommentEntry struct { comment []byte } -func (e *addrCommentEntry) address(net btcwire.BitcoinNet) (*btcutil.AddressPubKeyHash, error) { +func (e *addrCommentEntry) address(net *btcnet.Params) (*btcutil.AddressPubKeyHash, error) { return btcutil.NewAddressPubKeyHash(e.pubKeyHash160[:], net) } diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index e4315b8..02f20ba 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -32,10 +32,10 @@ import ( "github.com/davecgh/go-spew/spew" ) -var netParams = &btcnet.MainNetParams +var tstNetParams = &btcnet.MainNetParams func TestBtcAddressSerializer(t *testing.T) { - fakeWallet := &Wallet{net: netParams.Net} + fakeWallet := &Wallet{net: (*netParams)(tstNetParams)} kdfp := &kdfParameters{ mem: 1024, nIter: 5, @@ -88,7 +88,7 @@ func TestBtcAddressSerializer(t *testing.T) { } func TestScriptAddressSerializer(t *testing.T) { - fakeWallet := &Wallet{net: netParams.Net} + fakeWallet := &Wallet{net: (*netParams)(tstNetParams)} script := []byte{btcscript.OP_TRUE, btcscript.OP_DUP, btcscript.OP_DROP} addr, err := newScriptAddress(fakeWallet, script, &BlockStamp{}) @@ -120,7 +120,7 @@ func TestScriptAddressSerializer(t *testing.T) { func TestWalletCreationSerialization(t *testing.T) { createdAt := &BlockStamp{} w1, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, createdAt, 100) + []byte("banana"), tstNetParams, createdAt, 100) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return @@ -332,7 +332,7 @@ func TestWalletPubkeyChaining(t *testing.T) { const keypoolSize = 5 w, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, &BlockStamp{}, keypoolSize) + []byte("banana"), tstNetParams, &BlockStamp{}, keypoolSize) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return @@ -508,7 +508,7 @@ func TestWatchingWalletExport(t *testing.T) { const keypoolSize = 10 createdAt := &BlockStamp{} w, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, createdAt, keypoolSize) + []byte("banana"), tstNetParams, createdAt, keypoolSize) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return @@ -717,10 +717,7 @@ func TestWatchingWalletExport(t *testing.T) { return } pk, _ := btcec.PrivKeyFromBytes(btcec.S256(), make([]byte, 32)) - wif, err := btcutil.NewWIF(pk, netParams.Net, true) - if err != nil { - t.Fatal(err) - } + wif := btcutil.NewWIF(pk, tstNetParams, true) if _, err := ww.ImportPrivateKey(wif, createdAt); err != ErrWalletIsWatchingOnly { t.Errorf("Nonsensical func ImportPrivateKey returned no or incorrect error: %v", err) return @@ -732,7 +729,7 @@ func TestImportPrivateKey(t *testing.T) { createHeight := int32(100) createdAt := &BlockStamp{Height: createHeight} w, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, createdAt, keypoolSize) + []byte("banana"), tstNetParams, createdAt, keypoolSize) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return @@ -761,10 +758,7 @@ func TestImportPrivateKey(t *testing.T) { } // import priv key - wif, err := btcutil.NewWIF((*btcec.PrivateKey)(pk), netParams.Net, false) - if err != nil { - t.Fatal(err) - } + wif := btcutil.NewWIF((*btcec.PrivateKey)(pk), tstNetParams, false) importHeight := int32(50) importedAt := &BlockStamp{Height: importHeight} address, err := w.ImportPrivateKey(wif, importedAt) @@ -927,7 +921,7 @@ func TestImportScript(t *testing.T) { createHeight := int32(100) createdAt := &BlockStamp{Height: createHeight} w, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, createdAt, keypoolSize) + []byte("banana"), tstNetParams, createdAt, keypoolSize) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return @@ -1198,7 +1192,7 @@ func TestChangePassphrase(t *testing.T) { const keypoolSize = 10 createdAt := &BlockStamp{} w, err := NewWallet("banana wallet", "A wallet for testing.", - []byte("banana"), netParams, createdAt, keypoolSize) + []byte("banana"), tstNetParams, createdAt, keypoolSize) if err != nil { t.Error("Error creating new wallet: " + err.Error()) return