main.mx raw

   1  package main
   2  
   3  import (
   4  	"runtime"
   5  
   6  	"git.smesh.lol/musiquay/web/common/helpers"
   7  	"git.smesh.lol/musiquay/web/common/jsbridge/ext"
   8  )
   9  
  10  // Method name strings built from byte constants. Package var initializers run
  11  // before main (moxie has no init()); push keeps them heap-allocated instead of
  12  // placing them in the data section, which wasm32 relocations have corrupted.
  13  var (
  14  	_true             string
  15  	_false            string
  16  	mGetPublicKey     string
  17  	mSignEvent        string
  18  	mGetRelays        string
  19  	mNip44Encrypt     string
  20  	mNip44Decrypt     string
  21  	mGetSharedSecret  string
  22  	mNip04Encrypt     string
  23  	mNip04Decrypt     string
  24  	mGetVaultStatus   string
  25  	mLockVault        string
  26  	mListIdentities   string
  27  	mSwitchIdentity   string
  28  	mAddIdentity      string
  29  	mRemoveIdentity   string
  30  	mNsecLogin        string
  31  	mGetPermissions   string
  32  	mSetPermission    string
  33  	mPromptResponse   string
  34  	mGetMnemonic      string
  35  	mIsHD             string
  36  	mResetExtension   string
  37  	mValidateMnemonic string
  38  	mGenerateMnemonic string
  39  	mNwcList          string
  40  	mUnlockVault      string
  41  	mCreateVault      string
  42  	mExportVault      string
  43  	mImportVault      string
  44  	mCreateHDVault    string
  45  	mRestoreHDVault   string
  46  	mDeriveIdentity   string
  47  	mProbeAccount     string
  48  	mNwcAdd           string
  49  	mNwcRemove        string
  50  	mNwcBuildRequest  string
  51  	mNwcParseResponse string
  52  	mEcdhWithSecret   string
  53  	mSignWithSecret   string
  54  	mPubkeyFromSecret string
  55  	mLastUnlockError  string
  56  )
  57  
  58  // --- Response builders: byte constants only ---
  59  
  60  func jsonTrue() (s string) { return jsonResult(_true) }
  61  func jsonFalse() (s string) { return jsonResult(_false) }
  62  
  63  func jsonResult(val string) (s string) {
  64  	b := push([]byte(nil), '{', '"', 'r', 'e', 's', 'u', 'l', 't', '"', ':')
  65  	b = b | val
  66  	b = b | "}"
  67  	return string(b)
  68  }
  69  
  70  func jsonResultStr(s string) (sv string) {
  71  	return jsonResult(helpers.JsonString(s))
  72  }
  73  
  74  func jsonErr(msg string) (s string) {
  75  	b := push([]byte(nil), '{', '"', 'e', 'r', 'r', 'o', 'r', '"', ':')
  76  	b = b | helpers.JsonString(msg)
  77  	b = b | "}"
  78  	return string(b)
  79  }
  80  
  81  func unknownMethod() (s string) {
  82  	return jsonErr(string(push([]byte(nil), 'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ', 'm', 'e', 't', 'h', 'o', 'd')))
  83  }
  84  
  85  // signerState is the signer worker's mutable state: the NWC wallet connection
  86  // list, the per-host permission table, the unlocked vault and its identity
  87  // list, and the HD vault's mnemonic state. Package globals are immutable
  88  // outside initialization, so all of it lives in one self-mutating type
  89  // reached through a package-level pointer.
  90  type signerState struct {
  91  	walletConns []nwcConn
  92  	permissions []permission
  93  
  94  	// vault.mx
  95  	vaultKey      []byte
  96  	vaultSalt     []byte // 32-byte Argon2id salt (v2+ only)
  97  	vaultHash     string // hex SHA-256 of password, for fast unlock check
  98  	vaultVersion  int32  // 1, 2, or 3
  99  	vaultArgon2M  int32  // argon2 memory param this vault was encrypted with
 100  	vaultOpen     bool
 101  	vaultExists   bool
 102  	vaultRawCache string // cached JSON from storage, set in loadVault callback
 103  	// v2 compat: shared IV from stored vault, used only to decrypt v2 fields
 104  	// during migration.
 105  	v2IV []byte
 106  
 107  	identities []identity
 108  	activeIdx  int32
 109  
 110  	// Set by unlockVault on failure for diagnostic surface via mgmtUnlockVault.
 111  	// Cleared on success. Values: "no-vault", "no-hash", "wrong-password",
 112  	// "bad-iv", "kdf-failed", "decrypt-failed".
 113  	lastUnlockErr string
 114  
 115  	// hd.mx
 116  	hdMnemonic    string // decrypted BIP-39 phrase, "" when not an HD vault
 117  	hdNextAccount int32  // next unused account index
 118  }
 119  
 120  var signSt *signerState
 121  
 122  func initState() {
 123  	if signSt != nil {
 124  		return
 125  	}
 126  	// signSt and everything it holds live as long as this worker: build it in
 127  	// the root arena, not in this call's frame arena which dies on return.
 128  	runtime.SovereignSetArena(runtime.RootArena())
 129  	signSt = &signerState{}
 130  	runtime.SovereignRestoreArena(runtime.RootArena())
 131  }
 132  
 133  func initSignerGlobals() {
 134  	_true = string(push([]byte(nil), 't', 'r', 'u', 'e'))
 135  	_false = string(push([]byte(nil), 'f', 'a', 'l', 's', 'e'))
 136  	mGetPublicKey = string(push([]byte(nil), 'g', 'e', 't', 'P', 'u', 'b', 'l', 'i', 'c', 'K', 'e', 'y'))
 137  	mSignEvent = string(push([]byte(nil), 's', 'i', 'g', 'n', 'E', 'v', 'e', 'n', 't'))
 138  	mGetRelays = string(push([]byte(nil), 'g', 'e', 't', 'R', 'e', 'l', 'a', 'y', 's'))
 139  	mNip44Encrypt = string(push([]byte(nil), 'n', 'i', 'p', '4', '4', '.', 'e', 'n', 'c', 'r', 'y', 'p', 't'))
 140  	mNip44Decrypt = string(push([]byte(nil), 'n', 'i', 'p', '4', '4', '.', 'd', 'e', 'c', 'r', 'y', 'p', 't'))
 141  	mGetSharedSecret = string(push([]byte(nil), 'g', 'e', 't', 'S', 'h', 'a', 'r', 'e', 'd', 'S', 'e', 'c', 'r', 'e', 't'))
 142  	mNip04Encrypt = string(push([]byte(nil), 'n', 'i', 'p', '0', '4', '.', 'e', 'n', 'c', 'r', 'y', 'p', 't'))
 143  	mNip04Decrypt = string(push([]byte(nil), 'n', 'i', 'p', '0', '4', '.', 'd', 'e', 'c', 'r', 'y', 'p', 't'))
 144  	mGetVaultStatus = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'g', 'e', 't', 'V', 'a', 'u', 'l', 't', 'S', 't', 'a', 't', 'u', 's'))
 145  	mLockVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'l', 'o', 'c', 'k', 'V', 'a', 'u', 'l', 't'))
 146  	mListIdentities = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'l', 'i', 's', 't', 'I', 'd', 'e', 'n', 't', 'i', 't', 'i', 'e', 's'))
 147  	mSwitchIdentity = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 's', 'w', 'i', 't', 'c', 'h', 'I', 'd', 'e', 'n', 't', 'i', 't', 'y'))
 148  	mAddIdentity = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'a', 'd', 'd', 'I', 'd', 'e', 'n', 't', 'i', 't', 'y'))
 149  	mRemoveIdentity = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'r', 'e', 'm', 'o', 'v', 'e', 'I', 'd', 'e', 'n', 't', 'i', 't', 'y'))
 150  	mNsecLogin = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 's', 'e', 'c', 'L', 'o', 'g', 'i', 'n'))
 151  	mGetPermissions = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'g', 'e', 't', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n', 's'))
 152  	mSetPermission = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 's', 'e', 't', 'P', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n'))
 153  	mPromptResponse = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'p', 'r', 'o', 'm', 'p', 't', 'R', 'e', 's', 'p', 'o', 'n', 's', 'e'))
 154  	mGetMnemonic = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'g', 'e', 't', 'M', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
 155  	mIsHD = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'i', 's', 'H', 'D'))
 156  	mResetExtension = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'r', 'e', 's', 'e', 't', 'E', 'x', 't', 'e', 'n', 's', 'i', 'o', 'n'))
 157  	mValidateMnemonic = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'v', 'a', 'l', 'i', 'd', 'a', 't', 'e', 'M', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
 158  	mGenerateMnemonic = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'M', 'n', 'e', 'm', 'o', 'n', 'i', 'c'))
 159  	mNwcList = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 'w', 'c', '.', 'l', 'i', 's', 't'))
 160  	mUnlockVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'u', 'n', 'l', 'o', 'c', 'k', 'V', 'a', 'u', 'l', 't'))
 161  	mCreateVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'c', 'r', 'e', 'a', 't', 'e', 'V', 'a', 'u', 'l', 't'))
 162  	mExportVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'e', 'x', 'p', 'o', 'r', 't', 'V', 'a', 'u', 'l', 't'))
 163  	mImportVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'i', 'm', 'p', 'o', 'r', 't', 'V', 'a', 'u', 'l', 't'))
 164  	mCreateHDVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'c', 'r', 'e', 'a', 't', 'e', 'H', 'D', 'V', 'a', 'u', 'l', 't'))
 165  	mRestoreHDVault = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'r', 'e', 's', 't', 'o', 'r', 'e', 'H', 'D', 'V', 'a', 'u', 'l', 't'))
 166  	mDeriveIdentity = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'd', 'e', 'r', 'i', 'v', 'e', 'I', 'd', 'e', 'n', 't', 'i', 't', 'y'))
 167  	mProbeAccount = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'p', 'r', 'o', 'b', 'e', 'A', 'c', 'c', 'o', 'u', 'n', 't'))
 168  	mNwcAdd = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 'w', 'c', '.', 'a', 'd', 'd'))
 169  	mNwcRemove = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 'w', 'c', '.', 'r', 'e', 'm', 'o', 'v', 'e'))
 170  	mNwcBuildRequest = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 'w', 'c', '.', 'b', 'u', 'i', 'l', 'd', 'R', 'e', 'q', 'u', 'e', 's', 't'))
 171  	mNwcParseResponse = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'n', 'w', 'c', '.', 'p', 'a', 'r', 's', 'e', 'R', 'e', 's', 'p', 'o', 'n', 's', 'e'))
 172  	mEcdhWithSecret = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'e', 'c', 'd', 'h', 'W', 'i', 't', 'h', 'S', 'e', 'c', 'r', 'e', 't'))
 173  	mSignWithSecret = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 's', 'i', 'g', 'n', 'W', 'i', 't', 'h', 'S', 'e', 'c', 'r', 'e', 't'))
 174  	mPubkeyFromSecret = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'p', 'u', 'b', 'k', 'e', 'y', 'F', 'r', 'o', 'm', 'S', 'e', 'c', 'r', 'e', 't'))
 175  	mLastUnlockError = string(push([]byte(nil), 'm', 'u', 's', 'i', 'q', 'u', 'a', 'y', '.', 'l', 'a', 's', 't', 'U', 'n', 'l', 'o', 'c', 'k', 'E', 'r', 'r', 'o', 'r'))
 176  }
 177  
 178  func main() {
 179  	initSignerGlobals()
 180  
 181  	initState()
 182  	loadVault()
 183  	loadPermissions()
 184  	ext.OnMessage(handleMessage)
 185  }
 186  
 187  // handleMessage dispatches using string equality against heap-built method names.
 188  func handleMessage(method, paramsJSON string, senderTabID int32, respond func(string)) {
 189  	if method == mLockVault { respond(mgmtLockVault()); return }
 190  	if method == mValidateMnemonic { respond(mgmtValidateMnemonic(paramsJSON)); return }
 191  	if method == mSwitchIdentity { respond(mgmtSwitchIdentity(paramsJSON)); return }
 192  	if method == mRemoveIdentity { respond(mgmtRemoveIdentity(paramsJSON)); return }
 193  	if method == mResetExtension { respond(mgmtResetExtension()); return }
 194  	if method == mCreateVault { respond(mgmtCreateVault(paramsJSON)); return }
 195  	if method == mGetPublicKey { respond(nip07GetPublicKey()); return }
 196  	if method == mSignEvent { respond(nip07SignEvent(paramsJSON)); return }
 197  	if method == mGetRelays { respond("{\"result\":{}}"); return }
 198  	if method == mNip44Encrypt { respond(nip07Nip44Encrypt(paramsJSON)); return }
 199  	if method == mNip44Decrypt { respond(nip07Nip44Decrypt(paramsJSON)); return }
 200  	if method == mGetSharedSecret { respond(nip07GetSharedSecret(paramsJSON)); return }
 201  	if method == mNip04Encrypt { respond(nip07Nip04Encrypt(paramsJSON)); return }
 202  	if method == mNip04Decrypt { respond(nip07Nip04Decrypt(paramsJSON)); return }
 203  	if method == mGetVaultStatus { respond(mgmtGetVaultStatus()); return }
 204  	if method == mListIdentities { respond(mgmtListIdentities()); return }
 205  	if method == mAddIdentity { respond(mgmtAddIdentity(paramsJSON)); return }
 206  	if method == mNsecLogin { respond(mgmtNsecLogin(paramsJSON)); return }
 207  	if method == mGetPermissions { respond(mgmtGetPermissions()); return }
 208  	if method == mSetPermission { respond(mgmtSetPermission(paramsJSON)); return }
 209  	if method == mPromptResponse { respond(mgmtPromptResponse(paramsJSON)); return }
 210  	if method == mGetMnemonic { respond(mgmtGetMnemonic()); return }
 211  	if method == mIsHD { respond(mgmtIsHD()); return }
 212  	if method == mGenerateMnemonic { respond(mgmtGenerateMnemonic()); return }
 213  	if method == mNwcList { respond(nwcList()); return }
 214  	if method == mUnlockVault { respond(mgmtUnlockVault(paramsJSON)); return }
 215  	if method == mExportVault { respond(mgmtExportVault(paramsJSON)); return }
 216  	if method == mImportVault { respond(mgmtImportVault(paramsJSON)); return }
 217  	if method == mCreateHDVault { respond(mgmtCreateHDVault(paramsJSON)); return }
 218  	if method == mRestoreHDVault { respond(mgmtRestoreHDVault(paramsJSON)); return }
 219  	if method == mDeriveIdentity { respond(mgmtDeriveIdentity(paramsJSON)); return }
 220  	if method == mProbeAccount { respond(mgmtProbeAccount(paramsJSON)); return }
 221  	if method == mNwcAdd { respond(nwcAdd(paramsJSON)); return }
 222  	if method == mNwcRemove { respond(nwcRemove(paramsJSON)); return }
 223  	if method == mNwcBuildRequest { respond(nwcBuildRequest(paramsJSON)); return }
 224  	if method == mNwcParseResponse { respond(nwcParseResponse(paramsJSON)); return }
 225  	if method == mEcdhWithSecret { respond(cryptoEcdhWithSecret(paramsJSON)); return }
 226  	if method == mSignWithSecret { respond(cryptoSignWithSecret(paramsJSON)); return }
 227  	if method == mPubkeyFromSecret { respond(cryptoPubkeyFromSecret(paramsJSON)); return }
 228  	if method == mLastUnlockError { respond(jsonResultStr(signSt.lastUnlockErr)); return }
 229  	respond(unknownMethod())
 230  }
 231