package main import ( "git.smesh.lol/smesh/web/common/crypto/nip44" "git.smesh.lol/smesh/web/common/helpers" "git.smesh.lol/smesh/web/common/jsbridge/schnorr" "git.smesh.lol/smesh/web/common/jsbridge/subtle" "git.smesh.lol/smesh/web/common/nostr" nosig "git.smesh.lol/smesh/web/common/nostr/sig" ) // NIP-07 method handlers. Secret keys never leave this process. var errNoActiveIdentity = func() string { return jsonErr(string(append([]byte(nil), 'n', 'o', ' ', 'a', 'c', 't', 'i', 'v', 'e', ' ', 'i', 'd', 'e', 'n', 't', 'i', 't', 'y'))) } func nip07GetPublicKey() (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } return jsonResult(helpers.JsonString(id.Pubkey)) } func nip07SignEvent(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kEvent := string(append([]byte(nil), 'e', 'v', 'e', 'n', 't')) eventRaw := helpers.JsonGetValue(paramsJSON, kEvent) if eventRaw == "" { eventRaw = paramsJSON } ev := nostr.ParseEvent(eventRaw) if ev == nil { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'e', 'v', 'e', 'n', 't'))) } skBytes := helpers.HexDecode(id.Seckey) if skBytes == nil { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y'))) } pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { 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'))) } ev.PubKey = helpers.HexEncode(pkBytes) (*nosig.Event)(ev).ComputeID() idBytes := helpers.HexDecode(ev.ID) if idBytes == nil { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'i', 'd'))) } aux := []byte{:32} subtle.RandomBytes(aux) sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux) if !ok { return jsonErr(string(append([]byte(nil), 's', 'i', 'g', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } ev.Sig = helpers.HexEncode(sig) return jsonResult(ev.ToJSON()) } func nip07Nip04Encrypt(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kPubkey := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) kPlaintext := string(append([]byte(nil), 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't')) pk := helpers.JsonGetString(paramsJSON, kPubkey) pt := helpers.JsonGetString(paramsJSON, kPlaintext) if pk == "" || pt == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } skBytes := helpers.HexDecode(id.Seckey) pkBytes := helpers.HexDecode(pk) shared, ok := schnorr.ECDH(skBytes, pkBytes) if !ok { return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } iv := []byte{:16} subtle.RandomBytes(iv) ct := subtle.AESCBCEncrypt(shared, iv, []byte(pt)) if len(ct) == 0 { return jsonErr(string(append([]byte(nil), 'e', 'n', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } ctB64 := helpers.Base64Encode(ct) ivB64 := helpers.Base64Encode(iv) result := []byte(nil) | ctB64 result = append(result, '?', 'i', 'v', '=') result = result | ivB64 return jsonResult(helpers.JsonString(string(result))) } func nip07Nip04Decrypt(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kPubkey2 := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) kCiphertext := string(append([]byte(nil), 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't')) pk := helpers.JsonGetString(paramsJSON, kPubkey2) ct := helpers.JsonGetString(paramsJSON, kCiphertext) if pk == "" || ct == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } skBytes := helpers.HexDecode(id.Seckey) pkBytes := helpers.HexDecode(pk) shared, ok := schnorr.ECDH(skBytes, pkBytes) if !ok { return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } ivIdx := -1 for i := 0; i < len(ct)-3; i++ { if ct[i] == '?' && ct[i+1] == 'i' && ct[i+2] == 'v' && ct[i+3] == '=' { ivIdx = i break } } if ivIdx < 0 { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't', ' ', 'f', 'o', 'r', 'm', 'a', 't'))) } ctBytes := helpers.Base64Decode(ct[:ivIdx]) ivBytes := helpers.Base64Decode(ct[ivIdx+4:]) if ctBytes == nil || ivBytes == nil { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'b', 'a', 's', 'e', '6', '4'))) } pt := subtle.AESCBCDecrypt(shared, ivBytes, ctBytes) if pt == nil { return jsonErr(string(append([]byte(nil), 'd', 'e', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } return jsonResult(helpers.JsonString(string(pt))) } // nip07Nip44Encrypt is synchronous: nip44.ConversationKey uses a sync ECDH. func nip07Nip44Encrypt(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kPubkeyN := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) kPlaintextN := string(append([]byte(nil), 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't')) pk := helpers.JsonGetString(paramsJSON, kPubkeyN) pt := helpers.JsonGetString(paramsJSON, kPlaintextN) if pk == "" || pt == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } sk32, ok := helpers.HexDecode32(id.Seckey) if !ok { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y'))) } pk32, ok := helpers.HexDecode32(pk) if !ok { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'p', 'u', 'b', 'k', 'e', 'y'))) } convKey, ok := nip44.ConversationKey(sk32, pk32) if !ok { 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'))) } var nonce [32]byte subtle.RandomBytes(nonce[:]) return jsonResult(helpers.JsonString(nip44.Encrypt(pt, convKey, nonce))) } // nip07Nip44Decrypt is synchronous. func nip07Nip44Decrypt(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kPubkeyD := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) kCiphertextD := string(append([]byte(nil), 'c', 'i', 'p', 'h', 'e', 'r', 't', 'e', 'x', 't')) pk := helpers.JsonGetString(paramsJSON, kPubkeyD) ct := helpers.JsonGetString(paramsJSON, kCiphertextD) if pk == "" || ct == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } sk32, ok := helpers.HexDecode32(id.Seckey) if !ok { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'k', 'e', 'y'))) } pk32, ok := helpers.HexDecode32(pk) if !ok { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'p', 'u', 'b', 'k', 'e', 'y'))) } convKey, ok := nip44.ConversationKey(sk32, pk32) if !ok { 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'))) } pt, ok := nip44.Decrypt(ct, convKey) if !ok { return jsonErr(string(append([]byte(nil), 'd', 'e', 'c', 'r', 'y', 'p', 't', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } return jsonResult(helpers.JsonString(pt)) } // nip07GetSharedSecret is synchronous: schnorr.ECDH is a sync wasmimport. func nip07GetSharedSecret(paramsJSON string) (s string) { id := activeIdentity() if id == nil { return errNoActiveIdentity() } kPubkeyS := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) pk := helpers.JsonGetString(paramsJSON, kPubkeyS) if pk == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'u', 'b', 'k', 'e', 'y'))) } shared, ok := schnorr.ECDH(helpers.HexDecode(id.Seckey), helpers.HexDecode(pk)) if !ok { return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } return jsonResult(helpers.JsonString(helpers.HexEncode(shared))) } // cryptoEcdhWithSecret: ECDH with caller-provided secret key. // params: {"secret":"","pubkey":""} // returns: {"result":""} func cryptoEcdhWithSecret(paramsJSON string) (s string) { kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't')) kPubkey := string(append([]byte(nil), 'p', 'u', 'b', 'k', 'e', 'y')) sk := helpers.JsonGetString(paramsJSON, kSecret) pk := helpers.JsonGetString(paramsJSON, kPubkey) if sk == "" || pk == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } skBytes := helpers.HexDecode(sk) pkBytes := helpers.HexDecode(pk) if len(skBytes) != 32 || len(pkBytes) != 32 { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'k', 'e', 'y', ' ', 'l', 'e', 'n', 'g', 't', 'h'))) } shared, ok := schnorr.ECDH(skBytes, pkBytes) if !ok { return jsonErr(string(append([]byte(nil), 'e', 'c', 'd', 'h', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } return jsonResult(helpers.JsonString(helpers.HexEncode(shared))) } // cryptoSignWithSecret: sign event with caller-provided secret key. // params: {"secret":"","event":""} // returns: {"result":""} func cryptoSignWithSecret(paramsJSON string) (s string) { kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't')) kEvent := string(append([]byte(nil), 'e', 'v', 'e', 'n', 't')) sk := helpers.JsonGetString(paramsJSON, kSecret) eventRaw := helpers.JsonGetValue(paramsJSON, kEvent) if sk == "" || eventRaw == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 'p', 'a', 'r', 'a', 'm', 's'))) } skBytes := helpers.HexDecode(sk) if len(skBytes) != 32 { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'r', 'e', 't'))) } ev := nostr.ParseEvent(eventRaw) if ev == nil { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'e', 'v', 'e', 'n', 't'))) } pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { 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'))) } ev.PubKey = helpers.HexEncode(pkBytes) (*nosig.Event)(ev).ComputeID() idBytes := helpers.HexDecode(ev.ID) if len(idBytes) != 32 { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'i', 'd'))) } aux := []byte{:32} subtle.RandomBytes(aux) sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux) if !ok { return jsonErr(string(append([]byte(nil), 's', 'i', 'g', 'n', ' ', 'f', 'a', 'i', 'l', 'e', 'd'))) } ev.Sig = helpers.HexEncode(sig) return jsonResult(ev.ToJSON()) } // cryptoPubkeyFromSecret: derive x-only pubkey from caller-provided secret. // params: {"secret":""} // returns: {"result":""} func cryptoPubkeyFromSecret(paramsJSON string) (s string) { kSecret := string(append([]byte(nil), 's', 'e', 'c', 'r', 'e', 't')) sk := helpers.JsonGetString(paramsJSON, kSecret) if sk == "" { return jsonErr(string(append([]byte(nil), 'm', 'i', 's', 's', 'i', 'n', 'g', ' ', 's', 'e', 'c', 'r', 'e', 't'))) } skBytes := helpers.HexDecode(sk) if len(skBytes) != 32 { return jsonErr(string(append([]byte(nil), 'i', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 's', 'e', 'c', 'r', 'e', 't'))) } pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { 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'))) } return jsonResult(helpers.JsonString(helpers.HexEncode(pkBytes))) }