hd.mx raw

   1  package main
   2  
   3  import (
   4  	"crypto/bip32"
   5  	"crypto/bip39"
   6  
   7  	"git.smesh.lol/smesh/web/common/helpers"
   8  	"git.smesh.lol/smesh/web/common/jsbridge/schnorr"
   9  	"git.smesh.lol/smesh/web/common/jsbridge/subtle"
  10  )
  11  
  12  // HD keychain wraps moxie stdlib BIP-39 + BIP-32 (crypto/bip39 + crypto/secp256k1)
  13  // with smesh's WASM bridge for entropy and JSON handling.
  14  
  15  var (
  16  	hdMnemonic    string // decrypted BIP-39 phrase, "" when not an HD vault
  17  	hdNextAccount int32    // next unused account index
  18  )
  19  
  20  func mgmtGenerateMnemonic() (s string) {
  21  	entropy := []byte{:16}
  22  	subtle.RandomBytes(entropy)
  23  	return jsonResult(helpers.JsonString(bip39.EntropyToMnemonic(entropy)))
  24  }
  25  
  26  func mgmtValidateMnemonic(paramsJSON string) (s string) {
  27  	kMnemonic := string(append([]byte(nil), 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
  28  	phrase := helpers.JsonGetString(paramsJSON, kMnemonic)
  29  	if bip39.ValidateMnemonic(phrase) {
  30  		return jsonTrue()
  31  	}
  32  	return jsonFalse()
  33  }
  34  
  35  func mgmtGetMnemonic() (s string) {
  36  	if hdMnemonic == "" {
  37  		return jsonResult(string(append([]byte(nil), '"', '"')))
  38  	}
  39  	return jsonResult(helpers.JsonString(hdMnemonic))
  40  }
  41  
  42  func mgmtIsHD() (s string) {
  43  	if hdMnemonic != "" {
  44  		return jsonTrue()
  45  	}
  46  	return jsonFalse()
  47  }
  48  
  49  func mgmtCreateHDVault(paramsJSON string) (s string) {
  50  	kPassword := string(append([]byte(nil), 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'))
  51  	kMnemonic := string(append([]byte(nil), 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
  52  	pw := helpers.JsonGetString(paramsJSON, kPassword)
  53  	phrase := helpers.JsonGetString(paramsJSON, kMnemonic)
  54  	if pw == "" {
  55  		return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd')))
  56  	}
  57  	if phrase == "" {
  58  		entropy := []byte{:16}
  59  		subtle.RandomBytes(entropy)
  60  		phrase = bip39.EntropyToMnemonic(entropy)
  61  	} else if !bip39.ValidateMnemonic(phrase) {
  62  		// Reject invalid input. Otherwise PBKDF2-SHA512 happily produces a
  63  		// deterministic seed from any string, which would silently give every
  64  		// caller the same key for the same garbage input.
  65  		return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c')))
  66  	}
  67  	seed := bip39.MnemonicToSeed(phrase, "")
  68  	if seed == nil {
  69  		return jsonErr(string(append([]byte(nil), 's', 'e', 'e', 'd', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
  70  	}
  71  	key, _ := bip32.DerivePath(seed, bip32.NostrPath(0))
  72  	if key == nil {
  73  		return jsonErr(string(append([]byte(nil), 'k', 'e', 'y', ' ', 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
  74  	}
  75  	pkBytes, ok := schnorr.PubKeyFromSecKey(key)
  76  	if !ok {
  77  		return jsonErr(string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
  78  	}
  79  	if !createVault(pw) {
  80  		return jsonErr(string(append([]byte(nil), 'v', 'a', 'u', 'l', 't', ' ', 'c', 'r', 'e', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
  81  	}
  82  	identities = []identity{{Pubkey: helpers.HexEncode(pkBytes), Seckey: helpers.HexEncode(key), Name: ""}}
  83  	activeIdx = 0
  84  	hdMnemonic = phrase
  85  	hdNextAccount = 1
  86  	saveVault()
  87  	return jsonTrue()
  88  }
  89  
  90  func mgmtRestoreHDVault(paramsJSON string) (s string) {
  91  	kPassword := string(append([]byte(nil), 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'))
  92  	kMnemonic := string(append([]byte(nil), 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
  93  	pw := helpers.JsonGetString(paramsJSON, kPassword)
  94  	phrase := helpers.JsonGetString(paramsJSON, kMnemonic)
  95  	if pw == "" || phrase == "" {
  96  		return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's')))
  97  	}
  98  	if !bip39.ValidateMnemonic(phrase) {
  99  		return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c')))
 100  	}
 101  	pb := append([]byte(nil), '{', '"', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', '"', ':')
 102  	pb = pb | helpers.JsonString(pw)
 103  	pb = append(pb, ',', '"', 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c', '"', ':')
 104  	pb = pb | helpers.JsonString(phrase)
 105  	pb = append(pb, '}')
 106  	return mgmtCreateHDVault(string(pb))
 107  }
 108  
 109  func mgmtDeriveIdentity(paramsJSON string) (s string) {
 110  	if hdMnemonic == "" {
 111  		return jsonErr(string(append([]byte(nil), 'n', 'o', 't', ' ', 'a', 'n', ' ', 'H', 'D', ' ', 'v', 'a', 'u', 'l', 't')))
 112  	}
 113  	kAccount := string(append([]byte(nil), 'a', 'c', 'c', 'o', 'u', 'n', 't'))
 114  	accountRaw := helpers.JsonGetValue(paramsJSON, kAccount)
 115  	account := 0
 116  	for i := 0; i < len(accountRaw); i++ {
 117  		c := accountRaw[i]
 118  		if c >= '0' && c <= '9' {
 119  			account = account*10 + int32(c-'0')
 120  		}
 121  	}
 122  	seed := bip39.MnemonicToSeed(hdMnemonic, "")
 123  	key, _ := bip32.DerivePath(seed, bip32.NostrPath(account))
 124  	if key == nil {
 125  		return jsonErr(string(append([]byte(nil), 'd', 'e', 'r', 'i', 'v', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
 126  	}
 127  	pkBytes, ok := schnorr.PubKeyFromSecKey(key)
 128  	if !ok {
 129  		return jsonErr(string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
 130  	}
 131  	pk := helpers.HexEncode(pkBytes)
 132  	identities = append(identities, identity{Pubkey: pk, Seckey: helpers.HexEncode(key)})
 133  	if account >= hdNextAccount {
 134  		hdNextAccount = account + 1
 135  	}
 136  	saveVault()
 137  	return jsonResult(helpers.JsonString(pk))
 138  }
 139  
 140  func mgmtProbeAccount(paramsJSON string) (s string) {
 141  	if hdMnemonic == "" {
 142  		return jsonErr(string(append([]byte(nil), 'n', 'o', 't', ' ', 'a', 'n', ' ', 'H', 'D', ' ', 'v', 'a', 'u', 'l', 't')))
 143  	}
 144  	kAccounts := string(append([]byte(nil), 'a', 'c', 'c', 'o', 'u', 'n', 't', 's'))
 145  	accounts := helpers.JsonGetIntArray(paramsJSON, kAccounts)
 146  	if len(accounts) == 0 {
 147  		return jsonResult(string(append([]byte(nil), '[', ']')))
 148  	}
 149  	seed := bip39.MnemonicToSeed(hdMnemonic, "")
 150  	if seed == nil {
 151  		return jsonErr(string(append([]byte(nil), 's', 'e', 'e', 'd', ' ', 'f', 'a', 'i', 'l', 'e', 'd')))
 152  	}
 153  	pubkeys := []string{:0:len(accounts)}
 154  	for _, acct := range accounts {
 155  		key, _ := bip32.DerivePath(seed, bip32.NostrPath(int32(acct)))
 156  		if key == nil {
 157  			pubkeys = append(pubkeys, "")
 158  			continue
 159  		}
 160  		pkBytes, ok := schnorr.PubKeyFromSecKey(key)
 161  		if !ok {
 162  			pubkeys = append(pubkeys, "")
 163  		} else {
 164  			pubkeys = append(pubkeys, helpers.HexEncode(pkBytes))
 165  		}
 166  	}
 167  	b := append([]byte(nil), '[')
 168  	for i, pk := range pubkeys {
 169  		if i > 0 {
 170  			b = append(b, ',')
 171  		}
 172  		b = b | helpers.JsonString(pk)
 173  	}
 174  	b = append(b, ']')
 175  	return jsonResult(string(b))
 176  }
 177