diff --git a/account.go b/account.go index 628f0a7..2ec7af9 100644 --- a/account.go +++ b/account.go @@ -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 diff --git a/rpcserver.go b/rpcserver.go index 0cdb266..1ef3780 100644 --- a/rpcserver.go +++ b/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 } diff --git a/wallet/wallet.go b/wallet/wallet.go index 5d4b394..de9eaf9 100644 --- a/wallet/wallet.go +++ b/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>