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