hd.mx raw

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