github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/wif.go (about)

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