bip32.mx raw

   1  // Package bip32 implements BIP-32 hierarchical deterministic key derivation
   2  // for secp256k1. https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
   3  //
   4  // Only the secret-key derivation path is exposed (DerivePath, DeriveChild).
   5  // Public-key-only derivation (xpub) is not implemented since the smesh
   6  // signer always derives from the master seed with the secret key in hand.
   7  //
   8  // Lives in its own package so callers of crypto/secp256k1 do not transitively
   9  // link in crypto/hmac and crypto/sha512 (and a non-trivial amount of dead code
  10  // when the moxie linker is run without dead-code elimination).
  11  package bip32
  12  
  13  import (
  14  	"crypto/hmac"
  15  	"crypto/secp256k1"
  16  	"crypto/sha512"
  17  )
  18  
  19  // HardenedBit is the BIP-32 hardened-derivation marker (index high bit).
  20  const HardenedBit = uint32(0x80000000)
  21  
  22  // MasterSeed is the BIP-32 master key separator ("Bitcoin seed").
  23  const masterKey = "Bitcoin seed"
  24  
  25  // DeriveChild derives a child key + chain code from a parent key + chain code
  26  // at the given index. Returns (nil, nil) on failure (parent key invalid for
  27  // non-hardened derivation, child scalar at zero, etc.).
  28  func DeriveChild(parentKey, chainCode []byte, index uint32) (childKey, childChain []byte) {
  29  	if len(parentKey) != 32 || len(chainCode) != 32 {
  30  		return nil, nil
  31  	}
  32  	data := []byte{:37}
  33  	if index&HardenedBit != 0 {
  34  		data[0] = 0x00
  35  		copy(data[1:], parentKey)
  36  	} else {
  37  		var sk [32]byte
  38  		copy(sk[:], parentKey)
  39  		cpk, ok := secp256k1.CompressedPubKey(sk)
  40  		if !ok {
  41  			return nil, nil
  42  		}
  43  		copy(data, cpk[:])
  44  	}
  45  	data[33] = byte(index >> 24)
  46  	data[34] = byte(index >> 16)
  47  	data[35] = byte(index >> 8)
  48  	data[36] = byte(index)
  49  	mac := hmac.New(sha512.New, chainCode)
  50  	mac.Write(data)
  51  	result := mac.Sum(nil)
  52  	if len(result) != 64 {
  53  		return nil, nil
  54  	}
  55  	il := result[:32]
  56  	childChain = result[32:]
  57  	var ilArr, parentArr [32]byte
  58  	copy(ilArr[:], il)
  59  	copy(parentArr[:], parentKey)
  60  	r, ok := secp256k1.ScalarAddModN(parentArr, ilArr)
  61  	if !ok {
  62  		return nil, nil
  63  	}
  64  	childKey = r[:]
  65  	return childKey, childChain
  66  }
  67  
  68  // DerivePath derives a key + chain code from a 64-byte master seed along the
  69  // given BIP-32 path. Empty path returns the master key + chain code as-is.
  70  // Returns (nil, nil) on any derivation failure.
  71  func DerivePath(seed []byte, path []uint32) (key, chain []byte) {
  72  	mac := hmac.New(sha512.New, masterKey)
  73  	mac.Write(seed)
  74  	master := mac.Sum(nil)
  75  	if len(master) != 64 {
  76  		return nil, nil
  77  	}
  78  	key = master[:32]
  79  	chain = master[32:]
  80  	for _, idx := range path {
  81  		key, chain = DeriveChild(key, chain, idx)
  82  		if key == nil {
  83  			return nil, nil
  84  		}
  85  	}
  86  	return key, chain
  87  }
  88  
  89  // NostrPath returns the BIP-32 path for a Nostr key at the given account index.
  90  // NIP-06: m/44'/1237'/account'/0/0
  91  func NostrPath(account int) []uint32 {
  92  	return []uint32{
  93  		44 | HardenedBit,
  94  		1237 | HardenedBit,
  95  		uint32(account) | HardenedBit,
  96  		0,
  97  		0,
  98  	}
  99  }
 100