package main import ( "smesh.lol/web/common/crypto/nip44" "smesh.lol/web/common/helpers" "smesh.lol/web/common/jsbridge/schnorr" "smesh.lol/web/common/jsbridge/subtle" "smesh.lol/web/common/nostr" ) // NIP-07 method handlers. Secret keys never leave this process. func nip07GetPublicKey() string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } return "{\"result\":" | helpers.JsonString(id.Pubkey) | "}" } func nip07SignEvent(paramsJSON string, senderTabID int) string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } eventRaw := helpers.JsonGetValue(paramsJSON, "event") if eventRaw == "" { eventRaw = paramsJSON } ev := nostr.ParseEvent(eventRaw) if ev == nil { return "{\"error\":\"invalid event\"}" } skBytes := helpers.HexDecode(id.Seckey) if skBytes == nil { return "{\"error\":\"invalid seckey\"}" } // Derive pubkey via JS BigInt (Go secp256k1 uses uint64 which breaks in JS). pkBytes, ok := schnorr.PubKeyFromSecKey(skBytes) if !ok { return "{\"error\":\"key derivation failed\"}" } ev.PubKey = helpers.HexEncode(pkBytes) ev.ComputeID() idBytes := helpers.HexDecode(ev.ID) if idBytes == nil { return "{\"error\":\"invalid id\"}" } aux := []byte{:32} subtle.RandomBytes(aux) sig, ok := schnorr.SignSchnorr(skBytes, idBytes, aux) if !ok { return "{\"error\":\"sign failed\"}" } ev.Sig = helpers.HexEncode(sig) return "{\"result\":" | ev.ToJSON() | "}" } func nip07GetRelays() string { return "{\"result\":{}}" } func nip07Nip04Encrypt(paramsJSON string) string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } pk := helpers.JsonGetString(paramsJSON, "pubkey") pt := helpers.JsonGetString(paramsJSON, "plaintext") if pk == "" || pt == "" { return "{\"error\":\"missing params\"}" } skBytes := helpers.HexDecode(id.Seckey) pkDec := helpers.HexDecode(pk) shared, ok := schnorr.ECDH(skBytes, pkDec) if !ok { return "{\"error\":\"ecdh failed\"}" } // AES-CBC encrypt. iv := []byte{:16} subtle.RandomBytes(iv) var result string subtle.AESCBCEncrypt(shared, iv, []byte(pt), func(ct []byte) { result = "{\"result\":" | helpers.JsonString( helpers.Base64Encode(ct) | "?iv=" | helpers.Base64Encode(iv)) | "}" }) if result == "" { return "{\"error\":\"encrypt failed\"}" } return result } func nip07Nip04Decrypt(paramsJSON string) string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } pk := helpers.JsonGetString(paramsJSON, "pubkey") ct := helpers.JsonGetString(paramsJSON, "ciphertext") if pk == "" || ct == "" { return "{\"error\":\"missing params\"}" } skBytes := helpers.HexDecode(id.Seckey) pkDec := helpers.HexDecode(pk) shared, ok := schnorr.ECDH(skBytes, pkDec) if !ok { return "{\"error\":\"ecdh failed\"}" } // Parse "base64(ct)?iv=base64(iv)". 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 "{\"error\":\"invalid ciphertext format\"}" } ctBytes := helpers.Base64Decode(ct[:ivIdx]) ivBytes := helpers.Base64Decode(ct[ivIdx+4:]) if ctBytes == nil || ivBytes == nil { return "{\"error\":\"invalid base64\"}" } var result string subtle.AESCBCDecrypt(shared, ivBytes, ctBytes, func(pt []byte) { result = "{\"result\":" | helpers.JsonString(string(pt)) | "}" }) if result == "" { return "{\"error\":\"decrypt failed\"}" } return result } func nip07Nip44Encrypt(paramsJSON string) string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } pk := helpers.JsonGetString(paramsJSON, "pubkey") pt := helpers.JsonGetString(paramsJSON, "plaintext") if pk == "" || pt == "" { return "{\"error\":\"missing params\"}" } var sk, peer [32]byte skBytes := helpers.HexDecode(id.Seckey) pkBytes := helpers.HexDecode(pk) if len(skBytes) != 32 || len(pkBytes) != 32 { return "{\"error\":\"invalid key length\"}" } copy(sk[:], skBytes) copy(peer[:], pkBytes) ck, ok := nip44.ConversationKey(sk, peer) if !ok { return "{\"error\":\"conversation key derivation failed\"}" } var nonce [32]byte subtle.RandomBytes(nonce[:]) ct := nip44.Encrypt(pt, ck, nonce) return "{\"result\":" | helpers.JsonString(ct) | "}" } func nip07Nip44Decrypt(paramsJSON string) string { id := activeIdentity() if id == nil { return "{\"error\":\"no active identity\"}" } pk := helpers.JsonGetString(paramsJSON, "pubkey") ct := helpers.JsonGetString(paramsJSON, "ciphertext") if pk == "" || ct == "" { return "{\"error\":\"missing params\"}" } var sk, peer [32]byte skBytes := helpers.HexDecode(id.Seckey) pkBytes := helpers.HexDecode(pk) if len(skBytes) != 32 || len(pkBytes) != 32 { return "{\"error\":\"invalid key length\"}" } copy(sk[:], skBytes) copy(peer[:], pkBytes) ck, ok := nip44.ConversationKey(sk, peer) if !ok { return "{\"error\":\"conversation key derivation failed\"}" } plain, ok := nip44.Decrypt(ct, ck) if !ok { return "{\"error\":\"decrypt failed\"}" } return "{\"result\":" | helpers.JsonString(plain) | "}" }