Add support for compressed pubkeys (used by default).

Wallets that include compressed pubkeys are no longer compatible with
armory, however, imported wallets from armory (using uncompressed
pubkeys) are still valid.
This commit is contained in:
Josh Rickmar 2013-11-04 11:50:32 -05:00
parent 18fb993d0b
commit f3408bad91
3 changed files with 163 additions and 79 deletions

View file

@ -226,12 +226,16 @@ func (w *BtcWallet) txToPairs(pairs map[string]uint64, fee uint64, minconf int)
if err != nil {
return nil, fmt.Errorf("cannot get address key: %v", err)
}
ai, err := w.GetAddressInfo(addrstr)
if err != nil {
return nil, fmt.Errorf("cannot get address info: %v", err)
}
// TODO(jrick): we want compressed pubkeys. Switch wallet to
// generate addresses from the compressed key. This will break
// armory wallet compat but oh well.
sigscript, err := btcscript.SignatureScript(msgtx, i,
ip.Subscript, btcscript.SigHashAll, privkey, false)
ip.Subscript, btcscript.SigHashAll, privkey, ai.Compressed)
if err != nil {
return nil, fmt.Errorf("cannot create sigscript: %s", err)
}

View file

@ -31,7 +31,6 @@ import (
"github.com/conformal/btcec"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
"github.com/davecgh/go-spew/spew"
"hash"
"io"
"math/big"
@ -39,8 +38,6 @@ import (
"time"
)
var _ = spew.Dump
const (
// Length in bytes of KDF output.
kdfOutputBytes = 32
@ -128,20 +125,20 @@ func calcSha512(buf []byte) []byte {
return calcHash(buf, sha512.New())
}
// First byte in uncompressed pubKey field.
const pubkeyUncompressed = 0x4
// pubkeyFromPrivkey creates a 65-byte encoded pubkey based on a
// 32-byte privkey.
//
// TODO(jrick): this must be changed to a compressed pubkey.
func pubkeyFromPrivkey(privkey []byte) (pubkey []byte) {
// 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) {
x, y := btcec.S256().ScalarBaseMult(privkey)
pub := (*btcec.PublicKey)(&ecdsa.PublicKey{
Curve: btcec.S256(),
X: x,
Y: y,
})
if compress {
return pub.SerializeCompressed()
}
return pub.SerializeUncompressed()
}
@ -195,22 +192,21 @@ func Key(passphrase []byte, params *kdfParameters) []byte {
return masterKey
}
// leftPad returns a new slice of length size. The contents of input are right
// aligned in the new slice.
func leftPad(input []byte, size int) (out []byte) {
n := len(input)
if n > size {
n = size
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)
}
out = make([]byte, size)
copy(out[len(out)-n:], input)
return
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 65 bytes or nil (in which case it
// is generated by the privkey).
// bytes long, and pubkey may either be 33 bytes, 65 bytes or nil (in
// which case it is generated by the privkey).
func ChainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) {
if len(privkey) != 32 {
return nil, fmt.Errorf("invalid privkey length %d (must be 32)",
@ -221,8 +217,8 @@ func ChainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) {
len(chaincode))
}
if pubkey == nil {
pubkey = pubkeyFromPrivkey(privkey)
} else if len(pubkey) != 65 {
pubkey = pubkeyFromPrivkey(privkey, true)
} else if !(len(pubkey) == 65 || len(pubkey) == 33) {
return nil, fmt.Errorf("invalid pubkey length %d", len(pubkey))
}
@ -243,7 +239,7 @@ func ChainedPrivKey(privkey, pubkey, chaincode []byte) ([]byte, error) {
t := new(big.Int).Mul(chainXor, privint)
b := t.Mod(t, btcec.S256().N).Bytes()
return leftPad(b, 32), nil
return pad(32, b), nil
}
type varEntries []io.WriterTo
@ -320,6 +316,10 @@ func (v *varEntries) ReadFrom(r io.Reader) (n int64, err error) {
}
}
type addressHashKey string
type transactionHashKey string
type comment []byte
// 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.
@ -341,16 +341,16 @@ type Wallet struct {
syncedBlockHeight int32
syncedBlockHash btcwire.ShaHash
addrMap map[[ripemd160.Size]byte]*btcAddress
addrCommentMap map[[ripemd160.Size]byte]*[]byte
txCommentMap map[[sha256.Size]byte]*[]byte
addrMap map[addressHashKey]*btcAddress
addrCommentMap map[addressHashKey]comment
txCommentMap map[transactionHashKey]comment
// These are not serialized
key struct {
sync.Mutex
secret []byte
}
chainIdxMap map[int64]*[ripemd160.Size]byte
chainIdxMap map[int64]addressHashKey
lastChainIdx int64
}
@ -409,22 +409,22 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, cre
keyGenerator: *root,
syncedBlockHeight: createdAt.Height,
syncedBlockHash: createdAt.Hash,
addrMap: make(map[[ripemd160.Size]byte]*btcAddress),
addrCommentMap: make(map[[ripemd160.Size]byte]*[]byte),
txCommentMap: make(map[[sha256.Size]byte]*[]byte),
chainIdxMap: make(map[int64]*[ripemd160.Size]byte),
addrMap: make(map[addressHashKey]*btcAddress),
addrCommentMap: make(map[addressHashKey]comment),
txCommentMap: make(map[transactionHashKey]comment),
chainIdxMap: make(map[int64]addressHashKey),
lastChainIdx: pregenerated - 1,
}
// Add root address to maps.
w.addrMap[w.keyGenerator.pubKeyHash] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = &w.keyGenerator.pubKeyHash
w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = addressHashKey(w.keyGenerator.pubKeyHash[:])
// Pre-generate 100 encrypted addresses and add to maps.
addr := &w.keyGenerator
cc := addr.chaincode[:]
for i := 0; i < pregenerated; i++ {
privkey, err := ChainedPrivKey(addr.privKeyCT, addr.pubKey[:], cc)
privkey, err := ChainedPrivKey(addr.privKeyCT, addr.pubKey, cc)
if err != nil {
return nil, err
}
@ -435,9 +435,9 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet, cre
if err = newaddr.encrypt(aeskey); err != nil {
return nil, err
}
w.addrMap[newaddr.pubKeyHash] = newaddr
w.addrMap[addressHashKey(newaddr.pubKeyHash[:])] = newaddr
newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = &newaddr.pubKeyHash
w.chainIdxMap[newaddr.chainIndex] = addressHashKey(newaddr.pubKeyHash[:])
copy(newaddr.chaincode[:], cc) // armory does this.. but why?
addr = newaddr
}
@ -458,10 +458,10 @@ func (w *Wallet) Name() string {
func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
var read int64
w.addrMap = make(map[[ripemd160.Size]byte]*btcAddress)
w.addrCommentMap = make(map[[ripemd160.Size]byte]*[]byte)
w.chainIdxMap = make(map[int64]*[ripemd160.Size]byte)
w.txCommentMap = make(map[[sha256.Size]byte]*[]byte)
w.addrMap = make(map[addressHashKey]*btcAddress)
w.addrCommentMap = make(map[addressHashKey]comment)
w.chainIdxMap = make(map[int64]addressHashKey)
w.txCommentMap = make(map[transactionHashKey]comment)
var id [8]byte
var appendedEntries varEntries
@ -505,8 +505,8 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
}
// Add root address to address map
w.addrMap[w.keyGenerator.pubKeyHash] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = &w.keyGenerator.pubKeyHash
w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator
w.chainIdxMap[w.keyGenerator.chainIndex] = addressHashKey(w.keyGenerator.pubKeyHash[:])
// Fill unserializied fields.
wts := ([]io.WriterTo)(appendedEntries)
@ -514,17 +514,17 @@ func (w *Wallet) ReadFrom(r io.Reader) (n int64, err error) {
switch wt.(type) {
case *addrEntry:
e := wt.(*addrEntry)
w.addrMap[e.pubKeyHash160] = &e.addr
w.chainIdxMap[e.addr.chainIndex] = &e.pubKeyHash160
w.addrMap[addressHashKey(e.pubKeyHash160[:])] = &e.addr
w.chainIdxMap[e.addr.chainIndex] = addressHashKey(e.pubKeyHash160[:])
if w.lastChainIdx < e.addr.chainIndex {
w.lastChainIdx = e.addr.chainIndex
}
case *addrCommentEntry:
e := wt.(*addrCommentEntry)
w.addrCommentMap[e.pubKeyHash160] = &e.comment
w.addrCommentMap[addressHashKey(e.pubKeyHash160[:])] = comment(e.comment)
case *txCommentEntry:
e := wt.(*txCommentEntry)
w.txCommentMap[e.txHash] = &e.comment
w.txCommentMap[transactionHashKey(e.txHash[:])] = comment(e.comment)
default:
return n, errors.New("unknown appended entry")
}
@ -539,25 +539,25 @@ func (w *Wallet) WriteTo(wtr io.Writer) (n int64, err error) {
wts := make([]io.WriterTo, len(w.addrMap)-1)
for hash, addr := range w.addrMap {
if addr.chainIndex != -1 { // ignore root address
e := &addrEntry{
pubKeyHash160: hash,
addr: *addr,
e := addrEntry{
addr: *addr,
}
wts[addr.chainIndex] = e
copy(e.pubKeyHash160[:], []byte(hash))
wts[addr.chainIndex] = &e
}
}
for hash, comment := range w.addrCommentMap {
e := &addrCommentEntry{
pubKeyHash160: hash,
comment: *comment,
comment: []byte(comment),
}
copy(e.pubKeyHash160[:], []byte(hash))
wts = append(wts, e)
}
for hash, comment := range w.txCommentMap {
e := &txCommentEntry{
txHash: hash,
comment: *comment,
comment: []byte(comment),
}
copy(e.txHash[:], []byte(hash))
wts = append(wts, e)
}
appendedEntries := varEntries(wts)
@ -663,7 +663,7 @@ func (w *Wallet) NextUnusedAddress() (string, error) {
if err != nil {
return "", errors.New("cannot find generated address")
}
addr := w.addrMap[*new160]
addr := w.addrMap[new160]
if addr == nil {
return "", errors.New("cannot find generated address")
}
@ -687,10 +687,7 @@ func (w *Wallet) GetAddressKey(addr string) (key *ecdsa.PrivateKey, err error) {
return nil, errors.New("wallet and address networks mismatch")
}
addrHash := new([ripemd160.Size]byte)
copy(addrHash[:], addr160)
btcaddr, ok := w.addrMap[*addrHash]
btcaddr, ok := w.addrMap[addressHashKey(addr160)]
if !ok {
return nil, errors.New("address not in wallet")
}
@ -702,7 +699,7 @@ func (w *Wallet) GetAddressKey(addr string) (key *ecdsa.PrivateKey, err error) {
return nil, errors.New("no private key for address")
}
pubkey, err := btcec.ParsePubKey(btcaddr.pubKey[:], btcec.S256())
pubkey, err := btcec.ParsePubKey(btcaddr.pubKey, btcec.S256())
if err != nil {
return nil, err
}
@ -719,6 +716,26 @@ func (w *Wallet) GetAddressKey(addr string) (key *ecdsa.PrivateKey, err error) {
return key, nil
}
func (w *Wallet) GetAddressInfo(addr string) (*AddressInfo, error) {
addr160, net, err := btcutil.DecodeAddress(addr)
if err != nil {
return nil, err
}
switch {
case net == btcutil.MainNetAddr && w.net != btcwire.MainNet:
fallthrough
case net == btcutil.TestNetAddr && w.net != btcwire.TestNet:
return nil, errors.New("wallet and address networks mismatch")
}
btcaddr, ok := w.addrMap[addressHashKey(addr160)]
if !ok {
return nil, errors.New("address not in wallet")
}
return btcaddr.info(w.net)
}
// Net returns the bitcoin network identifier for this wallet.
func (w *Wallet) Net() btcwire.BitcoinNet {
return w.net
@ -747,9 +764,9 @@ func (w *Wallet) CreatedAt() int32 {
return w.keyGenerator.firstBlock
}
func (w *Wallet) addr160ForIdx(idx int64) (*[ripemd160.Size]byte, error) {
func (w *Wallet) addr160ForIdx(idx int64) (addressHashKey, error) {
if idx > w.lastChainIdx {
return nil, errors.New("chain index out of range")
return "", errors.New("chain index out of range")
}
return w.chainIdxMap[idx], nil
}
@ -759,6 +776,7 @@ func (w *Wallet) addr160ForIdx(idx int64) (*[ripemd160.Size]byte, error) {
type AddressInfo struct {
Address string
FirstBlock int32
Compressed bool
}
// GetActiveAddresses returns all wallet addresses that have been
@ -771,7 +789,7 @@ func (w *Wallet) GetActiveAddresses() []*AddressInfo {
if err != nil {
return addrs
}
addr := w.addrMap[*addr160]
addr := w.addrMap[addr160]
info, err := addr.info(w.Net())
if err == nil {
addrs = append(addrs, info)
@ -805,9 +823,11 @@ func (wf *walletFlags) WriteTo(w io.Writer) (n int64, err error) {
}
type addrFlags struct {
hasPrivKey bool
hasPubKey bool
encrypted bool
hasPrivKey bool
hasPubKey bool
encrypted bool
createPrivKeyNextUnlock bool // unimplemented in btcwallet
compressed bool
}
func (af *addrFlags) ReadFrom(r io.Reader) (n int64, err error) {
@ -829,6 +849,12 @@ func (af *addrFlags) ReadFrom(r io.Reader) (n int64, err error) {
return n, errors.New("address flag specifies unencrypted address")
}
af.encrypted = true
if b[0]&(1<<3) != 0 {
af.createPrivKeyNextUnlock = true
}
if b[0]&(1<<4) != 0 {
af.compressed = true
}
return n, nil
}
@ -846,6 +872,12 @@ func (af *addrFlags) WriteTo(w io.Writer) (n int64, err error) {
return n, errors.New("address must be encrypted")
}
b[0] |= 1 << 2
if af.createPrivKeyNextUnlock {
b[0] |= 1 << 3
}
if af.compressed {
b[0] |= 1 << 4
}
return binaryWrite(w, binary.LittleEndian, b)
}
@ -858,7 +890,7 @@ type btcAddress struct {
chainDepth int64 // currently unused (will use when extending a locked wallet)
initVector [16]byte
privKey [32]byte
pubKey [65]byte
pubKey publicKey
firstSeen int64
lastSeen int64
firstBlock int32
@ -866,6 +898,54 @@ type btcAddress struct {
privKeyCT []byte // non-nil if unlocked.
}
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))
}
// 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).
@ -885,14 +965,14 @@ func newBtcAddress(privkey, iv []byte, bs *BlockStamp) (addr *btcAddress, err er
flags: addrFlags{
hasPrivKey: true,
hasPubKey: true,
compressed: true,
},
firstSeen: time.Now().Unix(),
firstBlock: bs.Height,
}
copy(addr.initVector[:], iv)
pub := pubkeyFromPrivkey(privkey)
copy(addr.pubKey[:], pub)
copy(addr.pubKeyHash[:], calcHash160(pub))
addr.pubKey = pubkeyFromPrivkey(privkey, true)
copy(addr.pubKeyHash[:], calcHash160(addr.pubKey))
return addr, nil
}
@ -969,7 +1049,7 @@ func (a *btcAddress) ReadFrom(r io.Reader) (n int64, err error) {
{a.chaincode[:], chkChaincode},
{a.initVector[:], chkInitVector},
{a.privKey[:], chkPrivKey},
{a.pubKey[:], chkPubKey},
{a.pubKey, chkPubKey},
}
for i := range checks {
if err = verifyAndFix(checks[i].data, checks[i].chk); err != nil {
@ -997,7 +1077,7 @@ func (a *btcAddress) WriteTo(w io.Writer) (n int64, err error) {
&a.privKey,
walletHash(a.privKey[:]),
&a.pubKey,
walletHash(a.pubKey[:]),
walletHash(a.pubKey),
&a.firstSeen,
&a.lastSeen,
&a.firstBlock,
@ -1067,7 +1147,7 @@ func (a *btcAddress) unlock(key []byte) error {
ct := make([]byte, 32)
aesDecrypter.XORKeyStream(ct, a.privKey[:])
pubKey, err := btcec.ParsePubKey(a.pubKey[:], btcec.S256())
pubKey, err := btcec.ParsePubKey(a.pubKey, btcec.S256())
if err != nil {
return fmt.Errorf("cannot parse pubkey: %s", err)
}
@ -1102,6 +1182,7 @@ func (a *btcAddress) info(net btcwire.BitcoinNet) (*AddressInfo, error) {
return &AddressInfo{
Address: address,
FirstBlock: a.firstBlock,
Compressed: a.flags.compressed,
}, nil
}
@ -1369,8 +1450,7 @@ func (e *txCommentEntry) ReadFrom(r io.Reader) (n int64, err error) {
return n + read, err
}
type deletedEntry struct {
}
type deletedEntry struct{}
func (e *deletedEntry) ReadFrom(r io.Reader) (n int64, err error) {
var read int64

View file

@ -36,7 +36,7 @@ func TestBtcAddressSerializer(t *testing.T) {
key := Key([]byte("banana"), kdfp)
privKey := make([]byte, 32)
rand.Read(privKey)
addr, err := newBtcAddress(privKey, nil)
addr, err := newBtcAddress(privKey, nil, &BlockStamp{})
if err != nil {
t.Error(err.Error())
return