/* * Copyright (c) 2013, 2014 Conformal Systems LLC * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package keystore import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha512" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "io/ioutil" "math/big" "os" "path/filepath" "sync" "time" "golang.org/x/crypto/ripemd160" "github.com/btcsuite/btcec" "github.com/btcsuite/btcnet" "github.com/btcsuite/btcscript" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwire" "github.com/conformal/btcwallet/rename" ) const ( filename = "wallet.bin" // Length in bytes of KDF output. kdfOutputBytes = 32 // Maximum length in bytes of a comment that can have a size represented // as a uint16. maxCommentLen = (1 << 16) - 1 ) const ( defaultKdfComputeTime = 0.25 defaultKdfMaxMem = 32 * 1024 * 1024 ) // Possible errors when dealing with key stores. var ( ErrAddressNotFound = errors.New("address not found") ErrAlreadyEncrypted = errors.New("private key is already encrypted") ErrChecksumMismatch = errors.New("checksum mismatch") ErrDuplicate = errors.New("duplicate key or address") ErrMalformedEntry = errors.New("malformed entry") ErrWatchingOnly = errors.New("keystore is watching-only") ErrLocked = errors.New("keystore is locked") ErrWrongPassphrase = errors.New("wrong passphrase") ) var fileID = [8]byte{0xba, 'W', 'A', 'L', 'L', 'E', 'T', 0x00} type entryHeader byte const ( addrCommentHeader entryHeader = 1 << iota txCommentHeader deletedHeader scriptHeader addrHeader entryHeader = 0 ) // We want to use binaryRead and binaryWrite instead of binary.Read // and binary.Write because those from the binary package do not return // the number of bytes actually written or read. We need to return // this value to correctly support the io.ReaderFrom and io.WriterTo // interfaces. func binaryRead(r io.Reader, order binary.ByteOrder, data interface{}) (n int64, err error) { var read int buf := make([]byte, binary.Size(data)) if read, err = io.ReadFull(r, buf); err != nil { return int64(read), err } return int64(read), binary.Read(bytes.NewBuffer(buf), order, data) } // See comment for binaryRead(). func binaryWrite(w io.Writer, order binary.ByteOrder, data interface{}) (n int64, err error) { buf := bytes.Buffer{} if err = binary.Write(&buf, order, data); err != nil { return 0, err } written, err := w.Write(buf.Bytes()) return int64(written), err } // pubkeyFromPrivkey creates an encoded pubkey based on a // 32-byte privkey. The returned pubkey is 33 bytes if compressed, // or 65 bytes if uncompressed. func pubkeyFromPrivkey(privkey []byte, compress bool) (pubkey []byte) { _, pk := btcec.PrivKeyFromBytes(btcec.S256(), privkey) if compress { return pk.SerializeCompressed() } return pk.SerializeUncompressed() } func keyOneIter(passphrase, salt []byte, memReqts uint64) []byte { saltedpass := append(passphrase, salt...) lutbl := make([]byte, memReqts) // Seed for lookup table seed := sha512.Sum512(saltedpass) copy(lutbl[:sha512.Size], seed[:]) for nByte := 0; nByte < (int(memReqts) - sha512.Size); nByte += sha512.Size { hash := sha512.Sum512(lutbl[nByte : nByte+sha512.Size]) copy(lutbl[nByte+sha512.Size:nByte+2*sha512.Size], hash[:]) } x := lutbl[cap(lutbl)-sha512.Size:] seqCt := uint32(memReqts / sha512.Size) nLookups := seqCt / 2 for i := uint32(0); i < nLookups; i++ { // Armory ignores endianness here. We assume LE. newIdx := binary.LittleEndian.Uint32(x[cap(x)-4:]) % seqCt // Index of hash result at newIdx vIdx := newIdx * sha512.Size v := lutbl[vIdx : vIdx+sha512.Size] // XOR hash x with hash v for j := 0; j < sha512.Size; j++ { x[j] ^= v[j] } // Save new hash to x hash := sha512.Sum512(x) copy(x, hash[:]) } return x[:kdfOutputBytes] } // kdf implements the key derivation function used by Armory // based on the ROMix algorithm described in Colin Percival's paper // "Stronger Key Derivation via Sequential Memory-Hard Functions" // (http://www.tarsnap.com/scrypt/scrypt.pdf). func kdf(passphrase []byte, params *kdfParameters) []byte { masterKey := passphrase for i := uint32(0); i < params.nIter; i++ { masterKey = keyOneIter(masterKey, params.salt[:], params.mem) } return masterKey } func pad(size int, b []byte) []byte { // Prevent a possible panic if the input exceeds the expected size. if len(b) > size { size = len(b) } p := make([]byte, size) copy(p[size-len(b):], b) return p } // chainedPrivKey deterministically generates a new private key using a // previous address and chaincode. privkey and chaincode must be 32 // bytes long, and pubkey may either be 33 or 65 bytes. func chainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) { if len(privkey) != 32 { return nil, fmt.Errorf("invalid privkey length %d (must be 32)", len(privkey)) } if len(chaincode) != 32 { return nil, fmt.Errorf("invalid chaincode length %d (must be 32)", len(chaincode)) } switch n := len(pubkey); n { case btcec.PubKeyBytesLenUncompressed, btcec.PubKeyBytesLenCompressed: // Correct length default: return nil, fmt.Errorf("invalid pubkey length %d", n) } xorbytes := make([]byte, 32) chainMod := btcwire.DoubleSha256(pubkey) for i := range xorbytes { xorbytes[i] = chainMod[i] ^ chaincode[i] } chainXor := new(big.Int).SetBytes(xorbytes) privint := new(big.Int).SetBytes(privkey) t := new(big.Int).Mul(chainXor, privint) b := t.Mod(t, btcec.S256().N).Bytes() return pad(32, b), nil } // chainedPubKey deterministically generates a new public key using a // previous public key and chaincode. pubkey must be 33 or 65 bytes, and // chaincode must be 32 bytes long. func chainedPubKey(pubkey, chaincode []byte) ([]byte, error) { var compressed bool switch n := len(pubkey); n { case btcec.PubKeyBytesLenUncompressed: compressed = false case btcec.PubKeyBytesLenCompressed: compressed = true default: // Incorrect serialized pubkey length return nil, fmt.Errorf("invalid pubkey length %d", n) } if len(chaincode) != 32 { return nil, fmt.Errorf("invalid chaincode length %d (must be 32)", len(chaincode)) } xorbytes := make([]byte, 32) chainMod := btcwire.DoubleSha256(pubkey) for i := range xorbytes { xorbytes[i] = chainMod[i] ^ chaincode[i] } oldPk, err := btcec.ParsePubKey(pubkey, btcec.S256()) if err != nil { return nil, err } newX, newY := btcec.S256().ScalarMult(oldPk.X, oldPk.Y, xorbytes) if err != nil { return nil, err } newPk := &btcec.PublicKey{ Curve: btcec.S256(), X: newX, Y: newY, } if compressed { return newPk.SerializeCompressed(), nil } return newPk.SerializeUncompressed(), nil } type version struct { major byte minor byte bugfix byte autoincrement byte } // Enforce that version satisifies the io.ReaderFrom and // io.WriterTo interfaces. var _ io.ReaderFrom = &version{} var _ io.WriterTo = &version{} // readerFromVersion is an io.ReaderFrom and io.WriterTo that // can specify any particular key store file format for reading // depending on the key store file version. type readerFromVersion interface { readFromVersion(version, io.Reader) (int64, error) io.WriterTo } func (v version) String() string { str := fmt.Sprintf("%d.%d", v.major, v.minor) if v.bugfix != 0x00 || v.autoincrement != 0x00 { str += fmt.Sprintf(".%d", v.bugfix) } if v.autoincrement != 0x00 { str += fmt.Sprintf(".%d", v.autoincrement) } return str } func (v version) Uint32() uint32 { return uint32(v.major)<<6 | uint32(v.minor)<<4 | uint32(v.bugfix)<<2 | uint32(v.autoincrement) } func (v *version) ReadFrom(r io.Reader) (int64, error) { // Read 4 bytes for the version. var versBytes [4]byte n, err := io.ReadFull(r, versBytes[:]) if err != nil { return int64(n), err } v.major = versBytes[0] v.minor = versBytes[1] v.bugfix = versBytes[2] v.autoincrement = versBytes[3] return int64(n), nil } func (v *version) WriteTo(w io.Writer) (int64, error) { // Write 4 bytes for the version. versBytes := []byte{ v.major, v.minor, v.bugfix, v.autoincrement, } n, err := w.Write(versBytes) return int64(n), err } // LT returns whether v is an earlier version than v2. func (v version) LT(v2 version) bool { switch { case v.major < v2.major: return true case v.minor < v2.minor: return true case v.bugfix < v2.bugfix: return true case v.autoincrement < v2.autoincrement: return true default: return false } } // EQ returns whether v2 is an equal version to v. func (v version) EQ(v2 version) bool { switch { case v.major != v2.major: return false case v.minor != v2.minor: return false case v.bugfix != v2.bugfix: return false case v.autoincrement != v2.autoincrement: return false default: return true } } // GT returns whether v is a later version than v2. func (v version) GT(v2 version) bool { switch { case v.major > v2.major: return true case v.minor > v2.minor: return true case v.bugfix > v2.bugfix: return true case v.autoincrement > v2.autoincrement: return true default: return false } } // Various versions. var ( // VersArmory is the latest version used by Armory. VersArmory = version{1, 35, 0, 0} // Vers20LastBlocks is the version where key store files now hold // the 20 most recently seen block hashes. Vers20LastBlocks = version{1, 36, 0, 0} // VersUnsetNeedsPrivkeyFlag is the bugfix version where the // createPrivKeyNextUnlock address flag is correctly unset // after creating and encrypting its private key after unlock. // Otherwise, re-creating private keys will occur too early // in the address chain and fail due to encrypting an already // encrypted address. Key store versions at or before this // version include a special case to allow the duplicate // encrypt. VersUnsetNeedsPrivkeyFlag = version{1, 36, 1, 0} // VersCurrent is the current key store file version. VersCurrent = VersUnsetNeedsPrivkeyFlag ) type varEntries struct { store *Store entries []io.WriterTo } func (v *varEntries) WriteTo(w io.Writer) (n int64, err error) { ss := v.entries var written int64 for _, s := range ss { var err error if written, err = s.WriteTo(w); err != nil { return n + written, err } n += written } return n, nil } func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) { var read int64 // Remove any previous entries. v.entries = nil wts := v.entries // Keep reading entries until an EOF is reached. for { var header entryHeader if read, err = binaryRead(r, binary.LittleEndian, &header); err != nil { // EOF here is not an error. if err == io.EOF { return n + read, nil } return n + read, err } n += read var wt io.WriterTo switch header { case addrHeader: var entry addrEntry entry.addr.store = v.store if read, err = entry.ReadFrom(r); err != nil { return n + read, err } n += read wt = &entry case scriptHeader: var entry scriptEntry entry.script.store = v.store if read, err = entry.ReadFrom(r); err != nil { return n + read, err } n += read wt = &entry default: return n, fmt.Errorf("unknown entry header: %d", uint8(header)) } if wt != nil { wts = append(wts, wt) v.entries = wts } } } // Key stores use a custom network parameters type so it can be an io.ReaderFrom. // Due to the way and order that key stores are currently serialized and how // address reading requires the key store's network parameters, setting and // erroring on unknown key store 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) case btcwire.SimNet: *net = (netParams)(btcnet.SimNetParams) 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 type comment []byte func getAddressKey(addr btcutil.Address) addressKey { return addressKey(addr.ScriptAddress()) } // Store represents an key store in memory. It implements the // io.ReaderFrom and io.WriterTo interfaces to read from and // write to any type of byte streams, including files. type Store struct { // TODO: Use atomic operations for dirty so the reader lock // doesn't need to be grabbed. dirty bool path string dir string file string mtx sync.RWMutex vers version net *netParams flags walletFlags createDate int64 name [32]byte desc [256]byte highestUsed int64 kdfParams kdfParameters keyGenerator btcAddress // These are non-standard and fit in the extra 1024 bytes between the // root address and the appended entries. recent recentBlocks addrMap map[addressKey]walletAddress // The rest of the fields in this struct are not serialized. passphrase []byte secret []byte chainIdxMap map[int64]btcutil.Address importedAddrs []walletAddress lastChainIdx int64 missingKeysStart int64 } // New creates and initializes a new Store. name's and desc's byte length // must not exceed 32 and 256 bytes, respectively. All address private keys // are encrypted with passphrase. The key store is returned locked. func New(dir string, desc string, passphrase []byte, net *btcnet.Params, createdAt *BlockStamp) (*Store, error) { // Check sizes of inputs. if len(desc) > 256 { return nil, errors.New("desc exceeds 256 byte maximum size") } // Randomly-generate rootkey and chaincode. rootkey := make([]byte, 32) if _, err := rand.Read(rootkey); err != nil { return nil, err } chaincode := make([]byte, 32) if _, err := rand.Read(chaincode); err != nil { return nil, err } // Compute AES key and encrypt root address. kdfp, err := computeKdfParameters(defaultKdfComputeTime, defaultKdfMaxMem) if err != nil { return nil, err } aeskey := kdf(passphrase, kdfp) // Create and fill key store. s := &Store{ path: filepath.Join(dir, filename), dir: dir, file: filename, vers: VersCurrent, net: (*netParams)(net), flags: walletFlags{ useEncryption: true, watchingOnly: false, }, createDate: time.Now().Unix(), highestUsed: rootKeyChainIdx, kdfParams: *kdfp, recent: recentBlocks{ lastHeight: createdAt.Height, hashes: []*btcwire.ShaHash{ createdAt.Hash, }, }, addrMap: make(map[addressKey]walletAddress), chainIdxMap: make(map[int64]btcutil.Address), lastChainIdx: rootKeyChainIdx, missingKeysStart: rootKeyChainIdx, secret: aeskey, } copy(s.desc[:], []byte(desc)) // Create new root address from key and chaincode. root, err := newRootBtcAddress(s, rootkey, nil, chaincode, createdAt) if err != nil { return nil, err } // Verify root address keypairs. if err := root.verifyKeypairs(); err != nil { return nil, err } if err := root.encrypt(aeskey); err != nil { return nil, err } s.keyGenerator = *root // Add root address to maps. rootAddr := s.keyGenerator.Address() s.addrMap[getAddressKey(rootAddr)] = &s.keyGenerator s.chainIdxMap[rootKeyChainIdx] = rootAddr // key store must be returned locked. if err := s.Lock(); err != nil { return nil, err } return s, nil } // ReadFrom reads data from a io.Reader and saves it to a key store, // returning the number of bytes read and any errors encountered. func (s *Store) ReadFrom(r io.Reader) (n int64, err error) { s.mtx.Lock() defer s.mtx.Unlock() var read int64 s.net = &netParams{} s.addrMap = make(map[addressKey]walletAddress) s.chainIdxMap = make(map[int64]btcutil.Address) var id [8]byte appendedEntries := varEntries{store: s} s.keyGenerator.store = s // Iterate through each entry needing to be read. If data // implements io.ReaderFrom, use its ReadFrom func. Otherwise, // data is a pointer to a fixed sized value. datas := []interface{}{ &id, &s.vers, s.net, &s.flags, make([]byte, 6), // Bytes for Armory unique ID &s.createDate, &s.name, &s.desc, &s.highestUsed, &s.kdfParams, make([]byte, 256), &s.keyGenerator, newUnusedSpace(1024, &s.recent), &appendedEntries, } for _, data := range datas { var err error switch d := data.(type) { case readerFromVersion: read, err = d.readFromVersion(s.vers, r) case io.ReaderFrom: read, err = d.ReadFrom(r) default: read, err = binaryRead(r, binary.LittleEndian, d) } n += read if err != nil { return n, err } } if id != fileID { return n, errors.New("unknown file ID") } // Add root address to address map. rootAddr := s.keyGenerator.Address() s.addrMap[getAddressKey(rootAddr)] = &s.keyGenerator s.chainIdxMap[rootKeyChainIdx] = rootAddr s.lastChainIdx = rootKeyChainIdx // Fill unserializied fields. wts := appendedEntries.entries for _, wt := range wts { switch e := wt.(type) { case *addrEntry: addr := e.addr.Address() s.addrMap[getAddressKey(addr)] = &e.addr if e.addr.Imported() { s.importedAddrs = append(s.importedAddrs, &e.addr) } else { s.chainIdxMap[e.addr.chainIndex] = addr if s.lastChainIdx < e.addr.chainIndex { s.lastChainIdx = e.addr.chainIndex } } // If the private keys have not been created yet, mark the // earliest so all can be created on next key store unlock. if e.addr.flags.createPrivKeyNextUnlock { switch { case s.missingKeysStart == rootKeyChainIdx: fallthrough case e.addr.chainIndex < s.missingKeysStart: s.missingKeysStart = e.addr.chainIndex } } case *scriptEntry: addr := e.script.Address() s.addrMap[getAddressKey(addr)] = &e.script // script are always imported. s.importedAddrs = append(s.importedAddrs, &e.script) default: return n, errors.New("unknown appended entry") } } return n, nil } // WriteTo serializes a key store and writes it to a io.Writer, // returning the number of bytes written and any errors encountered. func (s *Store) WriteTo(w io.Writer) (n int64, err error) { s.mtx.RLock() defer s.mtx.RUnlock() return s.writeTo(w) } func (s *Store) writeTo(w io.Writer) (n int64, err error) { var wts []io.WriterTo var chainedAddrs = make([]io.WriterTo, len(s.chainIdxMap)-1) var importedAddrs []io.WriterTo for _, wAddr := range s.addrMap { switch btcAddr := wAddr.(type) { case *btcAddress: e := &addrEntry{ addr: *btcAddr, } copy(e.pubKeyHash160[:], btcAddr.AddrHash()) if btcAddr.Imported() { // No order for imported addresses. importedAddrs = append(importedAddrs, e) } else if btcAddr.chainIndex >= 0 { // Chained addresses are sorted. This is // kind of nice but probably isn't necessary. chainedAddrs[btcAddr.chainIndex] = e } case *scriptAddress: e := &scriptEntry{ script: *btcAddr, } copy(e.scriptHash160[:], btcAddr.AddrHash()) // scripts are always imported importedAddrs = append(importedAddrs, e) } } wts = append(chainedAddrs, importedAddrs...) appendedEntries := varEntries{store: s, entries: wts} // Iterate through each entry needing to be written. If data // implements io.WriterTo, use its WriteTo func. Otherwise, // data is a pointer to a fixed size value. datas := []interface{}{ &fileID, &VersCurrent, s.net, &s.flags, make([]byte, 6), // Bytes for Armory unique ID &s.createDate, &s.name, &s.desc, &s.highestUsed, &s.kdfParams, make([]byte, 256), &s.keyGenerator, newUnusedSpace(1024, &s.recent), &appendedEntries, } var written int64 for _, data := range datas { if s, ok := data.(io.WriterTo); ok { written, err = s.WriteTo(w) } else { written, err = binaryWrite(w, binary.LittleEndian, data) } n += written if err != nil { return n, err } } return n, nil } // TODO: set this automatically. func (s *Store) MarkDirty() { s.mtx.Lock() defer s.mtx.Unlock() s.dirty = true } func (s *Store) WriteIfDirty() error { s.mtx.RLock() if !s.dirty { s.mtx.RUnlock() return nil } // TempFile creates the file 0600, so no need to chmod it. fi, err := ioutil.TempFile(s.dir, s.file) if err != nil { s.mtx.RUnlock() return err } fiPath := fi.Name() _, err = s.writeTo(fi) if err != nil { s.mtx.RUnlock() fi.Close() return err } err = fi.Sync() if err != nil { s.mtx.RUnlock() fi.Close() return err } fi.Close() err = rename.Atomic(fiPath, s.path) s.mtx.RUnlock() if err == nil { s.mtx.Lock() s.dirty = false s.mtx.Unlock() } return err } // OpenDir opens a new key store from the specified directory. If the file // does not exist, the error from the os package will be returned, and can // be checked with os.IsNotExist to differentiate missing file errors from // others (including deserialization). func OpenDir(dir string) (*Store, error) { path := filepath.Join(dir, filename) fi, err := os.OpenFile(path, os.O_RDONLY, 0) if err != nil { return nil, err } defer fi.Close() store := new(Store) _, err = store.ReadFrom(fi) if err != nil { return nil, err } store.path = path store.dir = dir store.file = filename return store, nil } // Unlock derives an AES key from passphrase and key store's KDF // parameters and unlocks the root key of the key store. If // the unlock was successful, the key store's secret key is saved, // allowing the decryption of any encrypted private key. Any // addresses created while the key store was locked without private // keys are created at this time. func (s *Store) Unlock(passphrase []byte) error { s.mtx.Lock() defer s.mtx.Unlock() if s.flags.watchingOnly { return ErrWatchingOnly } // Derive key from KDF parameters and passphrase. key := kdf(passphrase, &s.kdfParams) // Unlock root address with derived key. if _, err := s.keyGenerator.unlock(key); err != nil { return err } // If unlock was successful, save the passphrase and aes key. s.passphrase = passphrase s.secret = key return s.createMissingPrivateKeys() } // Lock performs a best try effort to remove and zero all secret keys // associated with the key store. func (s *Store) Lock() (err error) { s.mtx.Lock() defer s.mtx.Unlock() if s.flags.watchingOnly { return ErrWatchingOnly } // Remove clear text passphrase from key store. if s.isLocked() { err = ErrLocked } else { zero(s.passphrase) s.passphrase = nil zero(s.secret) s.secret = nil } // Remove clear text private keys from all address entries. for _, addr := range s.addrMap { if baddr, ok := addr.(*btcAddress); ok { _ = baddr.lock() } } return err } // ChangePassphrase creates a new AES key from a new passphrase and // re-encrypts all encrypted private keys with the new key. func (s *Store) ChangePassphrase(new []byte) error { s.mtx.Lock() defer s.mtx.Unlock() if s.flags.watchingOnly { return ErrWatchingOnly } if s.isLocked() { return ErrLocked } oldkey := s.secret newkey := kdf(new, &s.kdfParams) for _, wa := range s.addrMap { // Only btcAddresses curently have private keys. a, ok := wa.(*btcAddress) if !ok { continue } if err := a.changeEncryptionKey(oldkey, newkey); err != nil { return err } } // zero old secrets. zero(s.passphrase) zero(s.secret) // Save new secrets. s.passphrase = new s.secret = newkey return nil } func zero(b []byte) { for i := range b { b[i] = 0 } } // IsLocked returns whether a key store is unlocked (in which case the // key is saved in memory), or locked. func (s *Store) IsLocked() bool { s.mtx.RLock() defer s.mtx.RUnlock() return s.isLocked() } func (s *Store) isLocked() bool { return len(s.secret) != 32 } // NextChainedAddress attempts to get the next chained address. If the key // store is unlocked, the next pubkey and private key of the address chain are // derived. If the key store is locke, only the next pubkey is derived, and // the private key will be generated on next unlock. func (s *Store) NextChainedAddress(bs *BlockStamp) (btcutil.Address, error) { s.mtx.Lock() defer s.mtx.Unlock() return s.nextChainedAddress(bs) } func (s *Store) nextChainedAddress(bs *BlockStamp) (btcutil.Address, error) { addr, err := s.nextChainedBtcAddress(bs) if err != nil { return nil, err } return addr.Address(), nil } // ChangeAddress returns the next chained address from the key store, marking // the address for a change transaction output. func (s *Store) ChangeAddress(bs *BlockStamp) (btcutil.Address, error) { s.mtx.Lock() defer s.mtx.Unlock() addr, err := s.nextChainedBtcAddress(bs) if err != nil { return nil, err } addr.flags.change = true // Create and return payment address for address hash. return addr.Address(), nil } func (s *Store) nextChainedBtcAddress(bs *BlockStamp) (*btcAddress, error) { // Attempt to get address hash of next chained address. nextAPKH, ok := s.chainIdxMap[s.highestUsed+1] if !ok { if s.isLocked() { // Chain pubkeys. if err := s.extendLocked(bs); err != nil { return nil, err } } else { // Chain private and pubkeys. if err := s.extendUnlocked(bs); err != nil { return nil, err } } // Should be added to the internal maps, try lookup again. nextAPKH, ok = s.chainIdxMap[s.highestUsed+1] if !ok { return nil, errors.New("chain index map inproperly updated") } } // Look up address. addr, ok := s.addrMap[getAddressKey(nextAPKH)] if !ok { return nil, errors.New("cannot find generated address") } btcAddr, ok := addr.(*btcAddress) if !ok { return nil, errors.New("found non-pubkey chained address") } s.highestUsed++ return btcAddr, nil } // LastChainedAddress returns the most recently requested chained // address from calling NextChainedAddress, or the root address if // no chained addresses have been requested. func (s *Store) LastChainedAddress() btcutil.Address { s.mtx.RLock() defer s.mtx.RUnlock() return s.chainIdxMap[s.highestUsed] } // extendUnlocked grows address chain for an unlocked keystore. func (s *Store) extendUnlocked(bs *BlockStamp) error { // Get last chained address. New chained addresses will be // chained off of this address's chaincode and private key. a := s.chainIdxMap[s.lastChainIdx] waddr, ok := s.addrMap[getAddressKey(a)] if !ok { return errors.New("expected last chained address not found") } if s.isLocked() { return ErrLocked } lastAddr, ok := waddr.(*btcAddress) if !ok { return errors.New("found non-pubkey chained address") } privkey, err := lastAddr.unlock(s.secret) if err != nil { return err } cc := lastAddr.chaincode[:] privkey, err = chainedPrivKey(privkey, lastAddr.pubKeyBytes(), cc) if err != nil { return err } newAddr, err := newBtcAddress(s, privkey, nil, bs, true) if err != nil { return err } if err := newAddr.verifyKeypairs(); err != nil { return err } if err = newAddr.encrypt(s.secret); err != nil { return err } a = newAddr.Address() s.addrMap[getAddressKey(a)] = newAddr newAddr.chainIndex = lastAddr.chainIndex + 1 s.chainIdxMap[newAddr.chainIndex] = a s.lastChainIdx++ copy(newAddr.chaincode[:], cc) return nil } // extendLocked creates one new address without a private key (allowing for // extending the address chain from a locked key store) chained from the // last used chained address and adds the address to the key store's internal // bookkeeping structures. func (s *Store) extendLocked(bs *BlockStamp) error { a := s.chainIdxMap[s.lastChainIdx] waddr, ok := s.addrMap[getAddressKey(a)] if !ok { return errors.New("expected last chained address not found") } addr, ok := waddr.(*btcAddress) if !ok { return errors.New("found non-pubkey chained address") } cc := addr.chaincode[:] nextPubkey, err := chainedPubKey(addr.pubKeyBytes(), cc) if err != nil { return err } newaddr, err := newBtcAddressWithoutPrivkey(s, nextPubkey, nil, bs) if err != nil { return err } a = newaddr.Address() s.addrMap[getAddressKey(a)] = newaddr newaddr.chainIndex = addr.chainIndex + 1 s.chainIdxMap[newaddr.chainIndex] = a s.lastChainIdx++ copy(newaddr.chaincode[:], cc) if s.missingKeysStart == rootKeyChainIdx { s.missingKeysStart = newaddr.chainIndex } return nil } func (s *Store) createMissingPrivateKeys() error { idx := s.missingKeysStart if idx == rootKeyChainIdx { return nil } // Lookup previous address. apkh, ok := s.chainIdxMap[idx-1] if !ok { return errors.New("missing previous chained address") } prevWAddr := s.addrMap[getAddressKey(apkh)] if s.isLocked() { return ErrLocked } prevAddr, ok := prevWAddr.(*btcAddress) if !ok { return errors.New("found non-pubkey chained address") } prevPrivKey, err := prevAddr.unlock(s.secret) if err != nil { return err } for i := idx; ; i++ { // Get the next private key for the ith address in the address chain. ithPrivKey, err := chainedPrivKey(prevPrivKey, prevAddr.pubKeyBytes(), prevAddr.chaincode[:]) if err != nil { return err } // Get the address with the missing private key, set, and // encrypt. apkh, ok := s.chainIdxMap[i] if !ok { // Finished. break } waddr := s.addrMap[getAddressKey(apkh)] addr, ok := waddr.(*btcAddress) if !ok { return errors.New("found non-pubkey chained address") } addr.privKeyCT = ithPrivKey if err := addr.encrypt(s.secret); err != nil { // Avoid bug: see comment for VersUnsetNeedsPrivkeyFlag. if err != ErrAlreadyEncrypted || s.vers.LT(VersUnsetNeedsPrivkeyFlag) { return err } } addr.flags.createPrivKeyNextUnlock = false // Set previous address and private key for next iteration. prevAddr = addr prevPrivKey = ithPrivKey } s.missingKeysStart = rootKeyChainIdx return nil } // Address returns an walletAddress structure for an address in a key store. // This address may be typecast into other interfaces (like PubKeyAddress // and ScriptAddress) if specific information e.g. keys is required. func (s *Store) Address(a btcutil.Address) (WalletAddress, error) { s.mtx.RLock() defer s.mtx.RUnlock() // Look up address by address hash. btcaddr, ok := s.addrMap[getAddressKey(a)] if !ok { return nil, ErrAddressNotFound } return btcaddr, nil } // Net returns the bitcoin network parameters for this key store. func (s *Store) Net() *btcnet.Params { s.mtx.RLock() defer s.mtx.RUnlock() return s.netParams() } func (s *Store) netParams() *btcnet.Params { return (*btcnet.Params)(s.net) } // SetSyncStatus sets the sync status for a single key store address. This // may error if the address is not found in the key store. // // When marking an address as unsynced, only the type Unsynced matters. // The value is ignored. func (s *Store) SetSyncStatus(a btcutil.Address, ss SyncStatus) error { s.mtx.Lock() defer s.mtx.Unlock() wa, ok := s.addrMap[getAddressKey(a)] if !ok { return ErrAddressNotFound } wa.setSyncStatus(ss) return nil } // SetSyncedWith marks already synced addresses in the key store to be in // sync with the recently-seen block described by the blockstamp. // Unsynced addresses are unaffected by this method and must be marked // as in sync with MarkAddressSynced or MarkAllSynced to be considered // in sync with bs. // // If bs is nil, the entire key store is marked unsynced. func (s *Store) SetSyncedWith(bs *BlockStamp) { s.mtx.Lock() defer s.mtx.Unlock() if bs == nil { s.recent.hashes = s.recent.hashes[:0] s.recent.lastHeight = s.keyGenerator.firstBlock s.keyGenerator.setSyncStatus(Unsynced(s.keyGenerator.firstBlock)) return } // Check if we're trying to rollback the last seen history. // If so, and this bs is already saved, remove anything // after and return. Otherwire, remove previous hashes. if bs.Height < s.recent.lastHeight { maybeIdx := len(s.recent.hashes) - 1 - int(s.recent.lastHeight-bs.Height) if maybeIdx >= 0 && maybeIdx < len(s.recent.hashes) && *s.recent.hashes[maybeIdx] == *bs.Hash { s.recent.lastHeight = bs.Height // subslice out the removed hashes. s.recent.hashes = s.recent.hashes[:maybeIdx] return } s.recent.hashes = nil } if bs.Height != s.recent.lastHeight+1 { s.recent.hashes = nil } s.recent.lastHeight = bs.Height if len(s.recent.hashes) == 20 { // Make room for the most recent hash. copy(s.recent.hashes, s.recent.hashes[1:]) // Set new block in the last position. s.recent.hashes[19] = bs.Hash } else { s.recent.hashes = append(s.recent.hashes, bs.Hash) } } // SyncHeight returns details about the block that a wallet is marked at least // synced through. The height is the height that rescans should start at when // syncing a wallet back to the best chain. // // NOTE: If the hash of the synced block is not known, hash will be nil, and // must be obtained from elsewhere. This must be explicitly checked before // dereferencing the pointer. func (s *Store) SyncedTo() (hash *btcwire.ShaHash, height int32) { s.mtx.RLock() defer s.mtx.RUnlock() switch h, ok := s.keyGenerator.SyncStatus().(PartialSync); { case ok && int32(h) > s.recent.lastHeight: height = int32(h) default: height = s.recent.lastHeight if n := len(s.recent.hashes); n != 0 { hash = s.recent.hashes[n-1] } } for _, a := range s.addrMap { var syncHeight int32 switch e := a.SyncStatus().(type) { case Unsynced: syncHeight = int32(e) case PartialSync: syncHeight = int32(e) case FullSync: continue } if syncHeight < height { height = syncHeight hash = nil // Can't go lower than 0. if height == 0 { return } } } return } // NewIterateRecentBlocks returns an iterator for recently-seen blocks. // The iterator starts at the most recently-added block, and Prev should // be used to access earlier blocks. func (s *Store) NewIterateRecentBlocks() *BlockIterator { s.mtx.RLock() defer s.mtx.RUnlock() return s.recent.iter(s) } // ImportPrivateKey imports a WIF private key into the keystore. The imported // address is created using either a compressed or uncompressed serialized // public key, depending on the CompressPubKey bool of the WIF. func (s *Store) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (btcutil.Address, error) { s.mtx.Lock() defer s.mtx.Unlock() if s.flags.watchingOnly { return nil, ErrWatchingOnly } // First, must check that the key being imported will not result // in a duplicate address. pkh := btcutil.Hash160(wif.SerializePubKey()) if _, ok := s.addrMap[addressKey(pkh)]; ok { return nil, ErrDuplicate } // The key store must be unlocked to encrypt the imported private key. if s.isLocked() { return nil, ErrLocked } // Create new address with this private key. privKey := wif.PrivKey.Serialize() btcaddr, err := newBtcAddress(s, privKey, nil, bs, wif.CompressPubKey) if err != nil { return nil, err } btcaddr.chainIndex = importedKeyChainIdx // Mark as unsynced if import height is below currently-synced // height. if len(s.recent.hashes) != 0 && bs.Height < s.recent.lastHeight { btcaddr.flags.unsynced = true } // Encrypt imported address with the derived AES key. if err = btcaddr.encrypt(s.secret); err != nil { return nil, err } addr := btcaddr.Address() // Add address to key store's bookkeeping structures. Adding to // the map will result in the imported address being serialized // on the next WriteTo call. s.addrMap[getAddressKey(addr)] = btcaddr s.importedAddrs = append(s.importedAddrs, btcaddr) // Create and return address. return addr, nil } // ImportScript creates a new scriptAddress with a user-provided script // and adds it to the key store. func (s *Store) ImportScript(script []byte, bs *BlockStamp) (btcutil.Address, error) { s.mtx.Lock() defer s.mtx.Unlock() if s.flags.watchingOnly { return nil, ErrWatchingOnly } if _, ok := s.addrMap[addressKey(btcutil.Hash160(script))]; ok { return nil, ErrDuplicate } // Create new address with this private key. scriptaddr, err := newScriptAddress(s, script, bs) if err != nil { return nil, err } // Mark as unsynced if import height is below currently-synced // height. if len(s.recent.hashes) != 0 && bs.Height < s.recent.lastHeight { scriptaddr.flags.unsynced = true } // Add address to key store's bookkeeping structures. Adding to // the map will result in the imported address being serialized // on the next WriteTo call. addr := scriptaddr.Address() s.addrMap[getAddressKey(addr)] = scriptaddr s.importedAddrs = append(s.importedAddrs, scriptaddr) // Create and return address. return addr, nil } // CreateDate returns the Unix time of the key store creation time. This // is used to compare the key store creation time against block headers and // set a better minimum block height of where to being rescans. func (s *Store) CreateDate() int64 { s.mtx.RLock() defer s.mtx.RUnlock() return s.createDate } // ExportWatchingWallet creates and returns a new key store with the same // addresses in w, but as a watching-only key store without any private keys. // New addresses created by the watching key store will match the new addresses // created the original key store (thanks to public key address chaining), but // will be missing the associated private keys. func (s *Store) ExportWatchingWallet() (*Store, error) { s.mtx.RLock() defer s.mtx.RUnlock() // Don't continue if key store is already watching-only. if s.flags.watchingOnly { return nil, ErrWatchingOnly } // Copy members of w into a new key store, but mark as watching-only and // do not include any private keys. ws := &Store{ vers: s.vers, net: s.net, flags: walletFlags{ useEncryption: false, watchingOnly: true, }, name: s.name, desc: s.desc, createDate: s.createDate, highestUsed: s.highestUsed, recent: recentBlocks{ lastHeight: s.recent.lastHeight, }, addrMap: make(map[addressKey]walletAddress), // todo oga make me a list chainIdxMap: make(map[int64]btcutil.Address), lastChainIdx: s.lastChainIdx, } kgwc := s.keyGenerator.watchingCopy(ws) ws.keyGenerator = *(kgwc.(*btcAddress)) if len(s.recent.hashes) != 0 { ws.recent.hashes = make([]*btcwire.ShaHash, 0, len(s.recent.hashes)) for _, hash := range s.recent.hashes { hashCpy := *hash ws.recent.hashes = append(ws.recent.hashes, &hashCpy) } } for apkh, addr := range s.addrMap { if !addr.Imported() { // Must be a btcAddress if !imported. btcAddr := addr.(*btcAddress) ws.chainIdxMap[btcAddr.chainIndex] = addr.Address() } apkhCopy := apkh ws.addrMap[apkhCopy] = addr.watchingCopy(ws) } if len(s.importedAddrs) != 0 { ws.importedAddrs = make([]walletAddress, 0, len(s.importedAddrs)) for _, addr := range s.importedAddrs { ws.importedAddrs = append(ws.importedAddrs, addr.watchingCopy(ws)) } } return ws, nil } // SyncStatus is the interface type for all sync variants. type SyncStatus interface { ImplementsSyncStatus() } type ( // Unsynced is a type representing an unsynced address. When this is // returned by a key store method, the value is the recorded first seen // block height. Unsynced int32 // PartialSync is a type representing a partially synced address (for // example, due to the result of a partially-completed rescan). PartialSync int32 // FullSync is a type representing an address that is in sync with the // recently seen blocks. FullSync struct{} ) // ImplementsSyncStatus is implemented to make Unsynced a SyncStatus. func (u Unsynced) ImplementsSyncStatus() {} // ImplementsSyncStatus is implemented to make PartialSync a SyncStatus. func (p PartialSync) ImplementsSyncStatus() {} // ImplementsSyncStatus is implemented to make FullSync a SyncStatus. func (f FullSync) ImplementsSyncStatus() {} // WalletAddress is an interface that provides acces to information regarding an // address managed by a key store. Concrete implementations of this type may // provide further fields to provide information specific to that type of // address. type WalletAddress interface { // Address returns a btcutil.Address for the backing address. Address() btcutil.Address // AddrHash returns the key or script hash related to the address AddrHash() string // FirstBlock returns the first block an address could be in. FirstBlock() int32 // Compressed returns true if the backing address was imported instead // of being part of an address chain. Imported() bool // Compressed returns true if the backing address was created for a // change output of a transaction. Change() bool // Compressed returns true if the backing address is compressed. Compressed() bool // SyncStatus returns the current synced state of an address. SyncStatus() SyncStatus } // SortedActiveAddresses returns all key store addresses that have been // requested to be generated. These do not include unused addresses in // the key pool. Use this when ordered addresses are needed. Otherwise, // ActiveAddresses is preferred. func (s *Store) SortedActiveAddresses() []WalletAddress { s.mtx.RLock() defer s.mtx.RUnlock() addrs := make([]WalletAddress, 0, s.highestUsed+int64(len(s.importedAddrs))+1) for i := int64(rootKeyChainIdx); i <= s.highestUsed; i++ { a := s.chainIdxMap[i] info, ok := s.addrMap[getAddressKey(a)] if ok { addrs = append(addrs, info) } } for _, addr := range s.importedAddrs { addrs = append(addrs, addr) } return addrs } // ActiveAddresses returns a map between active payment addresses // and their full info. These do not include unused addresses in the // key pool. If addresses must be sorted, use SortedActiveAddresses. func (s *Store) ActiveAddresses() map[btcutil.Address]WalletAddress { s.mtx.RLock() defer s.mtx.RUnlock() addrs := make(map[btcutil.Address]WalletAddress) for i := int64(rootKeyChainIdx); i <= s.highestUsed; i++ { a := s.chainIdxMap[i] addr := s.addrMap[getAddressKey(a)] addrs[addr.Address()] = addr } for _, addr := range s.importedAddrs { addrs[addr.Address()] = addr } return addrs } // ExtendActiveAddresses gets or creates the next n addresses from the // address chain and marks each as active. This is used to recover // deterministic (not imported) addresses from a key store backup, or to // keep the active addresses in sync between an encrypted key store with // private keys and an exported watching key store without. // // A slice is returned with the btcutil.Address of each new address. // The blockchain must be rescanned for these addresses. func (s *Store) ExtendActiveAddresses(n int) ([]btcutil.Address, error) { s.mtx.Lock() defer s.mtx.Unlock() last := s.addrMap[getAddressKey(s.chainIdxMap[s.highestUsed])] bs := &BlockStamp{Height: last.FirstBlock()} addrs := make([]btcutil.Address, n) for i := 0; i < n; i++ { addr, err := s.nextChainedAddress(bs) if err != nil { return nil, err } addrs[i] = addr } return addrs, nil } type walletFlags struct { useEncryption bool watchingOnly bool } func (wf *walletFlags) ReadFrom(r io.Reader) (int64, error) { var b [8]byte n, err := io.ReadFull(r, b[:]) if err != nil { return int64(n), err } wf.useEncryption = b[0]&(1<<0) != 0 wf.watchingOnly = b[0]&(1<<1) != 0 return int64(n), nil } func (wf *walletFlags) WriteTo(w io.Writer) (int64, error) { var b [8]byte if wf.useEncryption { b[0] |= 1 << 0 } if wf.watchingOnly { b[0] |= 1 << 1 } n, err := w.Write(b[:]) return int64(n), err } type addrFlags struct { hasPrivKey bool hasPubKey bool encrypted bool createPrivKeyNextUnlock bool compressed bool change bool unsynced bool partialSync bool } func (af *addrFlags) ReadFrom(r io.Reader) (int64, error) { var b [8]byte n, err := io.ReadFull(r, b[:]) if err != nil { return int64(n), err } af.hasPrivKey = b[0]&(1<<0) != 0 af.hasPubKey = b[0]&(1<<1) != 0 af.encrypted = b[0]&(1<<2) != 0 af.createPrivKeyNextUnlock = b[0]&(1<<3) != 0 af.compressed = b[0]&(1<<4) != 0 af.change = b[0]&(1<<5) != 0 af.unsynced = b[0]&(1<<6) != 0 af.partialSync = b[0]&(1<<7) != 0 // Currently (at least until watching-only key stores are implemented) // btcwallet shall refuse to open any unencrypted addresses. This // check only makes sense if there is a private key to encrypt, which // there may not be if the keypool was extended from just the last // public key and no private keys were written. if af.hasPrivKey && !af.encrypted { return int64(n), errors.New("private key is unencrypted") } return int64(n), nil } func (af *addrFlags) WriteTo(w io.Writer) (int64, error) { var b [8]byte if af.hasPrivKey { b[0] |= 1 << 0 } if af.hasPubKey { b[0] |= 1 << 1 } if af.hasPrivKey && !af.encrypted { // We only support encrypted privkeys. return 0, errors.New("address must be encrypted") } if af.encrypted { b[0] |= 1 << 2 } if af.createPrivKeyNextUnlock { b[0] |= 1 << 3 } if af.compressed { b[0] |= 1 << 4 } if af.change { b[0] |= 1 << 5 } if af.unsynced { b[0] |= 1 << 6 } if af.partialSync { b[0] |= 1 << 7 } n, err := w.Write(b[:]) return int64(n), err } // recentBlocks holds at most the last 20 seen block hashes as well as // the block height of the most recently seen block. type recentBlocks struct { hashes []*btcwire.ShaHash lastHeight int32 } func (rb *recentBlocks) readFromVersion(v version, r io.Reader) (int64, error) { if !v.LT(Vers20LastBlocks) { // Use current version. return rb.ReadFrom(r) } // Old file versions only saved the most recently seen // block height and hash, not the last 20. var read int64 // Read height. var heightBytes [4]byte // 4 bytes for a int32 n, err := io.ReadFull(r, heightBytes[:]) read += int64(n) if err != nil { return read, err } rb.lastHeight = int32(binary.LittleEndian.Uint32(heightBytes[:])) // If height is -1, the last synced block is unknown, so don't try // to read a block hash. if rb.lastHeight == -1 { rb.hashes = nil return read, nil } // Read block hash. var syncedBlockHash btcwire.ShaHash n, err = io.ReadFull(r, syncedBlockHash[:]) read += int64(n) if err != nil { return read, err } rb.hashes = []*btcwire.ShaHash{ &syncedBlockHash, } return read, nil } func (rb *recentBlocks) ReadFrom(r io.Reader) (int64, error) { var read int64 // Read number of saved blocks. This should not exceed 20. var nBlockBytes [4]byte // 4 bytes for a uint32 n, err := io.ReadFull(r, nBlockBytes[:]) read += int64(n) if err != nil { return read, err } nBlocks := binary.LittleEndian.Uint32(nBlockBytes[:]) if nBlocks > 20 { return read, errors.New("number of last seen blocks exceeds maximum of 20") } // Read most recently seen block height. var heightBytes [4]byte // 4 bytes for a int32 n, err = io.ReadFull(r, heightBytes[:]) read += int64(n) if err != nil { return read, err } height := int32(binary.LittleEndian.Uint32(heightBytes[:])) // height should not be -1 (or any other negative number) // since at this point we should be reading in at least one // known block. if height < 0 { return read, errors.New("expected a block but specified height is negative") } // Set last seen height. rb.lastHeight = height // Read nBlocks block hashes. Hashes are expected to be in // order of oldest to newest, but there's no way to check // that here. rb.hashes = make([]*btcwire.ShaHash, 0, nBlocks) for i := uint32(0); i < nBlocks; i++ { var blockSha btcwire.ShaHash n, err := io.ReadFull(r, blockSha[:]) read += int64(n) if err != nil { return read, err } rb.hashes = append(rb.hashes, &blockSha) } return read, nil } func (rb *recentBlocks) WriteTo(w io.Writer) (int64, error) { var written int64 // Write number of saved blocks. This should not exceed 20. nBlocks := uint32(len(rb.hashes)) if nBlocks > 20 { return written, errors.New("number of last seen blocks exceeds maximum of 20") } if nBlocks != 0 && rb.lastHeight < 0 { return written, errors.New("number of block hashes is positive, but height is negative") } var nBlockBytes [4]byte // 4 bytes for a uint32 binary.LittleEndian.PutUint32(nBlockBytes[:], nBlocks) n, err := w.Write(nBlockBytes[:]) written += int64(n) if err != nil { return written, err } // Write most recently seen block height. var heightBytes [4]byte // 4 bytes for a int32 binary.LittleEndian.PutUint32(heightBytes[:], uint32(rb.lastHeight)) n, err = w.Write(heightBytes[:]) written += int64(n) if err != nil { return written, err } // Write block hashes. for _, hash := range rb.hashes { n, err := w.Write(hash[:]) written += int64(n) if err != nil { return written, err } } return written, nil } // BlockIterator allows for the forwards and backwards iteration of recently // seen blocks. type BlockIterator struct { storeMtx *sync.RWMutex height int32 index int rb *recentBlocks } func (rb *recentBlocks) iter(s *Store) *BlockIterator { if rb.lastHeight == -1 || len(rb.hashes) == 0 { return nil } return &BlockIterator{ storeMtx: &s.mtx, height: rb.lastHeight, index: len(rb.hashes) - 1, rb: rb, } } func (it *BlockIterator) Next() bool { it.storeMtx.RLock() defer it.storeMtx.RUnlock() if it.index+1 >= len(it.rb.hashes) { return false } it.index++ return true } func (it *BlockIterator) Prev() bool { it.storeMtx.RLock() defer it.storeMtx.RUnlock() if it.index-1 < 0 { return false } it.index-- return true } func (it *BlockIterator) BlockStamp() BlockStamp { it.storeMtx.RLock() defer it.storeMtx.RUnlock() return BlockStamp{ Height: it.rb.lastHeight - int32(len(it.rb.hashes)-1-it.index), Hash: it.rb.hashes[it.index], } } // unusedSpace is a wrapper type to read or write one or more types // that btcwallet fits into an unused space left by Armory's key store file // format. type unusedSpace struct { nBytes int // number of unused bytes that armory left. rfvs []readerFromVersion } func newUnusedSpace(nBytes int, rfvs ...readerFromVersion) *unusedSpace { return &unusedSpace{ nBytes: nBytes, rfvs: rfvs, } } func (u *unusedSpace) readFromVersion(v version, r io.Reader) (int64, error) { var read int64 for _, rfv := range u.rfvs { n, err := rfv.readFromVersion(v, r) if err != nil { return read + n, err } read += n if read > int64(u.nBytes) { return read, errors.New("read too much from armory's unused space") } } // Read rest of actually unused bytes. unused := make([]byte, u.nBytes-int(read)) n, err := io.ReadFull(r, unused) return read + int64(n), err } func (u *unusedSpace) WriteTo(w io.Writer) (int64, error) { var written int64 for _, wt := range u.rfvs { n, err := wt.WriteTo(w) if err != nil { return written + n, err } written += n if written > int64(u.nBytes) { return written, errors.New("wrote too much to armory's unused space") } } // Write rest of actually unused bytes. unused := make([]byte, u.nBytes-int(written)) n, err := w.Write(unused) return written + int64(n), err } // walletAddress is the internal interface used to abstracted around the // different address types. type walletAddress interface { io.ReaderFrom io.WriterTo WalletAddress watchingCopy(*Store) walletAddress setSyncStatus(SyncStatus) } type btcAddress struct { store *Store address btcutil.Address flags addrFlags chaincode [32]byte chainIndex int64 chainDepth int64 // unused initVector [16]byte privKey [32]byte pubKey *btcec.PublicKey firstSeen int64 lastSeen int64 firstBlock int32 partialSyncHeight int32 // This is reappropriated from armory's `lastBlock` field. privKeyCT []byte // non-nil if unlocked. } const ( // Root address has a chain index of -1. Each subsequent // chained address increments the index. rootKeyChainIdx = -1 // Imported private keys are not part of the chain, and have a // special index of -2. importedKeyChainIdx = -2 ) const ( pubkeyCompressed byte = 0x2 pubkeyUncompressed byte = 0x4 ) type publicKey []byte func (k *publicKey) ReadFrom(r io.Reader) (n int64, err error) { var read int64 var format byte read, err = binaryRead(r, binary.LittleEndian, &format) if err != nil { return n + read, err } n += read // Remove the oddness from the format noodd := format noodd &= ^byte(0x1) var s []byte switch noodd { case pubkeyUncompressed: // Read the remaining 64 bytes. s = make([]byte, 64) case pubkeyCompressed: // Read the remaining 32 bytes. s = make([]byte, 32) default: return n, errors.New("unrecognized pubkey format") } read, err = binaryRead(r, binary.LittleEndian, &s) if err != nil { return n + read, err } n += read *k = append([]byte{format}, s...) return } func (k *publicKey) WriteTo(w io.Writer) (n int64, err error) { return binaryWrite(w, binary.LittleEndian, []byte(*k)) } // PubKeyAddress implements WalletAddress and additionally provides the // pubkey for a pubkey-based address. type PubKeyAddress interface { WalletAddress // PubKey returns the public key associated with the address. PubKey() *btcec.PublicKey // ExportPubKey returns the public key associated with the address // serialised as a hex encoded string. ExportPubKey() string // PrivKey returns the private key for the address. // It can fail if the key store is watching only, the key store is locked, // or the address doesn't have any keys. PrivKey() (*btcec.PrivateKey, error) // ExportPrivKey exports the WIF private key. ExportPrivKey() (*btcutil.WIF, error) } // newBtcAddress initializes and returns a new address. privkey must // be 32 bytes. iv must be 16 bytes, or nil (in which case it is // randomly generated). func newBtcAddress(wallet *Store, privkey, iv []byte, bs *BlockStamp, compressed bool) (addr *btcAddress, err error) { if len(privkey) != 32 { return nil, errors.New("private key is not 32 bytes") } addr, err = newBtcAddressWithoutPrivkey(wallet, pubkeyFromPrivkey(privkey, compressed), iv, bs) if err != nil { return nil, err } addr.flags.createPrivKeyNextUnlock = false addr.flags.hasPrivKey = true addr.privKeyCT = privkey return addr, nil } // newBtcAddressWithoutPrivkey initializes and returns a new address with an // unknown (at the time) private key that must be found later. pubkey must be // 33 or 65 bytes, and iv must be 16 bytes or empty (in which case it is // randomly generated). func newBtcAddressWithoutPrivkey(s *Store, pubkey, iv []byte, bs *BlockStamp) (addr *btcAddress, err error) { var compressed bool switch n := len(pubkey); n { case btcec.PubKeyBytesLenCompressed: compressed = true case btcec.PubKeyBytesLenUncompressed: compressed = false default: return nil, fmt.Errorf("invalid pubkey length %d", n) } if len(iv) == 0 { iv = make([]byte, 16) if _, err := rand.Read(iv); err != nil { return nil, err } } else if len(iv) != 16 { return nil, errors.New("init vector must be nil or 16 bytes large") } pk, err := btcec.ParsePubKey(pubkey, btcec.S256()) if err != nil { return nil, err } address, err := btcutil.NewAddressPubKeyHash(btcutil.Hash160(pubkey), s.netParams()) if err != nil { return nil, err } addr = &btcAddress{ flags: addrFlags{ hasPrivKey: false, hasPubKey: true, encrypted: false, createPrivKeyNextUnlock: true, compressed: compressed, change: false, unsynced: false, }, store: s, address: address, firstSeen: time.Now().Unix(), firstBlock: bs.Height, pubKey: pk, } copy(addr.initVector[:], iv) return addr, nil } // newRootBtcAddress generates a new address, also setting the // chaincode and chain index to represent this address as a root // address. func newRootBtcAddress(s *Store, privKey, iv, chaincode []byte, bs *BlockStamp) (addr *btcAddress, err error) { if len(chaincode) != 32 { return nil, errors.New("chaincode is not 32 bytes") } // Create new btcAddress with provided inputs. This will // always use a compressed pubkey. addr, err = newBtcAddress(s, privKey, iv, bs, true) if err != nil { return nil, err } copy(addr.chaincode[:], chaincode) addr.chainIndex = rootKeyChainIdx return addr, err } // verifyKeypairs creates a signature using the parsed private key and // verifies the signature with the parsed public key. If either of these // steps fail, the keypair generation failed and any funds sent to this // address will be unspendable. This step requires an unencrypted or // unlocked btcAddress. func (a *btcAddress) verifyKeypairs() error { if len(a.privKeyCT) != 32 { return errors.New("private key unavailable") } privKey := &btcec.PrivateKey{ PublicKey: *a.pubKey.ToECDSA(), D: new(big.Int).SetBytes(a.privKeyCT), } data := "String to sign." sig, err := privKey.Sign([]byte(data)) if err != nil { return err } ok := sig.Verify([]byte(data), privKey.PubKey()) if !ok { return errors.New("pubkey verification failed") } return nil } // ReadFrom reads an encrypted address from an io.Reader. func (a *btcAddress) ReadFrom(r io.Reader) (n int64, err error) { var read int64 // Checksums var chkPubKeyHash uint32 var chkChaincode uint32 var chkInitVector uint32 var chkPrivKey uint32 var chkPubKey uint32 var pubKeyHash [ripemd160.Size]byte var pubKey publicKey // Read serialized key store into addr fields and checksums. datas := []interface{}{ &pubKeyHash, &chkPubKeyHash, make([]byte, 4), // version &a.flags, &a.chaincode, &chkChaincode, &a.chainIndex, &a.chainDepth, &a.initVector, &chkInitVector, &a.privKey, &chkPrivKey, &pubKey, &chkPubKey, &a.firstSeen, &a.lastSeen, &a.firstBlock, &a.partialSyncHeight, } 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 }{ {pubKeyHash[:], chkPubKeyHash}, {a.chaincode[:], chkChaincode}, {a.initVector[:], chkInitVector}, {a.privKey[:], chkPrivKey}, {pubKey, chkPubKey}, } for i := range checks { if err = verifyAndFix(checks[i].data, checks[i].chk); err != nil { return n, err } } if !a.flags.hasPubKey { return n, errors.New("read in an address without a public key") } pk, err := btcec.ParsePubKey(pubKey, btcec.S256()) if err != nil { return n, err } a.pubKey = pk addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash[:], a.store.netParams()) if err != nil { return n, err } a.address = addr return n, nil } func (a *btcAddress) WriteTo(w io.Writer) (n int64, err error) { var written int64 pubKey := a.pubKeyBytes() hash := a.address.ScriptAddress() datas := []interface{}{ &hash, walletHash(hash), make([]byte, 4), //version &a.flags, &a.chaincode, walletHash(a.chaincode[:]), &a.chainIndex, &a.chainDepth, &a.initVector, walletHash(a.initVector[:]), &a.privKey, walletHash(a.privKey[:]), &pubKey, walletHash(pubKey), &a.firstSeen, &a.lastSeen, &a.firstBlock, &a.partialSyncHeight, } 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 } // encrypt attempts to encrypt an address's clear text private 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 *btcAddress) encrypt(key []byte) error { if a.flags.encrypted { return ErrAlreadyEncrypted } if len(a.privKeyCT) != 32 { return errors.New("invalid clear text private key") } aesBlockEncrypter, err := aes.NewCipher(key) if err != nil { return err } aesEncrypter := cipher.NewCFBEncrypter(aesBlockEncrypter, a.initVector[:]) aesEncrypter.XORKeyStream(a.privKey[:], a.privKeyCT) a.flags.hasPrivKey = true a.flags.encrypted = true return nil } // lock removes the reference this address holds to its clear text // private key. This function fails if the address is not encrypted. func (a *btcAddress) lock() error { if !a.flags.encrypted { return errors.New("unable to lock unencrypted address") } zero(a.privKeyCT) a.privKeyCT = nil return nil } // 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 *btcAddress) unlock(key []byte) (privKeyCT []byte, err error) { if !a.flags.encrypted { return nil, errors.New("unable to unlock unencrypted address") } // Decrypt private key with AES key. aesBlockDecrypter, err := aes.NewCipher(key) if err != nil { return nil, err } aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, a.initVector[:]) privkey := make([]byte, 32) aesDecrypter.XORKeyStream(privkey, a.privKey[:]) // If secret is already saved, simply compare the bytes. if len(a.privKeyCT) == 32 { if !bytes.Equal(a.privKeyCT, privkey) { return nil, ErrWrongPassphrase } privKeyCT := make([]byte, 32) copy(privKeyCT, a.privKeyCT) return privKeyCT, nil } x, y := btcec.S256().ScalarBaseMult(privkey) if x.Cmp(a.pubKey.X) != 0 || y.Cmp(a.pubKey.Y) != 0 { return nil, ErrWrongPassphrase } privkeyCopy := make([]byte, 32) copy(privkeyCopy, privkey) a.privKeyCT = privkey return privkeyCopy, nil } // 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 *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error { // Address must have a private key and be encrypted to continue. if !a.flags.hasPrivKey { return errors.New("no private key") } if !a.flags.encrypted { return errors.New("address is not encrypted") } privKeyCT, err := a.unlock(oldkey) if err != nil { return err } aesBlockEncrypter, err := aes.NewCipher(newkey) if err != nil { return err } newIV := make([]byte, len(a.initVector)) if _, err := rand.Read(newIV); err != nil { return err } copy(a.initVector[:], newIV) aesEncrypter := cipher.NewCFBEncrypter(aesBlockEncrypter, a.initVector[:]) aesEncrypter.XORKeyStream(a.privKey[:], privKeyCT) return nil } // Address returns the pub key address, implementing AddressInfo. func (a *btcAddress) Address() btcutil.Address { return a.address } // AddrHash returns the pub key hash, implementing WalletAddress. func (a *btcAddress) AddrHash() string { return string(a.address.ScriptAddress()) } // FirstBlock returns the first block the address is seen in, implementing // AddressInfo. func (a *btcAddress) FirstBlock() int32 { return a.firstBlock } // Imported returns the pub if the address was imported, or a chained address, // implementing AddressInfo. func (a *btcAddress) Imported() bool { return a.chainIndex == importedKeyChainIdx } // Change returns true if the address was created as a change address, // implementing AddressInfo. func (a *btcAddress) Change() bool { return a.flags.change } // Compressed returns true if the address backing key is compressed, // implementing AddressInfo. func (a *btcAddress) Compressed() bool { return a.flags.compressed } // SyncStatus returns a SyncStatus type for how the address is currently // synced. For an Unsynced type, the value is the recorded first seen // block height of the address. func (a *btcAddress) SyncStatus() SyncStatus { switch { case a.flags.unsynced && !a.flags.partialSync: return Unsynced(a.firstBlock) case a.flags.unsynced && a.flags.partialSync: return PartialSync(a.partialSyncHeight) default: return FullSync{} } } // PubKey returns the hex encoded pubkey for the address. Implementing // PubKeyAddress. func (a *btcAddress) PubKey() *btcec.PublicKey { return a.pubKey } func (a *btcAddress) pubKeyBytes() []byte { if a.Compressed() { return a.pubKey.SerializeCompressed() } return a.pubKey.SerializeUncompressed() } // ExportPubKey returns the public key associated with the address serialised as // a hex encoded string. Implemnts PubKeyAddress func (a *btcAddress) ExportPubKey() string { return hex.EncodeToString(a.pubKeyBytes()) } // PrivKey implements PubKeyAddress by returning the private key, or an error // if the key store is locked, watching only or the private key is missing. func (a *btcAddress) PrivKey() (*btcec.PrivateKey, error) { if a.store.flags.watchingOnly { return nil, ErrWatchingOnly } if !a.flags.hasPrivKey { return nil, errors.New("no private key for address") } // Key store must be unlocked to decrypt the private key. if a.store.isLocked() { return nil, ErrLocked } // Unlock address with key store secret. unlock returns a copy of // the clear text private key, and may be used safely even // during an address lock. privKeyCT, err := a.unlock(a.store.secret) if err != nil { return nil, err } return &btcec.PrivateKey{ PublicKey: *a.pubKey.ToECDSA(), D: new(big.Int).SetBytes(privKeyCT), }, nil } // ExportPrivKey exports the private key as a WIF for encoding as a string // in the Wallet Import Formt. func (a *btcAddress) ExportPrivKey() (*btcutil.WIF, error) { pk, err := a.PrivKey() if err != nil { return nil, err } // 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.store.netParams(), a.Compressed()) if err != nil { panic(err) } return wif, nil } // watchingCopy creates a copy of an address without a private key. // This is used to fill a watching a key store with addresses from a // normal key store. func (a *btcAddress) watchingCopy(s *Store) walletAddress { return &btcAddress{ store: s, address: a.address, flags: addrFlags{ hasPrivKey: false, hasPubKey: true, encrypted: false, createPrivKeyNextUnlock: false, compressed: a.flags.compressed, change: a.flags.change, unsynced: a.flags.unsynced, }, chaincode: a.chaincode, chainIndex: a.chainIndex, chainDepth: a.chainDepth, pubKey: a.pubKey, firstSeen: a.firstSeen, lastSeen: a.lastSeen, firstBlock: a.firstBlock, partialSyncHeight: a.partialSyncHeight, } } // setSyncStatus sets the address flags and possibly the partial sync height // depending on the type of s. func (a *btcAddress) setSyncStatus(s SyncStatus) { switch e := s.(type) { case Unsynced: a.flags.unsynced = true a.flags.partialSync = false a.partialSyncHeight = 0 case PartialSync: a.flags.unsynced = true a.flags.partialSync = true a.partialSyncHeight = int32(e) case FullSync: a.flags.unsynced = false a.flags.partialSync = false a.partialSyncHeight = 0 } } // 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 key store 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 unsynced bool partialSync 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 := io.ReadFull(r, 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 sf.unsynced = b[0]&(1<<6) != 0 sf.partialSync = b[0]&(1<<7) != 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 } if sf.unsynced { b[0] |= 1 << 6 } if sf.partialSync { b[0] |= 1 << 7 } n, err := w.Write(b[:]) return int64(n), err } // p2SHScript represents the variable length script entry in a key store. type p2SHScript []byte // ReadFrom implements the ReaderFrom interface by reading the P2SH script from // r in the format <4 bytes little endian length>