wif.go raw

   1  package util
   2  
   3  import (
   4  	"bytes"
   5  	"errors"
   6  	"github.com/p9c/p9/pkg/btcaddr"
   7  	"github.com/p9c/p9/pkg/chaincfg"
   8  	
   9  	"github.com/p9c/p9/pkg/base58"
  10  	"github.com/p9c/p9/pkg/chainhash"
  11  	ec "github.com/p9c/p9/pkg/ecc"
  12  )
  13  
  14  // ErrMalformedPrivateKey describes an error where a WIF-encoded private key cannot be decoded due to being improperly
  15  // formatted. This may occur if the byte length is incorrect or an unexpected magic number was encountered.
  16  var ErrMalformedPrivateKey = errors.New("malformed private key")
  17  
  18  // compressMagic is the magic byte used to identify a WIF encoding for an address created from a compressed serialized
  19  // public key.
  20  const compressMagic byte = 0x01
  21  
  22  // WIF contains the individual components described by the Wallet Import Format (WIF). A WIF string is typically used to
  23  // represent a private key and its associated address in a way that may be easily copied and imported into or exported
  24  // from wallet software. WIF strings may be decoded into this structure by calling DecodeWIF or created with a
  25  // user-provided private key by calling NewWIF.
  26  type WIF struct {
  27  	// PrivKey is the private key being imported or exported.
  28  	PrivKey *ec.PrivateKey
  29  	// CompressPubKey specifies whether the address controlled by the imported or exported private key was created by
  30  	// hashing a compressed (33-byte) serialized public key, rather than an uncompressed (65-byte) one.
  31  	CompressPubKey bool
  32  	// netID is the bitcoin network identifier byte used when WIF encoding the private key.
  33  	netID byte
  34  }
  35  
  36  // NewWIF creates a new WIF structure to export an address and its private key as a string encoded in the Wallet Import
  37  // Format. The compress argument specifies whether the address intended to be imported or exported was created by
  38  // serializing the public key compressed rather than uncompressed.
  39  func NewWIF(privKey *ec.PrivateKey, net *chaincfg.Params, compress bool) (*WIF, error) {
  40  	if net == nil {
  41  		return nil, errors.New("no network")
  42  	}
  43  	return &WIF{privKey, compress, net.PrivateKeyID}, nil
  44  }
  45  
  46  // IsForNet returns whether or not the decoded WIF structure is associated with the passed bitcoin network.
  47  func (w *WIF) IsForNet(net *chaincfg.Params) bool {
  48  	return w.netID == net.PrivateKeyID
  49  }
  50  
  51  // DecodeWIF creates a new WIF structure by decoding the string encoding of the import format.
  52  //
  53  // The WIF string must be a base58-encoded string of the following byte sequence:
  54  //
  55  //  * 1 byte to identify the network, must be 0x80 for mainnet or 0xef for either testnet3 or the regression test network
  56  //
  57  //  * 32 bytes of a binary-encoded, big-endian, zero-padded private key
  58  //
  59  //  * Optional 1 byte (equal to 0x01) if the address being imported or exported was created by taking the RIPEMD160 after
  60  //  SHA256 hash of a serialized compressed (33-byte) public key
  61  //
  62  //  * 4 bytes of checksum, must equal the first four bytes of the double SHA256 of every byte before the checksum in this
  63  //  sequence
  64  //
  65  // If the base58-decoded byte sequence does not match this, DecodeWIF will return a non-nil error.
  66  // ErrMalformedPrivateKey is returned when the WIF is of an impossible length or the expected compressed pubkey magic
  67  // number does not equal the expected value of 0x01. errChecksumMismatch is returned if the expected WIF checksum does
  68  // not match the calculated checksum.
  69  func DecodeWIF(wif string) (*WIF, error) {
  70  	decoded := base58.Decode(wif)
  71  	decodedLen := len(decoded)
  72  	var compress bool
  73  	// Length of base58 decoded WIF must be 32 bytes + an optional 1 byte (0x01) if compressed, plus 1 byte for netID +
  74  	// 4 bytes of checksum.
  75  	switch decodedLen {
  76  	case 1 + ec.PrivKeyBytesLen + 1 + 4:
  77  		if decoded[33] != compressMagic {
  78  			return nil, ErrMalformedPrivateKey
  79  		}
  80  		compress = true
  81  	case 1 + ec.PrivKeyBytesLen + 4:
  82  		compress = false
  83  	default:
  84  		return nil, ErrMalformedPrivateKey
  85  	}
  86  	// Checksum is first four bytes of double SHA256 of the identifier byte and privKey. Verify this matches the final 4
  87  	// bytes of the decoded private key.
  88  	var tosum []byte
  89  	if compress {
  90  		tosum = decoded[:1+ec.PrivKeyBytesLen+1]
  91  	} else {
  92  		tosum = decoded[:1+ec.PrivKeyBytesLen]
  93  	}
  94  	cksum := chainhash.DoubleHashB(tosum)[:4]
  95  	if !bytes.Equal(cksum, decoded[decodedLen-4:]) {
  96  		return nil, btcaddr.ErrChecksumMismatch
  97  	}
  98  	netID := decoded[0]
  99  	privKeyBytes := decoded[1 : 1+ec.PrivKeyBytesLen]
 100  	privKey, _ := ec.PrivKeyFromBytes(ec.S256(), privKeyBytes)
 101  	return &WIF{privKey, compress, netID}, nil
 102  }
 103  
 104  // String creates the Wallet Import Format string encoding of a WIF structure. See DecodeWIF for a detailed breakdown of
 105  // the format and requirements of a valid WIF string.
 106  func (w *WIF) String() string {
 107  	// Precalculate size. Maximum number of bytes before base58 encoding is one byte for the network, 32 bytes of
 108  	// private key, possibly one extra byte if the pubkey is to be compressed, and finally four bytes of checksum.
 109  	encodeLen := 1 + ec.PrivKeyBytesLen + 4
 110  	if w.CompressPubKey {
 111  		encodeLen++
 112  	}
 113  	a := make([]byte, 0, encodeLen)
 114  	a = append(a, w.netID)
 115  	// Pad and append bytes manually, instead of using Serialize, to avoid another call to make.
 116  	a = paddedAppend(ec.PrivKeyBytesLen, a, w.PrivKey.D.Bytes())
 117  	if w.CompressPubKey {
 118  		a = append(a, compressMagic)
 119  	}
 120  	cksum := chainhash.DoubleHashB(a)[:4]
 121  	a = append(a, cksum...)
 122  	return base58.Encode(a)
 123  }
 124  
 125  // SerializePubKey serializes the associated public key of the imported or exported private key in either a compressed
 126  // or uncompressed format. The serialization format chosen depends on the value of w.CompressPubKey.
 127  func (w *WIF) SerializePubKey() []byte {
 128  	pk := (*ec.PublicKey)(&w.PrivKey.PublicKey)
 129  	if w.CompressPubKey {
 130  		return pk.SerializeCompressed()
 131  	}
 132  	return pk.SerializeUncompressed()
 133  }
 134  
 135  // paddedAppend appends the src byte slice to dst, returning the new slice. If the length of the source is smaller than
 136  // the passed size, leading zero bytes are appended to the dst slice before appending src.
 137  func paddedAppend(size uint, dst, src []byte) []byte {
 138  	for i := 0; i < int(size)-len(src); i++ {
 139  		dst = append(dst, 0)
 140  	}
 141  	return append(dst, src...)
 142  }
 143