-
Notifications
You must be signed in to change notification settings - Fork 1
/
wif.go
144 lines (133 loc) · 5.94 KB
/
wif.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package util
import (
"bytes"
"errors"
"github.com/cybriq/p9/pkg/btcaddr"
"github.com/cybriq/p9/pkg/chaincfg"
"github.com/cybriq/p9/pkg/base58"
"github.com/cybriq/p9/pkg/chainhash"
ec "github.com/cybriq/p9/pkg/ecc"
)
// 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 *ec.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 *ec.PrivateKey, net *chaincfg.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 *chaincfg.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 := base58.Decode(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 + ec.PrivKeyBytesLen + 1 + 4:
if decoded[33] != compressMagic {
return nil, ErrMalformedPrivateKey
}
compress = true
case 1 + ec.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+ec.PrivKeyBytesLen+1]
} else {
tosum = decoded[:1+ec.PrivKeyBytesLen]
}
cksum := chainhash.DoubleHashB(tosum)[:4]
if !bytes.Equal(cksum, decoded[decodedLen-4:]) {
return nil, btcaddr.ErrChecksumMismatch
}
netID := decoded[0]
privKeyBytes := decoded[1 : 1+ec.PrivKeyBytesLen]
privKey, _ := ec.PrivKeyFromBytes(ec.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 + ec.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(ec.PrivKeyBytesLen, a, w.PrivKey.D.Bytes())
if w.CompressPubKey {
a = append(a, compressMagic)
}
cksum := chainhash.DoubleHashB(a)[:4]
a = append(a, cksum...)
return base58.Encode(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 := (*ec.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...)
}