/* * Copyright (c) 2013 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 wallet import ( "bytes" "code.google.com/p/go.crypto/ripemd160" "crypto/aes" "crypto/cipher" "crypto/sha256" "crypto/sha512" "encoding/binary" "errors" "fmt" "github.com/conformal/btcec" "github.com/conformal/btcutil" "github.com/conformal/btcwire" "io" "sync" ) const ( // 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 ) // Possible errors when dealing with wallets. var ( ChecksumErr = errors.New("Checksum mismatch") MalformedEntryErr = errors.New("Malformed entry") WalletDoesNotExist = errors.New("Non-existant wallet") ) type entryHeader byte const ( addrCommentHeader entryHeader = 1 << iota txCommentHeader deletedHeader 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 = r.Read(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) { var 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 } 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] } // Key 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 Key(passphrase, salt []byte, memReqts uint64, nIters uint32) []byte { masterKey := passphrase for i := uint32(0); i < nIters; i++ { masterKey = keyOneIter(masterKey, salt, memReqts) } return masterKey } type varEntries []io.WriterTo func (v *varEntries) WriteTo(w io.Writer) (n int64, err error) { ss := ([]io.WriterTo)(*v) 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 = nil wts := ([]io.WriterTo)(*v) // 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 = nil switch header { case addrHeader: var entry addrEntry 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 { return n + read, err } n += read wt = &entry case txCommentHeader: var entry txCommentEntry if read, err = entry.ReadFrom(r); err != nil { return n + read, err } n += read wt = &entry case deletedHeader: var entry deletedEntry if read, err = entry.ReadFrom(r); err != nil { return n + read, err } n += read default: return n, fmt.Errorf("Unknown entry header: %d", uint8(header)) } if wt != nil { wts = append(wts, wt) *v = wts } } return n, nil } // Wallet represents an btcd/Armory wallet in memory. It // implements the io.ReaderFrom and io.WriterTo interfaces to read // from and write to any type of byte streams, including files. // TODO(jrick) remove as many more magic numbers as possible. type Wallet struct { fileID [8]byte version uint32 netMagicBytes [4]byte walletFlags [8]byte uniqID [6]byte createDate [8]byte name [32]byte description [256]byte highestUsed int64 kdfParams kdfParameters encryptionParams [256]byte keyGenerator btcAddress appendedEntries varEntries // These are not serialized key struct { sync.Mutex secret []byte } addrMap map[[ripemd160.Size]byte]*btcAddress addrCommentMap map[[ripemd160.Size]byte]*[]byte chainIdxMap map[int64]*[ripemd160.Size]byte txCommentMap map[[sha256.Size]byte]*[]byte lastChainIdx int64 } // WriteTo serializes a Wallet and writes it to a io.Writer, // returning the number of bytes written and any errors encountered. func (wallet *Wallet) WriteTo(w io.Writer) (n int64, err error) { // 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{}{ &wallet.fileID, &wallet.version, &wallet.netMagicBytes, &wallet.walletFlags, &wallet.uniqID, &wallet.createDate, &wallet.name, &wallet.description, &wallet.highestUsed, &wallet.kdfParams, &wallet.encryptionParams, &wallet.keyGenerator, make([]byte, 1024), &wallet.appendedEntries, } var read int64 for _, data := range datas { if s, ok := data.(io.WriterTo); ok { read, err = s.WriteTo(w) } else { read, err = binaryWrite(w, binary.LittleEndian, data) } n += read if err != nil { return n, err } } return n, nil } // ReadFrom reads data from a io.Reader and saves it to a Wallet, // returning the number of bytes read and any errors encountered. func (wallet *Wallet) ReadFrom(r io.Reader) (n int64, err error) { var read int64 wallet.addrMap = make(map[[ripemd160.Size]byte]*btcAddress) wallet.addrCommentMap = make(map[[ripemd160.Size]byte]*[]byte) wallet.chainIdxMap = make(map[int64]*[ripemd160.Size]byte) wallet.txCommentMap = make(map[[sha256.Size]byte]*[]byte) // 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{}{ &wallet.fileID, &wallet.version, &wallet.netMagicBytes, &wallet.walletFlags, &wallet.uniqID, &wallet.createDate, &wallet.name, &wallet.description, &wallet.highestUsed, &wallet.kdfParams, &wallet.encryptionParams, &wallet.keyGenerator, make([]byte, 1024), &wallet.appendedEntries, } for _, data := range datas { var err error if rf, ok := data.(io.ReaderFrom); ok { read, err = rf.ReadFrom(r) } else { read, err = binaryRead(r, binary.LittleEndian, data) } n += read if err != nil { return n, err } } // Add root address to address map wallet.addrMap[wallet.keyGenerator.pubKeyHash] = &wallet.keyGenerator wallet.chainIdxMap[wallet.keyGenerator.chainIndex] = &wallet.keyGenerator.pubKeyHash // Fill unserializied fields. wts := ([]io.WriterTo)(wallet.appendedEntries) for _, wt := range wts { switch wt.(type) { case *addrEntry: e := wt.(*addrEntry) wallet.addrMap[e.pubKeyHash160] = &e.addr wallet.chainIdxMap[e.addr.chainIndex] = &e.pubKeyHash160 if wallet.lastChainIdx < e.addr.chainIndex { wallet.lastChainIdx = e.addr.chainIndex } case *addrCommentEntry: e := wt.(*addrCommentEntry) wallet.addrCommentMap[e.pubKeyHash160] = &e.comment case *txCommentEntry: e := wt.(*txCommentEntry) wallet.txCommentMap[e.txHash] = &e.comment default: return n, errors.New("Unknown appended entry") } } return n, nil } // Unlock derives an AES key from passphrase and wallet's KDF // parameters and unlocks the root key of the wallet. func (wallet *Wallet) Unlock(passphrase []byte) error { key := Key(passphrase, wallet.kdfParams.salt[:], wallet.kdfParams.mem, wallet.kdfParams.nIter) // Attempt unlocking root address if err := wallet.keyGenerator.unlock(key); err != nil { return err } else { wallet.key.Lock() wallet.key.secret = key wallet.key.Unlock() return nil } } // Lock does a best effort to zero the keys. // Being go this might not succeed but try anway. // TODO(jrick) func (wallet *Wallet) Lock() (err error) { wallet.key.Lock() if wallet.key.secret != nil { for i, _ := range wallet.key.secret { wallet.key.secret[i] = 0 } wallet.key.secret = nil } else { err = fmt.Errorf("Wallet already locked") } wallet.key.Unlock() return err } // Returns wallet version as string and int. // TODO(jrick) func (wallet *Wallet) Version() (string, int) { return "", 0 } // TODO(jrick) func (wallet *Wallet) NextUnusedAddress() string { _ = wallet.lastChainIdx wallet.highestUsed++ new160, err := wallet.addr160ForIdx(wallet.highestUsed) if err != nil { return "" } addr := wallet.addrMap[*new160] if addr != nil { return btcutil.Base58Encode(addr.pubKeyHash[:]) } else { return "" } } func (wallet *Wallet) addr160ForIdx(idx int64) (*[ripemd160.Size]byte, error) { if idx > wallet.lastChainIdx { return nil, errors.New("Chain index out of range") } return wallet.chainIdxMap[idx], nil } func (wallet *Wallet) GetActiveAddresses() []string { addrs := []string{} for i := int64(-1); i <= wallet.highestUsed; i++ { addr160, err := wallet.addr160ForIdx(i) if err != nil { return addrs } addr := wallet.addrMap[*addr160] addrs = append(addrs, btcutil.Base58Encode(addr.pubKeyHash[:])) } return addrs } /* func OpenWallet(file string) (*Wallet, error) { } */ type btcAddress struct { pubKeyHash [ripemd160.Size]byte version uint32 flags uint64 chaincode [32]byte chainIndex int64 chainDepth int64 initVector [16]byte privKey [32]byte pubKey [65]byte firstSeen uint64 lastSeen uint64 firstBlock uint32 lastBlock uint32 privKeyCT []byte // Points to clear text private key if unlocked. } // ReadFrom reads an encrypted address from an io.Reader. func (addr *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 // Read serialized wallet into addr fields and checksums. datas := []interface{}{ &addr.pubKeyHash, &chkPubKeyHash, &addr.version, &addr.flags, &addr.chaincode, &chkChaincode, &addr.chainIndex, &addr.chainDepth, &addr.initVector, &chkInitVector, &addr.privKey, &chkPrivKey, &addr.pubKey, &chkPubKey, &addr.firstSeen, &addr.lastSeen, &addr.firstBlock, &addr.lastBlock, } for _, data := range datas { if read, err = binaryRead(r, binary.LittleEndian, data); err != nil { return n + read, err } n += read } // Verify checksums, correct errors where possible. checks := []struct { data []byte chk uint32 }{ {addr.pubKeyHash[:], chkPubKeyHash}, {addr.chaincode[:], chkChaincode}, {addr.initVector[:], chkInitVector}, {addr.privKey[:], chkPrivKey}, {addr.pubKey[:], chkPubKey}, } for i, _ := range checks { if err = verifyAndFix(checks[i].data, checks[i].chk); err != nil { return n, err } } // TODO(jrick) verify encryption return n, nil } func (addr *btcAddress) WriteTo(w io.Writer) (n int64, err error) { var written int64 datas := []interface{}{ &addr.pubKeyHash, walletHash(addr.pubKeyHash[:]), &addr.version, &addr.flags, &addr.chaincode, walletHash(addr.chaincode[:]), &addr.chainIndex, &addr.chainDepth, &addr.initVector, walletHash(addr.initVector[:]), &addr.privKey, walletHash(addr.privKey[:]), &addr.pubKey, walletHash(addr.pubKey[:]), &addr.firstSeen, &addr.lastSeen, &addr.firstBlock, &addr.lastBlock, } for _, data := range datas { written, err = binaryWrite(w, binary.LittleEndian, data) if err != nil { return n + written, err } n += written } return n, nil } func (addr *btcAddress) unlock(key []byte) error { aesBlockDecrypter, err := aes.NewCipher([]byte(key)) if err != nil { return err } aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, addr.initVector[:]) ct := make([]byte, 32) aesDecrypter.XORKeyStream(ct, addr.privKey[:]) addr.privKeyCT = ct pubKey, err := btcec.ParsePubKey(addr.pubKey[:], btcec.S256()) if err != nil { return fmt.Errorf("ParsePubKey faild:", err) } x, y := btcec.S256().ScalarBaseMult(addr.privKeyCT) if x.Cmp(pubKey.X) != 0 || y.Cmp(pubKey.Y) != 0 { return fmt.Errorf("decryption failed") } return nil } // TODO(jrick) func (addr *btcAddress) changeEncryptionKey(oldkey, newkey []byte) error { return nil } // TODO(jrick) func (addr *btcAddress) verifyEncryptionKey() { } // TODO(jrick) func newRandomAddress(key []byte) *btcAddress { addr := &btcAddress{} return addr } func walletHash(b []byte) uint32 { sum := btcwire.DoubleSha256(b) return binary.LittleEndian.Uint32(sum) } // TODO(jrick) add error correction. func verifyAndFix(b []byte, chk uint32) error { if walletHash(b) != chk { return ChecksumErr } return nil } type kdfParameters struct { mem uint64 nIter uint32 salt [32]byte } func (params *kdfParameters) WriteTo(w io.Writer) (n int64, err error) { var written int64 memBytes := make([]byte, 8) nIterBytes := make([]byte, 4) binary.LittleEndian.PutUint64(memBytes, params.mem) binary.LittleEndian.PutUint32(nIterBytes, params.nIter) chkedBytes := append(memBytes, nIterBytes...) chkedBytes = append(chkedBytes, params.salt[:]...) datas := []interface{}{ ¶ms.mem, ¶ms.nIter, ¶ms.salt, walletHash(chkedBytes), make([]byte, 256-(binary.Size(params)+4)), // padding } for _, data := range datas { if written, err = binaryWrite(w, binary.LittleEndian, data); err != nil { return n + written, err } n += written } return n, nil } func (params *kdfParameters) ReadFrom(r io.Reader) (n int64, err error) { var read int64 // These must be read in but are not saved directly to params. chkedBytes := make([]byte, 44) var chk uint32 padding := make([]byte, 256-(binary.Size(params)+4)) datas := []interface{}{ chkedBytes, &chk, padding, } for _, data := range datas { if read, err = binaryRead(r, binary.LittleEndian, data); err != nil { return n + read, err } n += read } // Verify checksum if err = verifyAndFix(chkedBytes, chk); err != nil { return n, err } // Write params buf := bytes.NewBuffer(chkedBytes) datas = []interface{}{ ¶ms.mem, ¶ms.nIter, ¶ms.salt, } for _, data := range datas { if err = binary.Read(buf, binary.LittleEndian, data); err != nil { return n, err } } return n, nil } type addrEntry struct { pubKeyHash160 [ripemd160.Size]byte addr btcAddress } func (e *addrEntry) WriteTo(w io.Writer) (n int64, err error) { var written int64 // Write header if written, err = binaryWrite(w, binary.LittleEndian, addrHeader); err != nil { return n + written, err } n += written // Write hash if written, err = binaryWrite(w, binary.LittleEndian, &e.pubKeyHash160); err != nil { return n + written, err } n += written // Write btcAddress written, err = e.addr.WriteTo(w) return n + written, err } func (e *addrEntry) ReadFrom(r io.Reader) (n int64, err error) { var read int64 if read, err = binaryRead(r, binary.LittleEndian, &e.pubKeyHash160); err != nil { return n + read, err } n += read read, err = e.addr.ReadFrom(r) return n + read, err } type addrCommentEntry struct { pubKeyHash160 [ripemd160.Size]byte comment []byte } func (e *addrCommentEntry) WriteTo(w io.Writer) (n int64, err error) { var written int64 // Comments shall not overflow their entry. if len(e.comment) > maxCommentLen { return n, MalformedEntryErr } // Write header if written, err = binaryWrite(w, binary.LittleEndian, addrCommentHeader); err != nil { return n + written, err } n += written // Write hash if written, err = binaryWrite(w, binary.LittleEndian, &e.pubKeyHash160); err != nil { return n + written, err } n += written // Write length if written, err = binaryWrite(w, binary.LittleEndian, uint16(len(e.comment))); err != nil { return n + written, err } n += written // Write comment written, err = binaryWrite(w, binary.LittleEndian, e.comment) return n + written, err } func (e *addrCommentEntry) ReadFrom(r io.Reader) (n int64, err error) { var read int64 if read, err = binaryRead(r, binary.LittleEndian, &e.pubKeyHash160); err != nil { return n + read, err } n += read var clen uint16 if read, err = binaryRead(r, binary.LittleEndian, &clen); err != nil { return n + read, err } n += read e.comment = make([]byte, clen) read, err = binaryRead(r, binary.LittleEndian, e.comment) return n + read, err } type txCommentEntry struct { txHash [sha256.Size]byte comment []byte } func (e *txCommentEntry) WriteTo(w io.Writer) (n int64, err error) { var written int64 // Comments shall not overflow their entry. if len(e.comment) > maxCommentLen { return n, MalformedEntryErr } // Write header if written, err = binaryWrite(w, binary.LittleEndian, txCommentHeader); err != nil { return n + written, err } n += written // Write length if written, err = binaryWrite(w, binary.LittleEndian, uint16(len(e.comment))); err != nil { return n + written, err } // Write comment written, err = binaryWrite(w, binary.LittleEndian, e.comment) return n + written, err } func (e *txCommentEntry) ReadFrom(r io.Reader) (n int64, err error) { var read int64 if read, err = binaryRead(r, binary.LittleEndian, &e.txHash); err != nil { return n + read, err } n += read var clen uint16 if read, err = binaryRead(r, binary.LittleEndian, &clen); err != nil { return n + read, err } n += read e.comment = make([]byte, clen) read, err = binaryRead(r, binary.LittleEndian, e.comment) return n + read, err } type deletedEntry struct { } func (e *deletedEntry) ReadFrom(r io.Reader) (n int64, err error) { var read int64 var ulen uint16 if read, err = binaryRead(r, binary.LittleEndian, &ulen); err != nil { return n + read, err } n += read unused := make([]byte, ulen) if nRead, err := r.Read(unused); err == io.EOF { return n + int64(nRead), nil } else { return n + int64(nRead), err } } type UTXOStore struct { } type utxo struct { pubKeyHash [ripemd160.Size]byte *btcwire.TxOut block int64 }