lbcutil/wif.go
Josh Rickmar 5bcc7790a0 Update Address and WIF APIs to use btcnet.
This change removes all occurances of btcwire.BitcoinNet from exported
APIs, replacing each with *btcnet.Params.  This simplifies the logic
for verifying string encodings of addresses and WIF private keys which
use leading identifier numbers to differentiate between address types
and the network they are intended for.  It also allows the use of this
package for non-standard networks (not mainnet, regtest, or testnet3)
and future proofs it for the possible addition of future testnet
networks.

To update across this change, replace each btcwire.BitcoinNet
parameter with the associated *btcnet.Params.  For the standard
networks, these changes are:

  btcwire.MainNet  -> &btcnet.MainNetParams
  btcwire.TestNet  -> &btcnet.RegressionNetParams
  btcwire.TestNet3 -> &btcnet.TestNet3Params

ok @davecgh
2014-05-27 17:08:45 -05:00

169 lines
6.1 KiB
Go

// Copyright (c) 2013, 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcutil
import (
"bytes"
"errors"
"github.com/conformal/btcec"
"github.com/conformal/btcnet"
"github.com/conformal/btcwire"
)
// ErrMalformedPrivateKey describes an error where a WIF-encoded private
// key cannot be decoded due to being improperly formatted. This may occur
// if the byte length is incorrect or an unexpected magic number was
// encountered.
var ErrMalformedPrivateKey = errors.New("malformed private key")
// compressMagic is the magic byte used to identify a WIF encoding for
// an address created from a compressed serialized public key.
const compressMagic byte = 0x01
// WIF contains the individual components described by the Wallet Import Format
// (WIF). A WIF string is typically used to represent a private key and its
// associated address in a way that may be easily copied and imported into or
// exported from wallet software. WIF strings may be decoded into this
// structure by calling DecodeWIF or created with a user-provided private key
// by calling NewWIF.
type WIF struct {
// PrivKey is the private key being imported or exported.
PrivKey *btcec.PrivateKey
// CompressPubKey specifies whether the address controlled by the
// imported or exported private key was created by hashing a
// compressed (33-byte) serialized public key, rather than an
// uncompressed (65-byte) one.
CompressPubKey bool
// netID is the bitcoin network identifier byte used when
// WIF encoding the private key.
netID byte
}
// NewWIF creates a new WIF structure to export an address and its private key
// as a string encoded in the Wallet Import Format. The compress argument
// specifies whether the address intended to be imported or exported was created
// by serializing the public key compressed rather than uncompressed.
func NewWIF(privKey *btcec.PrivateKey, net *btcnet.Params, compress bool) (*WIF, error) {
if net == nil {
return nil, errors.New("no network")
}
return &WIF{privKey, compress, net.PrivateKeyID}, nil
}
// IsForNet returns whether or not the decoded WIF structure is associated
// with the passed bitcoin network.
func (w *WIF) IsForNet(net *btcnet.Params) bool {
return w.netID == net.PrivateKeyID
}
// DecodeWIF creates a new WIF structure by decoding the string encoding of
// the import format.
//
// The WIF string must be a base58-encoded string of the following byte
// sequence:
//
// * 1 byte to identify the network, must be 0x80 for mainnet or 0xef for
// either testnet3 or the regression test network
// * 32 bytes of a binary-encoded, big-endian, zero-padded private key
// * Optional 1 byte (equal to 0x01) if the address being imported or exported
// was created by taking the RIPEMD160 after SHA256 hash of a serialized
// compressed (33-byte) public key
// * 4 bytes of checksum, must equal the first four bytes of the double SHA256
// of every byte before the checksum in this sequence
//
// If the base58-decoded byte sequence does not match this, DecodeWIF will
// return a non-nil error. ErrMalformedPrivateKey is returned when the WIF
// is of an impossible length or the expected compressed pubkey magic number
// does not equal the expected value of 0x01. ErrChecksumMismatch is returned
// if the expected WIF checksum does not match the calculated checksum.
func DecodeWIF(wif string) (*WIF, error) {
decoded := Base58Decode(wif)
decodedLen := len(decoded)
var compress bool
// Length of base58 decoded WIF must be 32 bytes + an optional 1 byte
// (0x01) if compressed, plus 1 byte for netID + 4 bytes of checksum.
switch decodedLen {
case 1 + btcec.PrivKeyBytesLen + 1 + 4:
if decoded[33] != compressMagic {
return nil, ErrMalformedPrivateKey
}
compress = true
case 1 + btcec.PrivKeyBytesLen + 4:
compress = false
default:
return nil, ErrMalformedPrivateKey
}
// Checksum is first four bytes of double SHA256 of the identifier byte
// and privKey. Verify this matches the final 4 bytes of the decoded
// private key.
var tosum []byte
if compress {
tosum = decoded[:1+btcec.PrivKeyBytesLen+1]
} else {
tosum = decoded[:1+btcec.PrivKeyBytesLen]
}
cksum := btcwire.DoubleSha256(tosum)[:4]
if !bytes.Equal(cksum, decoded[decodedLen-4:]) {
return nil, ErrChecksumMismatch
}
netID := decoded[0]
privKeyBytes := decoded[1 : 1+btcec.PrivKeyBytesLen]
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes)
return &WIF{privKey, compress, netID}, nil
}
// String creates the Wallet Import Format string encoding of a WIF structure.
// See DecodeWIF for a detailed breakdown of the format and requirements of
// a valid WIF string.
func (w *WIF) String() string {
// Precalculate size. Maximum number of bytes before base58 encoding
// is one byte for the network, 32 bytes of private key, possibly one
// extra byte if the pubkey is to be compressed, and finally four
// bytes of checksum.
encodeLen := 1 + btcec.PrivKeyBytesLen + 4
if w.CompressPubKey {
encodeLen++
}
a := make([]byte, 0, encodeLen)
a = append(a, w.netID)
// Pad and append bytes manually, instead of using Serialize, to
// avoid another call to make.
a = paddedAppend(btcec.PrivKeyBytesLen, a, w.PrivKey.D.Bytes())
if w.CompressPubKey {
a = append(a, compressMagic)
}
cksum := btcwire.DoubleSha256(a)[:4]
a = append(a, cksum...)
return Base58Encode(a)
}
// SerializePubKey serializes the associated public key of the imported or
// exported private key in either a compressed or uncompressed format. The
// serialization format chosen depends on the value of w.CompressPubKey.
func (w *WIF) SerializePubKey() []byte {
pk := (*btcec.PublicKey)(&w.PrivKey.PublicKey)
if w.CompressPubKey {
return pk.SerializeCompressed()
}
return pk.SerializeUncompressed()
}
// paddedAppend appends the src byte slice to dst, returning the new slice.
// If the length of the source is smaller than the passed size, leading zero
// bytes are appended to the dst slice before appending src.
func paddedAppend(size uint, dst, src []byte) []byte {
for i := 0; i < int(size)-len(src); i++ {
dst = append(dst, 0)
}
return append(dst, src...)
}