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