diff --git a/addrconvs.go b/addrconvs.go deleted file mode 100644 index d728442..0000000 --- a/addrconvs.go +++ /dev/null @@ -1,136 +0,0 @@ -// 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/btcwire" -) - -// ErrUnknownNet describes an error where the Bitcoin network is -// not recognized. -var ErrUnknownNet = errors.New("unrecognized bitcoin network") - -// ErrMalformedAddress describes an error where an address is improperly -// formatted, either due to an incorrect length of the hashed pubkey or -// a non-matching checksum. -var 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. -var ErrMalformedPrivateKey = errors.New("malformed private key") - -// Constants used to specify which network a payment address belongs -// to. Mainnet address cannot be used on the Testnet, and vice versa. -const ( - // MainNetAddr is the address identifier for MainNet - MainNetAddr = 0x00 - - // TestNetAddr is the address identifier for TestNet - 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 = 0x05 - - // TestNetScriptHash is the script hash identifier for TestNet - TestNetScriptHash = 0xc4 -) - -// 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 += 1 - } - 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 -} diff --git a/addrconvs_test.go b/addrconvs_test.go deleted file mode 100644 index a64aefa..0000000 --- a/addrconvs_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// 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 ( - "bytes" - "github.com/conformal/btcutil" - "github.com/conformal/btcwire" - "testing" -) - -var encodePrivateKeyTests = []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"}, -} - -func TestEncodeDecodePrivateKey(t *testing.T) { - for x, test := range encodePrivateKeyTests { - 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) - } - - } -} diff --git a/address.go b/address.go index e2cb06d..bb59542 100644 --- a/address.go +++ b/address.go @@ -14,6 +14,20 @@ import ( ) var ( + // ErrUnknownNet describes an error where the Bitcoin network is + // not recognized. + ErrUnknownNet = errors.New("unrecognized bitcoin network") + + // ErrMalformedAddress describes an error where an address is improperly + // formatted, either due to an incorrect length of the hashed pubkey or + // a non-matching checksum. + 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 // to a bad checksum. ErrChecksumMismatch = errors.New("checksum mismatch") @@ -23,6 +37,28 @@ var ( ErrUnknownIdentifier = errors.New("unknown identifier byte") ) +// Constants used to specify which network a payment address belongs +// to. Mainnet address cannot be used on the Testnet, and vice versa. +const ( + // MainNetAddr is the address identifier for MainNet + MainNetAddr = 0x00 + + // TestNetAddr is the address identifier for TestNet + 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 = 0x05 + + // TestNetScriptHash is the script hash identifier for TestNet + TestNetScriptHash = 0xc4 +) + // checkBitcoinNet returns an error if the bitcoin network is not supported. func checkBitcoinNet(net btcwire.BitcoinNet) error { // Check for a valid bitcoin network. @@ -452,3 +488,92 @@ func (a *AddressPubKey) AddressPubKeyHash() *AddressPubKeyHash { return addr } + +// 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 += 1 + } + 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 +} diff --git a/address_test.go b/address_test.go index acfb6ef..4cf5293 100644 --- a/address_test.go +++ b/address_test.go @@ -575,3 +575,49 @@ 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) + } + + } +}