Add support for pay-to-script-hash addresses to wallet.
This commit is contained in:
parent
3831ba7abc
commit
2ef11ae7f5
4 changed files with 628 additions and 22 deletions
|
@ -310,6 +310,11 @@ func (a *Account) DumpPrivKeys() ([]string, error) {
|
|||
// key to privkeys.
|
||||
var privkeys []string
|
||||
for addr, info := range a.Wallet.ActiveAddresses() {
|
||||
// No keys to export for scripts.
|
||||
if _, isScript := addr.(*btcutil.AddressScriptHash); isScript {
|
||||
continue
|
||||
}
|
||||
|
||||
key, err := a.Wallet.AddressKey(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
39
rpcserver.go
39
rpcserver.go
|
@ -1583,12 +1583,13 @@ func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
_, scriptHash := addr.(*btcutil.AddressScriptHash)
|
||||
|
||||
result := map[string]interface{}{
|
||||
"address": addr.EncodeAddress(),
|
||||
"isvalid": true,
|
||||
"isscript": scriptHash,
|
||||
"address": addr.EncodeAddress(),
|
||||
"isvalid": true,
|
||||
// We could put whether or not the address is a script here,
|
||||
// by checking the type of "addr", however, the reference
|
||||
// implementation only puts that information if the script is
|
||||
// "ismine", and we follow that behaviour.
|
||||
}
|
||||
account, err := LookupAccountByAddress(addr.EncodeAddress())
|
||||
if err == nil {
|
||||
|
@ -1604,14 +1605,28 @@ func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
case *wallet.AddressPubKeyInfo:
|
||||
result["compressed"] = info.Compressed()
|
||||
result["pubkey"] = info.Pubkey
|
||||
default:
|
||||
}
|
||||
|
||||
// TODO(oga) when we handle different types of addresses then
|
||||
// we will need to check here and only provide the script,
|
||||
// hexsript and list of addresses.
|
||||
// if scripthash, the pubkey if pubkey/pubkeyhash, etc.
|
||||
// for now we only support p2pkh so is irrelavent
|
||||
case *wallet.AddressScriptInfo:
|
||||
result["isscript"] = true
|
||||
addrStrings := make([]string,
|
||||
len(info.Addresses))
|
||||
for i, a := range info.Addresses {
|
||||
addrStrings[i] = a.EncodeAddress()
|
||||
}
|
||||
result["addresses"] = addrStrings
|
||||
result["hex"] = info.Script
|
||||
|
||||
class := info.ScriptClass
|
||||
// script type
|
||||
result["script"] = class.String()
|
||||
if class == btcscript.MultiSigTy {
|
||||
result["sigsrequired"] =
|
||||
info.RequiredSigs
|
||||
}
|
||||
|
||||
default:
|
||||
/* This space intentionally left blank */
|
||||
}
|
||||
} else {
|
||||
result["ismine"] = false
|
||||
}
|
||||
|
|
447
wallet/wallet.go
447
wallet/wallet.go
|
@ -30,6 +30,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/conformal/btcec"
|
||||
"github.com/conformal/btcscript"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"io"
|
||||
|
@ -79,6 +80,7 @@ const (
|
|||
addrCommentHeader entryHeader = 1 << iota
|
||||
txCommentHeader
|
||||
deletedHeader
|
||||
scriptHeader
|
||||
addrHeader entryHeader = 0
|
||||
)
|
||||
|
||||
|
@ -443,6 +445,13 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
}
|
||||
n += read
|
||||
wt = &entry
|
||||
case scriptHeader:
|
||||
var entry scriptEntry
|
||||
if read, err = entry.ReadFrom(r); err != nil {
|
||||
return n + read, err
|
||||
}
|
||||
n += read
|
||||
wt = &entry
|
||||
case addrCommentHeader:
|
||||
var entry addrCommentEntry
|
||||
if read, err = entry.ReadFrom(r); err != nil {
|
||||
|
@ -473,8 +482,10 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Stringified byte slices for use as map lookup keys.
|
||||
type addressKey string
|
||||
type transactionHashKey string
|
||||
|
||||
type comment []byte
|
||||
|
||||
func getAddressKey(addr btcutil.Address) addressKey {
|
||||
|
@ -707,6 +718,12 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
case *scriptEntry:
|
||||
addr := e.script.address(w.net)
|
||||
w.addrMap[getAddressKey(addr)] = &e.script
|
||||
// script are always imported.
|
||||
w.importedAddrs = append(w.importedAddrs, &e.script)
|
||||
|
||||
case *addrCommentEntry:
|
||||
addr := e.address(w.net)
|
||||
w.addrCommentMap[getAddressKey(addr)] =
|
||||
|
@ -745,6 +762,14 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
|
|||
// kind of nice but probably isn't necessary.
|
||||
chainedAddrs[btcAddr.chainIndex] = e
|
||||
}
|
||||
|
||||
case *scriptAddress:
|
||||
e := &scriptEntry{
|
||||
script: *btcAddr,
|
||||
}
|
||||
copy(e.scriptHash160[:], btcAddr.scriptHash[:])
|
||||
// scripts are always imported
|
||||
importedAddrs = append(importedAddrs, e)
|
||||
}
|
||||
}
|
||||
wts = append(chainedAddrs, importedAddrs...)
|
||||
|
@ -752,7 +777,7 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
|
|||
e := &addrCommentEntry{
|
||||
comment: []byte(comment),
|
||||
}
|
||||
// addresskey is the address hash as a string, we can cast it
|
||||
// addresskey is the pubkey hash as a string, we can cast it
|
||||
// safely (though a little distasteful).
|
||||
copy(e.pubKeyHash160[:], []byte(addr))
|
||||
wts = append(wts, e)
|
||||
|
@ -1202,22 +1227,15 @@ func (w *Wallet) AddressKey(a btcutil.Address) (key *ecdsa.PrivateKey, err error
|
|||
D: new(big.Int).SetBytes(privKeyCT),
|
||||
}, nil
|
||||
default:
|
||||
// Currently only other type is script, they have no keys.
|
||||
return nil, errors.New("unsupported address type")
|
||||
}
|
||||
}
|
||||
|
||||
// AddressInfo returns an AddressInfo structure for an address in a wallet.
|
||||
func (w *Wallet) AddressInfo(a btcutil.Address) (AddressInfo, error) {
|
||||
// Currently, only P2PKH addresses are supported. This should
|
||||
// be extended to a switch-case statement when support for other
|
||||
// addresses are added.
|
||||
addr, ok := a.(*btcutil.AddressPubKeyHash)
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported address")
|
||||
}
|
||||
|
||||
// Look up address by address hash.
|
||||
btcaddr, ok := w.addrMap[getAddressKey(addr)]
|
||||
btcaddr, ok := w.addrMap[getAddressKey(a)]
|
||||
if !ok {
|
||||
return nil, ErrAddressNotFound
|
||||
}
|
||||
|
@ -1369,6 +1387,34 @@ func (w *Wallet) ImportPrivateKey(privkey []byte, compressed bool, bs *BlockStam
|
|||
return addr, nil
|
||||
}
|
||||
|
||||
// ImportScript creates a new scriptAddress with a user-provided script
|
||||
// and adds it to the wallet.
|
||||
func (w *Wallet) ImportScript(script []byte, bs *BlockStamp) (btcutil.Address, error) {
|
||||
if w.flags.watchingOnly {
|
||||
return nil, ErrWalletIsWatchingOnly
|
||||
}
|
||||
|
||||
if _, ok := w.addrMap[addressKey(btcutil.Hash160(script))]; ok {
|
||||
return nil, ErrDuplicate
|
||||
}
|
||||
|
||||
// Create new address with this private key.
|
||||
scriptaddr, err := newScriptAddress(script, bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add address to wallet's bookkeeping structures. Adding to
|
||||
// the map will result in the imported address being serialized
|
||||
// on the next WriteTo call.
|
||||
addr := scriptaddr.address(w.Net())
|
||||
w.addrMap[getAddressKey(addr)] = scriptaddr
|
||||
w.importedAddrs = append(w.importedAddrs, scriptaddr)
|
||||
|
||||
// Create and return address.
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// CreateDate returns the Unix time of the wallet creation time. This
|
||||
// is used to compare the wallet creation time against block headers and
|
||||
// set a better minimum block height of where to being rescans.
|
||||
|
@ -2435,6 +2481,346 @@ func (a *btcAddress) imported() bool {
|
|||
return a.chainIndex == importedKeyChainIdx
|
||||
}
|
||||
|
||||
// note that there is no encrypted bit here since if we had a script encrypted
|
||||
// and then used it on the blockchain this provides a simple known plaintext in
|
||||
// the wallet file. It was determined that the script in a p2sh transaction is
|
||||
// not a secret and any sane situation would also require a signature (which
|
||||
// does have a secret).
|
||||
type scriptFlags struct {
|
||||
hasScript bool
|
||||
change bool
|
||||
}
|
||||
|
||||
// ReadFrom implements the io.ReaderFrom interface by reading from r into sf.
|
||||
func (sf *scriptFlags) ReadFrom(r io.Reader) (int64, error) {
|
||||
var b [8]byte
|
||||
n, err := r.Read(b[:])
|
||||
if err != nil {
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// We match bits from addrFlags for similar fields. hence hasScript uses
|
||||
// the same bit as hasPubKey and the change bit is the same for both.
|
||||
sf.hasScript = b[0]&(1<<1) != 0
|
||||
sf.change = b[0]&(1<<5) != 0
|
||||
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriteTo interface by writing sf into w.
|
||||
func (sf *scriptFlags) WriteTo(w io.Writer) (int64, error) {
|
||||
var b [8]byte
|
||||
if sf.hasScript {
|
||||
b[0] |= 1 << 1
|
||||
}
|
||||
if sf.change {
|
||||
b[0] |= 1 << 5
|
||||
}
|
||||
|
||||
n, err := w.Write(b[:])
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// p2SHScript represents the variable length script entry in a wallet.
|
||||
type p2SHScript []byte
|
||||
|
||||
// ReadFrom implements the ReaderFrom interface by reading the P2SH script from
|
||||
// r in the format <4 bytes little endian length><script bytes>
|
||||
func (a *p2SHScript) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
//read length
|
||||
lenBytes := make([]byte, 4)
|
||||
|
||||
read, err := r.Read(lenBytes)
|
||||
n += int64(read)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(lenBytes)
|
||||
|
||||
script := make([]byte, length)
|
||||
|
||||
read, err = r.Read(script)
|
||||
n += int64(read)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
*a = script
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteTo implements the WriterTo interface by writing the P2SH script to w in
|
||||
// the format <4 bytes little endian length><script bytes>
|
||||
func (a *p2SHScript) WriteTo(w io.Writer) (n int64, err error) {
|
||||
// Prepare and write 32-bit little-endian length header
|
||||
lenBytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(lenBytes, uint32(len(*a)))
|
||||
|
||||
written, err := w.Write(lenBytes)
|
||||
n += int64(written)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Now write the bytes themselves.
|
||||
written, err = w.Write(*a)
|
||||
|
||||
return n + int64(written), err
|
||||
}
|
||||
|
||||
type scriptAddress struct {
|
||||
scriptHash [ripemd160.Size]byte
|
||||
flags scriptFlags
|
||||
script p2SHScript // variable length
|
||||
firstSeen int64
|
||||
lastSeen int64
|
||||
firstBlock int32
|
||||
lastBlock int32
|
||||
}
|
||||
|
||||
// newScriptAddress initializes and returns a new P2SH address.
|
||||
// iv must be 16 bytes, or nil (in which case it is randomly generated).
|
||||
func newScriptAddress(script []byte, bs *BlockStamp) (addr *scriptAddress, err error) {
|
||||
addr = &scriptAddress{
|
||||
flags: scriptFlags{
|
||||
hasScript: true,
|
||||
change: false,
|
||||
},
|
||||
script: script,
|
||||
firstSeen: time.Now().Unix(),
|
||||
firstBlock: bs.Height,
|
||||
}
|
||||
copy(addr.scriptHash[:], btcutil.Hash160(addr.script[:]))
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// ReadFrom reads an script address from an io.Reader.
|
||||
func (a *scriptAddress) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
var read int64
|
||||
|
||||
// Checksums
|
||||
var chkScriptHash uint32
|
||||
var chkScript uint32
|
||||
|
||||
// Read serialized wallet into addr fields and checksums.
|
||||
datas := []interface{}{
|
||||
&a.scriptHash,
|
||||
&chkScriptHash,
|
||||
make([]byte, 4), // version
|
||||
&a.flags,
|
||||
&a.script,
|
||||
&chkScript,
|
||||
&a.firstSeen,
|
||||
&a.lastSeen,
|
||||
&a.firstBlock,
|
||||
&a.lastBlock,
|
||||
}
|
||||
for _, data := range datas {
|
||||
if rf, ok := data.(io.ReaderFrom); ok {
|
||||
read, err = rf.ReadFrom(r)
|
||||
} else {
|
||||
read, err = binaryRead(r, binary.LittleEndian, data)
|
||||
}
|
||||
if err != nil {
|
||||
return n + read, err
|
||||
}
|
||||
n += read
|
||||
}
|
||||
|
||||
// Verify checksums, correct errors where possible.
|
||||
checks := []struct {
|
||||
data []byte
|
||||
chk uint32
|
||||
}{
|
||||
{a.scriptHash[:], chkScriptHash},
|
||||
{a.script, chkScript},
|
||||
}
|
||||
for i := range checks {
|
||||
if err = verifyAndFix(checks[i].data, checks[i].chk); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo by writing the scriptAddress to w.
|
||||
func (a *scriptAddress) WriteTo(w io.Writer) (n int64, err error) {
|
||||
var written int64
|
||||
|
||||
datas := []interface{}{
|
||||
&a.scriptHash,
|
||||
walletHash(a.scriptHash[:]),
|
||||
make([]byte, 4), //version
|
||||
&a.flags,
|
||||
&a.script,
|
||||
walletHash(a.script),
|
||||
&a.firstSeen,
|
||||
&a.lastSeen,
|
||||
&a.firstBlock,
|
||||
&a.lastBlock,
|
||||
}
|
||||
for _, data := range datas {
|
||||
if wt, ok := data.(io.WriterTo); ok {
|
||||
written, err = wt.WriteTo(w)
|
||||
} else {
|
||||
written, err = binaryWrite(w, binary.LittleEndian, data)
|
||||
}
|
||||
if err != nil {
|
||||
return n + written, err
|
||||
}
|
||||
n += written
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// verifyKeypairs always fails since there is no keypair for a scriptAddress
|
||||
func (a *scriptAddress) verifyKeypairs() error {
|
||||
return errors.New("keypairs are always bad for script")
|
||||
}
|
||||
|
||||
// encrypt attempts to encrypt an address's clear text script key,
|
||||
// failing if the address is already encrypted or if the private key is
|
||||
// not 32 bytes. If successful, the encryption flag is set.
|
||||
func (a *scriptAddress) encrypt(key []byte) error {
|
||||
return errors.New("unable to encrypt script")
|
||||
}
|
||||
|
||||
// lock removes the reference this address holds to its clear text
|
||||
// private key. This function fails if the address is not encrypted.
|
||||
func (a *scriptAddress) lock() error {
|
||||
// nothing to encrypt
|
||||
return errors.New("unable to lock unencrypted script")
|
||||
}
|
||||
|
||||
// unlock decrypts and stores a pointer to an address's private key,
|
||||
// failing if the address is not encrypted, or the provided key is
|
||||
// incorrect. The returned clear text private key will always be a copy
|
||||
// that may be safely used by the caller without worrying about it being
|
||||
// zeroed during an address lock.
|
||||
func (a *scriptAddress) unlock(key []byte) (privKeyCT []byte, err error) {
|
||||
return nil, errors.New("unable to unlock unencrypted script")
|
||||
}
|
||||
|
||||
// changeEncryptionKey re-encrypts the private keys for an address
|
||||
// with a new AES encryption key. oldkey must be the old AES encryption key
|
||||
// and is used to decrypt the private key.
|
||||
func (a *scriptAddress) changeEncryptionKey(oldkey, newkey []byte) error {
|
||||
return errors.New("script address tried to change encryption key")
|
||||
}
|
||||
|
||||
// address returns a btcutil.AddressScriptHash for a btcAddress.
|
||||
func (a *scriptAddress) address(net btcwire.BitcoinNet) btcutil.Address {
|
||||
// error is not returned because the hash will always be 20
|
||||
// bytes, and net is assumed to be valid.
|
||||
addr, _ := btcutil.NewAddressScriptHashFromHash(a.scriptHash[:], net)
|
||||
return addr
|
||||
}
|
||||
|
||||
// AddressScriptInfo implements the AddressInfo interface for a
|
||||
// pay-to-script-hash address. Additionally it has information about the script.
|
||||
type AddressScriptInfo struct {
|
||||
address btcutil.Address
|
||||
scriptHash string
|
||||
firstBlock int32
|
||||
imported bool
|
||||
change bool
|
||||
Script []byte
|
||||
ScriptClass btcscript.ScriptClass
|
||||
Addresses []btcutil.Address
|
||||
RequiredSigs int
|
||||
}
|
||||
|
||||
// Address returns the script address, implementing AddressInfo.
|
||||
func (ai *AddressScriptInfo) Address() btcutil.Address {
|
||||
return ai.address
|
||||
}
|
||||
|
||||
// AddrHash returns the script hash, implementing AddressInfo.
|
||||
func (ai *AddressScriptInfo) AddrHash() string {
|
||||
return ai.scriptHash
|
||||
}
|
||||
|
||||
// FirstBlock returns the first block the address is seen in, implementing
|
||||
// AddressInfo.
|
||||
func (ai *AddressScriptInfo) FirstBlock() int32 {
|
||||
return ai.firstBlock
|
||||
}
|
||||
|
||||
// Imported returns the pub if the address was imported, or a chained address,
|
||||
// implementing AddressInfo.
|
||||
func (ai *AddressScriptInfo) Imported() bool {
|
||||
return ai.imported
|
||||
}
|
||||
|
||||
// Change returns true if the address was created as a change address,
|
||||
// implementing AddressInfo.
|
||||
func (ai *AddressScriptInfo) Change() bool {
|
||||
return ai.change
|
||||
}
|
||||
|
||||
// Compressed returns false since script addresses are never compressed,
|
||||
// implementing AddressInfo.
|
||||
func (ai *AddressScriptInfo) Compressed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// info returns information about a btcAddress stored in a AddressInfo
|
||||
// struct.
|
||||
func (a *scriptAddress) info(net btcwire.BitcoinNet) (AddressInfo, error) {
|
||||
class, addresses, reqSigs, err :=
|
||||
btcscript.ExtractPkScriptAddrs(a.script, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
script := make([]byte, len(a.script))
|
||||
copy(script, a.script)
|
||||
|
||||
address := a.address(net)
|
||||
return &AddressScriptInfo{
|
||||
address: address,
|
||||
scriptHash: string(a.scriptHash[:]),
|
||||
firstBlock: a.firstBlock,
|
||||
imported: true,
|
||||
change: a.flags.change,
|
||||
Script: script,
|
||||
ScriptClass: class,
|
||||
Addresses: addresses,
|
||||
RequiredSigs: reqSigs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// watchingCopy creates a copy of an address without a private key.
|
||||
// This is used to fill a watching a wallet with addresses from a
|
||||
// normal wallet.
|
||||
func (a *scriptAddress) watchingCopy() walletAddress {
|
||||
// just deep copy the whole thing.
|
||||
return &scriptAddress{
|
||||
scriptHash: a.scriptHash,
|
||||
flags: scriptFlags{
|
||||
change: a.flags.change,
|
||||
},
|
||||
script: a.script,
|
||||
firstSeen: a.firstSeen,
|
||||
lastSeen: a.lastSeen,
|
||||
firstBlock: a.firstBlock,
|
||||
lastBlock: a.lastBlock,
|
||||
}
|
||||
}
|
||||
|
||||
// FirstBlockHeight returns the first blockheight the address is known at.
|
||||
func (a *scriptAddress) FirstBlockHeight() int32 {
|
||||
return a.firstBlock
|
||||
}
|
||||
|
||||
// imported returns true because script addresses are always imported.
|
||||
func (a *scriptAddress) imported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func walletHash(b []byte) uint32 {
|
||||
sum := btcwire.DoubleSha256(b)
|
||||
return binary.LittleEndian.Uint32(sum)
|
||||
|
@ -2598,6 +2984,47 @@ func (e *addrEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
return n + read, err
|
||||
}
|
||||
|
||||
// scriptEntry is the entry type for a P2SH script.
|
||||
type scriptEntry struct {
|
||||
scriptHash160 [ripemd160.Size]byte
|
||||
script scriptAddress
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo by writing the entry to w.
|
||||
func (e *scriptEntry) WriteTo(w io.Writer) (n int64, err error) {
|
||||
var written int64
|
||||
|
||||
// Write header
|
||||
if written, err = binaryWrite(w, binary.LittleEndian, scriptHeader); err != nil {
|
||||
return n + written, err
|
||||
}
|
||||
n += written
|
||||
|
||||
// Write hash
|
||||
if written, err = binaryWrite(w, binary.LittleEndian, &e.scriptHash160); err != nil {
|
||||
return n + written, err
|
||||
}
|
||||
n += written
|
||||
|
||||
// Write btcAddress
|
||||
written, err = e.script.WriteTo(w)
|
||||
n += written
|
||||
return n, err
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom by reading the entry from e.
|
||||
func (e *scriptEntry) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
var read int64
|
||||
|
||||
if read, err = binaryRead(r, binary.LittleEndian, &e.scriptHash160); err != nil {
|
||||
return n + read, err
|
||||
}
|
||||
n += read
|
||||
|
||||
read, err = e.script.ReadFrom(r)
|
||||
return n + read, err
|
||||
}
|
||||
|
||||
type addrCommentEntry struct {
|
||||
pubKeyHash160 [ripemd160.Size]byte
|
||||
comment []byte
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"github.com/conformal/btcec"
|
||||
"github.com/conformal/btcscript"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
@ -90,6 +91,41 @@ func TestBtcAddressSerializer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestScriptAddressSerializer(t *testing.T) {
|
||||
script := []byte{btcscript.OP_TRUE, btcscript.OP_DUP,
|
||||
btcscript.OP_DROP}
|
||||
addr, err := newScriptAddress(script, &BlockStamp{})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create("btcaddress.bin")
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := addr.WriteTo(file); err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
file.Seek(0, 0)
|
||||
|
||||
var readAddr scriptAddress
|
||||
_, err = readAddr.ReadFrom(file)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(addr, &readAddr) {
|
||||
t.Error("Original and read btcAddress differ.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalletCreationSerialization(t *testing.T) {
|
||||
createdAt := &BlockStamp{}
|
||||
w1, err := NewWallet("banana wallet", "A wallet for testing.",
|
||||
|
@ -560,6 +596,9 @@ func TestWatchingWalletExport(t *testing.T) {
|
|||
t.Errorf("Chained address marked as needing a private key to be generated later.")
|
||||
return
|
||||
}
|
||||
case *scriptAddress:
|
||||
t.Errorf("Chained address was a script!")
|
||||
return
|
||||
default:
|
||||
t.Errorf("Chained address unknown type!")
|
||||
return
|
||||
|
@ -762,6 +801,126 @@ func TestImportPrivateKey(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestImportScript(t *testing.T) {
|
||||
const keypoolSize = 10
|
||||
createdAt := &BlockStamp{}
|
||||
w, err := NewWallet("banana wallet", "A wallet for testing.",
|
||||
[]byte("banana"), btcwire.MainNet, createdAt, keypoolSize)
|
||||
if err != nil {
|
||||
t.Error("Error creating new wallet: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err = w.Unlock([]byte("banana")); err != nil {
|
||||
t.Errorf("Can't unlock original wallet: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
script := []byte{btcscript.OP_TRUE, btcscript.OP_DUP,
|
||||
btcscript.OP_DROP}
|
||||
stamp := &BlockStamp{}
|
||||
address, err := w.ImportScript(script, stamp)
|
||||
if err != nil {
|
||||
t.Error("error importing script: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// lookup address
|
||||
ainfo, err := w.AddressInfo(address)
|
||||
if err != nil {
|
||||
t.Error("error looking up script: " + err.Error())
|
||||
}
|
||||
|
||||
sinfo, ok := ainfo.(*AddressScriptInfo)
|
||||
if !ok {
|
||||
t.Error("address info found isn't a script")
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(script, sinfo.Script) {
|
||||
t.Error("original and looked-up script do not match.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.ScriptClass != btcscript.NonStandardTy {
|
||||
t.Error("script type incorrect.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.RequiredSigs != 0 {
|
||||
t.Error("required sigs funny number")
|
||||
return
|
||||
}
|
||||
|
||||
if len(sinfo.Addresses) != 0 {
|
||||
t.Error("addresses in bogus script.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.Address().EncodeAddress() != address.EncodeAddress() {
|
||||
t.Error("script address doesn't match entry.")
|
||||
return
|
||||
}
|
||||
|
||||
if string(sinfo.Address().ScriptAddress()) != sinfo.AddrHash() {
|
||||
t.Error("script hash doesn't match address.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.FirstBlock() != 0 {
|
||||
t.Error("funny first block")
|
||||
return
|
||||
}
|
||||
|
||||
if !sinfo.Imported() {
|
||||
t.Error("imported script info not imported.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.Change() {
|
||||
t.Error("imported script is change.")
|
||||
return
|
||||
}
|
||||
|
||||
if sinfo.Compressed() {
|
||||
t.Error("imported script is compressed.")
|
||||
return
|
||||
}
|
||||
|
||||
// serialise and deseralise and check still there.
|
||||
|
||||
// Test (de)serialization of wallet.
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = w.WriteTo(buf)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot write wallet: %v", err)
|
||||
return
|
||||
}
|
||||
w2 := new(Wallet)
|
||||
_, err = w2.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Errorf("Cannot read wallet: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = w2.Unlock([]byte("banana")); err != nil {
|
||||
t.Errorf("Can't unlock deserialised wallet: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// lookup address
|
||||
ainfo2, err := w2.AddressInfo(address)
|
||||
if err != nil {
|
||||
t.Error("error looking up info in deserialized wallet: " + err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ainfo, ainfo2) {
|
||||
t.Error("original and deserialized scriptinfo do not match.")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChangePassphrase(t *testing.T) {
|
||||
const keypoolSize = 10
|
||||
createdAt := &BlockStamp{}
|
||||
|
|
Loading…
Reference in a new issue