github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/btc/wallethd.go (about)

     1  /*
     2  This code originates from:
     3   * https://github.com/WeMeetAgain/go-hdwallet
     4  */
     5  
     6  package btc
     7  
     8  import (
     9  	"bytes"
    10  	"crypto/hmac"
    11  	"crypto/sha512"
    12  	"encoding/binary"
    13  	"errors"
    14  	"fmt"
    15  
    16  	"github.com/piotrnar/gocoin/lib/secp256k1"
    17  )
    18  
    19  const (
    20  	Public   = uint32(0x0488B21E) // xpub
    21  	Private  = uint32(0x0488ADE4) // xprv
    22  	PublicY  = uint32(0x049d7cb2) // ypub
    23  	PrivateY = uint32(0x049d7878) // yprv
    24  	PublicZ  = uint32(0x04b24746) // zpub
    25  	PrivateZ = uint32(0x04b2430c) // zprv
    26  
    27  	TestPublic   = uint32(0x043587cf) // tpub
    28  	TestPrivate  = uint32(0x04358394) // tprv
    29  	TestPublicY  = uint32(0x044a5262) // upub
    30  	TestPrivateY = uint32(0x044a4e28) // uprv
    31  	TestPublicZ  = uint32(0x045f1cf6) // vpub
    32  	TestPrivateZ = uint32(0x045f18bc) // vpub
    33  )
    34  
    35  // HDWallet defines the components of a hierarchical deterministic wallet
    36  type HDWallet struct {
    37  	Prefix   uint32
    38  	Depth    byte
    39  	Checksum [4]byte
    40  	I        uint32
    41  	ChCode   []byte //32 bytes
    42  	Key      []byte //33 bytes
    43  }
    44  
    45  func IsPublicHDPrefix(p uint32) bool {
    46  	return p == Public || p == PublicY || p == PublicZ ||
    47  		p == TestPublic || p == TestPublicY || p == TestPublicZ
    48  }
    49  
    50  func IsPrivateHDPrefix(p uint32) bool {
    51  	return p == Private || p == PrivateY || p == PrivateZ ||
    52  		p == TestPrivate || p == TestPrivateY || p == TestPrivateZ
    53  }
    54  
    55  func IsTestnetHDPrefix(p uint32) bool {
    56  	return p == TestPublic || p == TestPublicY || p == TestPublicZ ||
    57  		p == TestPrivate || p == TestPrivateY || p == TestPrivateZ
    58  }
    59  
    60  func PublishHDPrefix(p uint32) uint32 {
    61  	if p == Private {
    62  		return Public
    63  	}
    64  	if p == PrivateY {
    65  		return PublicY
    66  	}
    67  	if p == PrivateZ {
    68  		return PublicZ
    69  	}
    70  	if p == TestPrivate {
    71  		return TestPublic
    72  	}
    73  	if p == TestPrivateY {
    74  		return TestPublicY
    75  	}
    76  	if p == TestPrivateZ {
    77  		return TestPublicZ
    78  	}
    79  	return p
    80  }
    81  
    82  // Child returns the ith child of wallet w. Values of i >= 2^31
    83  // signify private key derivation. Attempting private key derivation
    84  // with a public key will throw an error.
    85  func (w *HDWallet) Child(i uint32) (res *HDWallet) {
    86  	var ha, newkey []byte
    87  	var chksum [20]byte
    88  
    89  	if IsPrivateHDPrefix(w.Prefix) {
    90  		pub := PublicFromPrivate(w.Key[1:], true)
    91  		mac := hmac.New(sha512.New, w.ChCode)
    92  		if i >= uint32(0x80000000) {
    93  			mac.Write(w.Key)
    94  		} else {
    95  			mac.Write(pub)
    96  		}
    97  		binary.Write(mac, binary.BigEndian, i)
    98  		ha = mac.Sum(nil)
    99  		newkey = append([]byte{0}, DeriveNextPrivate(ha[:32], w.Key[1:])...)
   100  		RimpHash(pub, chksum[:])
   101  	} else if IsPublicHDPrefix(w.Prefix) {
   102  		mac := hmac.New(sha512.New, w.ChCode)
   103  		if i >= uint32(0x80000000) {
   104  			panic("HDWallet.Child(): Private derivation on Public key")
   105  		}
   106  		mac.Write(w.Key)
   107  		binary.Write(mac, binary.BigEndian, i)
   108  		ha = mac.Sum(nil)
   109  		newkey = DeriveNextPublic(w.Key, ha[:32])
   110  		RimpHash(w.Key, chksum[:])
   111  	} else {
   112  		panic("HDWallet.Child(): Unexpected Prefix")
   113  	}
   114  	res = new(HDWallet)
   115  	res.Prefix = w.Prefix
   116  	res.Depth = w.Depth + 1
   117  	copy(res.Checksum[:], chksum[:4])
   118  	res.I = i
   119  	res.ChCode = ha[32:]
   120  	res.Key = newkey
   121  	return
   122  }
   123  
   124  // Serialize returns the serialized form of the wallet.
   125  // vbytes || depth || fingerprint || i || chaincode || key
   126  func (w *HDWallet) Serialize() []byte {
   127  	var tmp [32]byte
   128  	b := new(bytes.Buffer)
   129  	binary.Write(b, binary.BigEndian, w.Prefix)
   130  	b.WriteByte(w.Depth)
   131  	b.Write(w.Checksum[:])
   132  	binary.Write(b, binary.BigEndian, w.I)
   133  	b.Write(w.ChCode)
   134  	b.Write(w.Key)
   135  	ShaHash(b.Bytes(), tmp[:])
   136  	return append(b.Bytes(), tmp[:4]...)
   137  }
   138  
   139  // String returns the base58-encoded string form of the wallet.
   140  func (w *HDWallet) String() string {
   141  	return Encodeb58(w.Serialize())
   142  }
   143  
   144  // StringWallet returns a wallet given a base58-encoded extended key
   145  func StringWallet(data string) (*HDWallet, error) {
   146  	dbin := Decodeb58(data)
   147  	if err := ByteCheck(dbin); err != nil {
   148  		return &HDWallet{}, err
   149  	}
   150  	var res [32]byte
   151  	ShaHash(dbin[:(len(dbin)-4)], res[:])
   152  	if !bytes.Equal(res[:4], dbin[(len(dbin)-4):]) {
   153  		return &HDWallet{}, errors.New("StringWallet: Invalid checksum")
   154  	}
   155  	r := new(HDWallet)
   156  	r.Prefix = binary.BigEndian.Uint32(dbin[0:4])
   157  	r.Depth = dbin[4]
   158  	copy(r.Checksum[:], dbin[5:9])
   159  	r.I = binary.BigEndian.Uint32(dbin[9:13])
   160  	r.ChCode = dbin[13:45]
   161  	r.Key = dbin[45:78]
   162  	return r, nil
   163  }
   164  
   165  // Pub returns a new wallet which is the public key version of w.
   166  // If w is a public key, Pub returns a copy of w
   167  func (w *HDWallet) Pub() *HDWallet {
   168  	if IsPublicHDPrefix(w.Prefix) {
   169  		r := new(HDWallet)
   170  		*r = *w
   171  		return r
   172  	} else {
   173  		return &HDWallet{Prefix: PublishHDPrefix(w.Prefix), Depth: w.Depth, Checksum: w.Checksum,
   174  			I: w.I, ChCode: w.ChCode, Key: PublicFromPrivate(w.Key[1:], true)}
   175  	}
   176  }
   177  
   178  // StringChild returns the ith base58-encoded extended key of a base58-encoded extended key.
   179  func StringChild(data string, i uint32) string {
   180  	w, err := StringWallet(data)
   181  	if err != nil {
   182  		return ""
   183  	} else {
   184  		w = w.Child(i)
   185  		return w.String()
   186  	}
   187  }
   188  
   189  // StringToAddress returns the Bitcoin address of a base58-encoded extended key.
   190  func StringAddress(data string) (string, error) {
   191  	w, err := StringWallet(data)
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  
   196  	return NewAddrFromPubkey(w.Key, AddrVerPubkey(w.Prefix == TestPublic || w.Prefix == TestPrivate)).String(), nil
   197  }
   198  
   199  // PublicAddress returns the Base58 or bech32 encoded public address of the given HD key.
   200  func (w *HDWallet) PubAddr() *BtcAddr {
   201  	var pub []byte
   202  	if IsPrivateHDPrefix(w.Prefix) {
   203  		pub = PublicFromPrivate(w.Key[1:], true)
   204  	} else {
   205  		pub = w.Key
   206  	}
   207  
   208  	var h160 [20]byte
   209  	testnet := IsTestnetHDPrefix(w.Prefix)
   210  	RimpHash(pub, h160[:])
   211  	switch w.Prefix {
   212  	case PrivateZ, PublicZ, TestPrivateZ, TestPublicZ:
   213  		return NewAddrFromPkScript(append([]byte{0, 20}, h160[:]...), testnet)
   214  	case PrivateY, PublicY, TestPrivateY, TestPublicY:
   215  		tmp := Rimp160AfterSha256(append([]byte{0, 20}, h160[:]...))
   216  		return NewAddrFromHash160(tmp[:], AddrVerScript(testnet))
   217  	default:
   218  		return NewAddrFromHash160(h160[:], AddrVerPubkey(testnet))
   219  	}
   220  }
   221  
   222  // MasterKey returns a new wallet given a random seed.
   223  func MasterKey(seed []byte, testnet bool) *HDWallet {
   224  	key := []byte("Bitcoin seed")
   225  	mac := hmac.New(sha512.New, key)
   226  	mac.Write(seed)
   227  	I := mac.Sum(nil)
   228  	res := &HDWallet{ChCode: I[len(I)/2:], Key: append([]byte{0}, I[:len(I)/2]...)}
   229  	if testnet {
   230  		res.Prefix = TestPrivate
   231  	} else {
   232  		res.Prefix = Private
   233  	}
   234  	return res
   235  }
   236  
   237  // StringCheck is a validation check of a Base58-encoded extended key.
   238  func StringCheck(key string) error {
   239  	return ByteCheck(Decodeb58(key))
   240  }
   241  
   242  // ByteCheck verifies the consistency of a serialized HD address.
   243  func ByteCheck(dbin []byte) error {
   244  	// check proper length
   245  	if len(dbin) != 82 {
   246  		return errors.New("ByteCheck: Unexpected length")
   247  	}
   248  
   249  	// check for correct Public or Private Prefix
   250  	vb := binary.BigEndian.Uint32(dbin[:4])
   251  	if !IsPrivateHDPrefix(vb) && !IsPublicHDPrefix(vb) {
   252  		return fmt.Errorf("ByteCheck: Unexpected Prefix 0x%08x", vb)
   253  	}
   254  
   255  	// if Public, check x coord is on curve
   256  	if IsPublicHDPrefix(vb) {
   257  		var xy secp256k1.XY
   258  		xy.ParsePubkey(dbin[45:78])
   259  		if !xy.IsValid() {
   260  			return errors.New("ByteCheck: Invalid public key")
   261  		}
   262  	}
   263  	return nil
   264  }
   265  
   266  // HDKeyPrefix returns the first 32 bits, as expected for sepcific HD address.
   267  func HDKeyPrefix(private, testnet bool) uint32 {
   268  	if private {
   269  		if testnet {
   270  			return TestPrivate
   271  		} else {
   272  			return Private
   273  		}
   274  	} else {
   275  		if testnet {
   276  			return TestPublic
   277  		} else {
   278  			return Public
   279  		}
   280  	}
   281  }