mirror of
https://github.com/LBRYFoundation/lbcutil.git
synced 2025-08-23 17:47:30 +00:00
Introduce better WIF API.
The old functions DecodePrivateKey and EncodePrivateKey have been removed in favor of the DecodeWIF function and the String method of the new WIF type. ok @davecgh
This commit is contained in:
parent
973174daa4
commit
bff18e5a93
5 changed files with 294 additions and 166 deletions
100
address.go
100
address.go
|
@ -23,11 +23,6 @@ var (
|
||||||
// a non-matching checksum.
|
// a non-matching checksum.
|
||||||
ErrMalformedAddress = errors.New("malformed address")
|
ErrMalformedAddress = errors.New("malformed address")
|
||||||
|
|
||||||
// ErrMalformedPrivateKey describes an error where an address is
|
|
||||||
// improperly formatted, either due to an incorrect length of the
|
|
||||||
// private key or a non-matching checksum.
|
|
||||||
ErrMalformedPrivateKey = errors.New("malformed private key")
|
|
||||||
|
|
||||||
// ErrChecksumMismatch describes an error where decoding failed due
|
// ErrChecksumMismatch describes an error where decoding failed due
|
||||||
// to a bad checksum.
|
// to a bad checksum.
|
||||||
ErrChecksumMismatch = errors.New("checksum mismatch")
|
ErrChecksumMismatch = errors.New("checksum mismatch")
|
||||||
|
@ -46,12 +41,6 @@ const (
|
||||||
// TestNetAddr is the address identifier for TestNet
|
// TestNetAddr is the address identifier for TestNet
|
||||||
TestNetAddr = 0x6f
|
TestNetAddr = 0x6f
|
||||||
|
|
||||||
// MainNetKey is the key identifier for MainNet
|
|
||||||
MainNetKey = 0x80
|
|
||||||
|
|
||||||
// TestNetKey is the key identifier for TestNet
|
|
||||||
TestNetKey = 0xef
|
|
||||||
|
|
||||||
// MainNetScriptHash is the script hash identifier for MainNet
|
// MainNetScriptHash is the script hash identifier for MainNet
|
||||||
MainNetScriptHash = 0x05
|
MainNetScriptHash = 0x05
|
||||||
|
|
||||||
|
@ -511,92 +500,3 @@ func (a *AddressPubKey) AddressPubKeyHash() *AddressPubKeyHash {
|
||||||
func (a *AddressPubKey) PubKey() *btcec.PublicKey {
|
func (a *AddressPubKey) PubKey() *btcec.PublicKey {
|
||||||
return a.pubKey
|
return a.pubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodePrivateKey takes a 32-byte private key and encodes it into the
|
|
||||||
// Wallet Import Format (WIF).
|
|
||||||
func EncodePrivateKey(privKey []byte, net btcwire.BitcoinNet, compressed bool) (string, error) {
|
|
||||||
if len(privKey) != 32 {
|
|
||||||
return "", ErrMalformedPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var netID byte
|
|
||||||
switch net {
|
|
||||||
case btcwire.MainNet:
|
|
||||||
netID = MainNetKey
|
|
||||||
case btcwire.TestNet3:
|
|
||||||
netID = TestNetKey
|
|
||||||
default:
|
|
||||||
return "", ErrUnknownNet
|
|
||||||
}
|
|
||||||
|
|
||||||
tosum := append([]byte{netID}, privKey...)
|
|
||||||
if compressed {
|
|
||||||
tosum = append(tosum, 0x01)
|
|
||||||
}
|
|
||||||
cksum := btcwire.DoubleSha256(tosum)
|
|
||||||
|
|
||||||
// Private key before base58 encoding is 1 byte for netID, 32 bytes for
|
|
||||||
// privKey, plus an optional byte (0x01) if copressed, plus 4 bytes of checksum.
|
|
||||||
encodeLen := 37
|
|
||||||
if compressed {
|
|
||||||
encodeLen++
|
|
||||||
}
|
|
||||||
a := make([]byte, encodeLen, encodeLen)
|
|
||||||
a[0] = netID
|
|
||||||
copy(a[1:], privKey)
|
|
||||||
if compressed {
|
|
||||||
copy(a[32+1:], []byte{0x01})
|
|
||||||
copy(a[32+1+1:], cksum[:4])
|
|
||||||
} else {
|
|
||||||
copy(a[32+1:], cksum[:4])
|
|
||||||
}
|
|
||||||
return Base58Encode(a), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePrivateKey takes a Wallet Import Format (WIF) string and
|
|
||||||
// decodes into a 32-byte private key.
|
|
||||||
func DecodePrivateKey(wif string) ([]byte, btcwire.BitcoinNet, bool, error) {
|
|
||||||
decoded := Base58Decode(wif)
|
|
||||||
decodedLen := len(decoded)
|
|
||||||
compressed := false
|
|
||||||
|
|
||||||
// Length of decoded privkey must be 32 bytes + an optional 1 byte (0x01)
|
|
||||||
// if compressed, plus 1 byte for netID + 4 bytes of checksum
|
|
||||||
if decodedLen == 32+6 {
|
|
||||||
compressed = true
|
|
||||||
if decoded[33] != 0x01 {
|
|
||||||
return nil, 0, compressed, ErrMalformedPrivateKey
|
|
||||||
}
|
|
||||||
} else if decodedLen != 32+5 {
|
|
||||||
return nil, 0, compressed, ErrMalformedPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var net btcwire.BitcoinNet
|
|
||||||
switch decoded[0] {
|
|
||||||
case MainNetKey:
|
|
||||||
net = btcwire.MainNet
|
|
||||||
case TestNetKey:
|
|
||||||
net = btcwire.TestNet3
|
|
||||||
default:
|
|
||||||
return nil, 0, compressed, ErrUnknownNet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 compressed {
|
|
||||||
tosum = decoded[:32+1+1]
|
|
||||||
} else {
|
|
||||||
tosum = decoded[:32+1]
|
|
||||||
}
|
|
||||||
cksum := btcwire.DoubleSha256(tosum)[:4]
|
|
||||||
if !bytes.Equal(cksum, decoded[decodedLen-4:]) {
|
|
||||||
return nil, 0, compressed, ErrMalformedPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey := make([]byte, 32, 32)
|
|
||||||
copy(privKey[:], decoded[1:32+1])
|
|
||||||
|
|
||||||
return privKey, net, compressed, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -589,49 +589,3 @@ func TestAddresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeDecodePrivateKey(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
in []byte
|
|
||||||
net btcwire.BitcoinNet
|
|
||||||
compressed bool
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
{[]byte{
|
|
||||||
0x0c, 0x28, 0xfc, 0xa3, 0x86, 0xc7, 0xa2, 0x27,
|
|
||||||
0x60, 0x0b, 0x2f, 0xe5, 0x0b, 0x7c, 0xae, 0x11,
|
|
||||||
0xec, 0x86, 0xd3, 0xbf, 0x1f, 0xbe, 0x47, 0x1b,
|
|
||||||
0xe8, 0x98, 0x27, 0xe1, 0x9d, 0x72, 0xaa, 0x1d,
|
|
||||||
}, btcwire.MainNet, false, "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"},
|
|
||||||
{[]byte{
|
|
||||||
0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6,
|
|
||||||
0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81,
|
|
||||||
0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9,
|
|
||||||
0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98,
|
|
||||||
}, btcwire.TestNet3, true, "cV1Y7ARUr9Yx7BR55nTdnR7ZXNJphZtCCMBTEZBJe1hXt2kB684q"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for x, test := range tests {
|
|
||||||
wif, err := btcutil.EncodePrivateKey(test.in, test.net, test.compressed)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%x: %v", x, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if wif != test.out {
|
|
||||||
t.Errorf("TestEncodeDecodePrivateKey failed: want '%s', got '%s'",
|
|
||||||
test.out, wif)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
key, _, compressed, err := btcutil.DecodePrivateKey(test.out)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !bytes.Equal(key, test.in) || compressed != test.compressed {
|
|
||||||
t.Errorf("TestEncodeDecodePrivateKey failed: want '%x', got '%x'",
|
|
||||||
test.out, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,60 +2,65 @@
|
||||||
github.com/conformal/btcutil/base58.go Base58Decode 100.00% (20/20)
|
github.com/conformal/btcutil/base58.go Base58Decode 100.00% (20/20)
|
||||||
github.com/conformal/btcutil/base58.go Base58Encode 100.00% (15/15)
|
github.com/conformal/btcutil/base58.go Base58Encode 100.00% (15/15)
|
||||||
github.com/conformal/btcutil/block.go Block.Tx 100.00% (12/12)
|
github.com/conformal/btcutil/block.go Block.Tx 100.00% (12/12)
|
||||||
|
github.com/conformal/btcutil/wif.go WIF.String 100.00% (11/11)
|
||||||
github.com/conformal/btcutil/block.go Block.Transactions 100.00% (11/11)
|
github.com/conformal/btcutil/block.go Block.Transactions 100.00% (11/11)
|
||||||
github.com/conformal/btcutil/address.go encodeAddress 100.00% (9/9)
|
github.com/conformal/btcutil/address.go encodeAddress 100.00% (9/9)
|
||||||
github.com/conformal/btcutil/amount.go NewAmount 100.00% (9/9)
|
github.com/conformal/btcutil/amount.go NewAmount 100.00% (9/9)
|
||||||
github.com/conformal/btcutil/amount.go AmountUnit.String 100.00% (8/8)
|
github.com/conformal/btcutil/amount.go AmountUnit.String 100.00% (8/8)
|
||||||
github.com/conformal/btcutil/block.go NewBlockFromBytes 100.00% (7/7)
|
github.com/conformal/btcutil/block.go NewBlockFromBytes 100.00% (7/7)
|
||||||
github.com/conformal/btcutil/tx.go NewTxFromBytes 100.00% (7/7)
|
github.com/conformal/btcutil/tx.go NewTxFromBytes 100.00% (7/7)
|
||||||
github.com/conformal/btcutil/tx.go Tx.Sha 100.00% (5/5)
|
|
||||||
github.com/conformal/btcutil/block.go Block.Sha 100.00% (5/5)
|
github.com/conformal/btcutil/block.go Block.Sha 100.00% (5/5)
|
||||||
|
github.com/conformal/btcutil/tx.go Tx.Sha 100.00% (5/5)
|
||||||
github.com/conformal/btcutil/address.go checkBitcoinNet 100.00% (5/5)
|
github.com/conformal/btcutil/address.go checkBitcoinNet 100.00% (5/5)
|
||||||
github.com/conformal/btcutil/hash160.go calcHash 100.00% (2/2)
|
github.com/conformal/btcutil/hash160.go calcHash 100.00% (2/2)
|
||||||
github.com/conformal/btcutil/amount.go Amount.Format 100.00% (2/2)
|
github.com/conformal/btcutil/amount.go Amount.Format 100.00% (2/2)
|
||||||
github.com/conformal/btcutil/address.go NewAddressScriptHash 100.00% (2/2)
|
github.com/conformal/btcutil/address.go NewAddressScriptHash 100.00% (2/2)
|
||||||
github.com/conformal/btcutil/block.go OutOfRangeError.Error 100.00% (1/1)
|
github.com/conformal/btcutil/block.go NewBlockFromBlockAndBytes 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/amount.go Amount.ToUnit 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/amount.go Amount.String 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go Block.MsgBlock 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go NewBlock 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go Block.SetHeight 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.EncodeAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.ScriptAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.ScriptAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.Hash160 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.Hash160 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/tx.go NewTx 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressScriptHash.EncodeAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.ScriptAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressScriptHash.ScriptAddress 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/tx.go Tx.SetIndex 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.String 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressScriptHash.String 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.Hash160 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressScriptHash.Hash160 100.00% (1/1)
|
||||||
|
github.com/conformal/btcutil/tx.go Tx.Index 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.EncodeAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.EncodeAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.ScriptAddress 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.ScriptAddress 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.String 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/amount.go Amount.ToUnit 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/amount.go Amount.String 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go Block.MsgBlock 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go Block.Height 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go Block.SetHeight 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go NewBlock 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/block.go NewBlockFromBlockAndBytes 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/hash160.go Hash160 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/tx.go Tx.MsgTx 100.00% (1/1)
|
github.com/conformal/btcutil/tx.go Tx.MsgTx 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/tx.go Tx.Index 100.00% (1/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.String 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/tx.go Tx.SetIndex 100.00% (1/1)
|
github.com/conformal/btcutil/hash160.go Hash160 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/tx.go NewTx 100.00% (1/1)
|
github.com/conformal/btcutil/block.go OutOfRangeError.Error 100.00% (1/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.String 100.00% (1/1)
|
|
||||||
github.com/conformal/btcutil/address.go DecodeAddress 95.65% (22/23)
|
github.com/conformal/btcutil/address.go DecodeAddress 95.65% (22/23)
|
||||||
github.com/conformal/btcutil/appdata.go appDataDir 92.00% (23/25)
|
github.com/conformal/btcutil/appdata.go appDataDir 92.00% (23/25)
|
||||||
github.com/conformal/btcutil/address.go NewAddressPubKeyHash 91.67% (11/12)
|
github.com/conformal/btcutil/address.go NewAddressPubKeyHash 91.67% (11/12)
|
||||||
github.com/conformal/btcutil/address.go NewAddressScriptHashFromHash 91.67% (11/12)
|
github.com/conformal/btcutil/address.go NewAddressScriptHashFromHash 91.67% (11/12)
|
||||||
github.com/conformal/btcutil/address.go EncodePrivateKey 90.91% (20/22)
|
|
||||||
github.com/conformal/btcutil/block.go Block.TxLoc 88.89% (8/9)
|
github.com/conformal/btcutil/block.go Block.TxLoc 88.89% (8/9)
|
||||||
github.com/conformal/btcutil/block.go Block.Bytes 88.89% (8/9)
|
github.com/conformal/btcutil/block.go Block.Bytes 88.89% (8/9)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.serialize 85.71% (6/7)
|
github.com/conformal/btcutil/address.go AddressPubKey.serialize 85.71% (6/7)
|
||||||
github.com/conformal/btcutil/address.go DecodePrivateKey 83.33% (20/24)
|
|
||||||
github.com/conformal/btcutil/address.go NewAddressPubKey 83.33% (15/18)
|
github.com/conformal/btcutil/address.go NewAddressPubKey 83.33% (15/18)
|
||||||
|
github.com/conformal/btcutil/wif.go DecodeWIF 81.82% (18/22)
|
||||||
|
github.com/conformal/btcutil/wif.go NewWIF 75.00% (3/4)
|
||||||
github.com/conformal/btcutil/block.go Block.TxSha 75.00% (3/4)
|
github.com/conformal/btcutil/block.go Block.TxSha 75.00% (3/4)
|
||||||
|
github.com/conformal/btcutil/wif.go paddedAppend 66.67% (2/3)
|
||||||
|
github.com/conformal/btcutil/address.go AddressPubKey.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/address.go AddressScriptHash.IsForNet 60.00% (3/5)
|
github.com/conformal/btcutil/address.go AddressScriptHash.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKeyHash.IsForNet 60.00% (3/5)
|
github.com/conformal/btcutil/address.go AddressPubKeyHash.IsForNet 60.00% (3/5)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.IsForNet 60.00% (3/5)
|
|
||||||
github.com/conformal/btcutil/certgen.go NewTLSCertPair 0.00% (0/50)
|
github.com/conformal/btcutil/certgen.go NewTLSCertPair 0.00% (0/50)
|
||||||
|
github.com/conformal/btcutil/wif.go WIF.SerializePubKey 0.00% (0/4)
|
||||||
|
github.com/conformal/btcutil/wif.go WIF.IsForNet 0.00% (0/4)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.AddressPubKeyHash 0.00% (0/3)
|
github.com/conformal/btcutil/address.go AddressPubKey.AddressPubKeyHash 0.00% (0/3)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.Format 0.00% (0/1)
|
|
||||||
github.com/conformal/btcutil/appdata.go AppDataDir 0.00% (0/1)
|
github.com/conformal/btcutil/appdata.go AppDataDir 0.00% (0/1)
|
||||||
|
github.com/conformal/btcutil/address.go AddressPubKey.Format 0.00% (0/1)
|
||||||
github.com/conformal/btcutil/address.go AddressPubKey.SetFormat 0.00% (0/1)
|
github.com/conformal/btcutil/address.go AddressPubKey.SetFormat 0.00% (0/1)
|
||||||
github.com/conformal/btcutil ------------------------------- 78.89% (299/379)
|
github.com/conformal/btcutil/address.go AddressPubKey.PubKey 0.00% (0/1)
|
||||||
|
github.com/conformal/btcutil ------------------------------- 76.70% (293/382)
|
||||||
|
|
||||||
|
|
198
wif.go
Normal file
198
wif.go
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
// 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/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")
|
||||||
|
|
||||||
|
// These constants define the magic numbers used for identifing components
|
||||||
|
// of a WIF-encoded private key and the bitcoin address associated with it.
|
||||||
|
const (
|
||||||
|
// mainNetKey is the magic number identifying a WIF private key for
|
||||||
|
// the MainNet bitcoin network.
|
||||||
|
mainNetKey byte = 0x80
|
||||||
|
|
||||||
|
// testNetKey is the magic number identifying a WIF private key for
|
||||||
|
// the regression test and TestNet3 bitcoin networks.
|
||||||
|
testNetKey byte = 0xef
|
||||||
|
|
||||||
|
// compressMagic is the magic byte used to identify a WIF encoding for
|
||||||
|
// an address created from a compressed serialized public key.
|
||||||
|
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 net argument must be
|
||||||
|
// either btcwire.MainNet, btcwire.TestNet3 or btcwire.TestNet. 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 btcwire.BitcoinNet, compress bool) (*WIF, error) {
|
||||||
|
// Determine the key's network identifier byte. The same byte is
|
||||||
|
// shared for TestNet3 and TestNet (the regression test network).
|
||||||
|
switch net {
|
||||||
|
case btcwire.MainNet:
|
||||||
|
return &WIF{privKey, compress, mainNetKey}, nil
|
||||||
|
case btcwire.TestNet, btcwire.TestNet3:
|
||||||
|
return &WIF{privKey, compress, testNetKey}, nil
|
||||||
|
default:
|
||||||
|
return nil, ErrUnknownNet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsForNet returns whether or not the decoded WIF structure is associated
|
||||||
|
// with the passed bitcoin network.
|
||||||
|
func (w *WIF) IsForNet(net btcwire.BitcoinNet) bool {
|
||||||
|
switch net {
|
||||||
|
case btcwire.MainNet:
|
||||||
|
return w.netID == mainNetKey
|
||||||
|
case btcwire.TestNet, btcwire.TestNet3:
|
||||||
|
return w.netID == testNetKey
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
netID := decoded[0]
|
||||||
|
if netID != mainNetKey && netID != testNetKey {
|
||||||
|
return nil, ErrUnknownNet
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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...)
|
||||||
|
}
|
71
wif_test.go
Normal file
71
wif_test.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcec"
|
||||||
|
. "github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeDecodeWIF(t *testing.T) {
|
||||||
|
priv1, _ := btcec.PrivKeyFromBytes(btcec.S256(), []byte{
|
||||||
|
0x0c, 0x28, 0xfc, 0xa3, 0x86, 0xc7, 0xa2, 0x27,
|
||||||
|
0x60, 0x0b, 0x2f, 0xe5, 0x0b, 0x7c, 0xae, 0x11,
|
||||||
|
0xec, 0x86, 0xd3, 0xbf, 0x1f, 0xbe, 0x47, 0x1b,
|
||||||
|
0xe8, 0x98, 0x27, 0xe1, 0x9d, 0x72, 0xaa, 0x1d})
|
||||||
|
|
||||||
|
priv2, _ := btcec.PrivKeyFromBytes(btcec.S256(), []byte{
|
||||||
|
0xdd, 0xa3, 0x5a, 0x14, 0x88, 0xfb, 0x97, 0xb6,
|
||||||
|
0xeb, 0x3f, 0xe6, 0xe9, 0xef, 0x2a, 0x25, 0x81,
|
||||||
|
0x4e, 0x39, 0x6f, 0xb5, 0xdc, 0x29, 0x5f, 0xe9,
|
||||||
|
0x94, 0xb9, 0x67, 0x89, 0xb2, 0x1a, 0x03, 0x98})
|
||||||
|
|
||||||
|
wif1, err := NewWIF(priv1, btcwire.MainNet, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wif2, err := NewWIF(priv2, btcwire.TestNet3, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
wif *WIF
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
wif1,
|
||||||
|
"5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wif2,
|
||||||
|
"cV1Y7ARUr9Yx7BR55nTdnR7ZXNJphZtCCMBTEZBJe1hXt2kB684q",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
// Test that encoding the WIF structure matches the expected string.
|
||||||
|
s := test.wif.String()
|
||||||
|
if s != test.encoded {
|
||||||
|
t.Errorf("TestEncodeDecodePrivateKey failed: want '%s', got '%s'",
|
||||||
|
test.encoded, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that decoding the expected string results in the original WIF
|
||||||
|
// structure.
|
||||||
|
w, err := DecodeWIF(test.encoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := w.String(); got != test.encoded {
|
||||||
|
t.Errorf("NewWIF failed: want '%v', got '%v'", test.wif, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue