Skip to content

Commit

Permalink
feat: add NIP 44 encryption support
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Staab committed Aug 11, 2023
1 parent dffd6e5 commit 0f97ae0
Show file tree
Hide file tree
Showing 25 changed files with 281 additions and 23 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@
"@bitcoin-design/bitcoin-icons-react": "^0.1.9",
"@headlessui/react": "^1.7.16",
"@lightninglabs/lnc-web": "^0.2.4-alpha",
"@noble/ciphers": "^0.2.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"@noble/secp256k1": "^2.0.0",
"@scure/base": "^1.1.1",
"@scure/bip32": "^1.3.1",
"@scure/bip39": "^1.2.1",
"@scure/btc-signer": "^0.5.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { USER_REJECTED_ERROR } from "~/common/constants";
import utils from "~/common/lib/utils";
import { getHostFromSender } from "~/common/utils/helpers";
import state from "~/extension/background-script/state";
import i18n from "~/i18n/i18nConfig";
import { MessageDecryptGet, PermissionMethodNostr, Sender } from "~/types";

import { addPermissionFor, hasPermissionFor } from "./helpers";

const nip44DecryptOrPrompt = async (
message: MessageDecryptGet,
sender: Sender
) => {
const host = getHostFromSender(sender);
if (!host) return;

try {
const hasPermission = await hasPermissionFor(
PermissionMethodNostr["NOSTR_NIP44DECRYPT"],
host
);

if (hasPermission) {
const nostr = await state.getState().getNostr();
const response = await nostr.nip44Decrypt(
message.args.peer,
message.args.ciphertext
);

return { data: response };
} else {
const promptResponse = await utils.openPrompt<{
confirm: boolean;
rememberPermission: boolean;
}>({
...message,
action: "public/nostr/confirm",
args: {
description: i18n.t("permissions:nostr.nip44decrypt"),
},
});

// add permission to db only if user decided to always allow this request
if (promptResponse.data.rememberPermission) {
await addPermissionFor(
PermissionMethodNostr["NOSTR_NIP44DECRYPT"],
host
);
}
if (promptResponse.data.confirm) {
const nostr = await state.getState().getNostr();
const response = await nostr.nip44Decrypt(
message.args.peer,
message.args.ciphertext
);

return { data: response };
} else {
return { error: USER_REJECTED_ERROR };
}
}
} catch (e) {
console.error("decrypt failed", e);
if (e instanceof Error) {
return { error: e.message };
}
}
};

export default nip44DecryptOrPrompt;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { USER_REJECTED_ERROR } from "~/common/constants";
import utils from "~/common/lib/utils";
import { getHostFromSender } from "~/common/utils/helpers";
import state from "~/extension/background-script/state";
import i18n from "~/i18n/i18nConfig";
import { MessageEncryptGet, PermissionMethodNostr, Sender } from "~/types";

import { addPermissionFor, hasPermissionFor } from "./helpers";

const nip44EncryptOrPrompt = async (
message: MessageEncryptGet,
sender: Sender
) => {
const host = getHostFromSender(sender);
if (!host) return;

try {
const hasPermission = await hasPermissionFor(
PermissionMethodNostr["NOSTR_NIP44ENCRYPT"],
host
);

if (hasPermission) {
const response = (await state.getState().getNostr()).nip44Encrypt(
message.args.peer,
message.args.plaintext
);

return { data: response };
} else {
const promptResponse = await utils.openPrompt<{
confirm: boolean;
rememberPermission: boolean;
}>({
...message,
action: "public/nostr/confirm",
args: {
description: i18n.t("permissions:nostr.nip44encrypt"),
},
});

// add permission to db only if user decided to always allow this request
if (promptResponse.data.rememberPermission) {
await addPermissionFor(
PermissionMethodNostr["NOSTR_NIP44ENCRYPT"],
host
);
}
if (promptResponse.data.confirm) {
const response = (await state.getState().getNostr()).nip44Encrypt(
message.args.peer,
message.args.plaintext
);

return { data: response };
} else {
return { error: USER_REJECTED_ERROR };
}
}
} catch (e) {
console.error("encrypt failed", e);
if (e instanceof Error) {
return { error: e.message };
}
}
};

export default nip44EncryptOrPrompt;
55 changes: 55 additions & 0 deletions src/extension/background-script/nostr/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { base64 } from "@scure/base";
import { randomBytes } from "@noble/hashes/utils";
import { sha256 } from "@noble/hashes/sha256";
import { xchacha20 } from "@noble/ciphers/chacha";
import { schnorr } from "@noble/curves/secp256k1";
import * as secp256k1 from "@noble/secp256k1";
import { Buffer } from "buffer";
Expand All @@ -10,6 +14,10 @@ import { Event } from "~/extension/providers/nostr/types";

import { getEventHash, signEvent } from "../actions/nostr/helpers";

const utf8Decoder = new TextDecoder();

const utf8Encoder = new TextEncoder();

class Nostr {
privateKey: string;

Expand Down Expand Up @@ -69,6 +77,53 @@ class Nostr {
return Utf8.stringify(decrypted);
}

nip44Encrypt(pubkey: string, text: string, v = 1) {
if (v !== 1) {
throw new Error("NIP44: unknown encryption version");
}

const key = this.nip44GetSharedSecret(pubkey);
const nonce = randomBytes(24);
const plaintext = utf8Encoder.encode(text);
const ciphertext = xchacha20(key, nonce, plaintext);

return JSON.stringify({
ciphertext: base64.encode(ciphertext),
nonce: base64.encode(nonce),
v,
});
}

nip44Decrypt(pubkey: string, payload: string) {
let data;
try {
data = JSON.parse(payload) as {
ciphertext: string;
nonce: string;
v: number;
};
} catch (e) {
throw new Error("NIP44: failed to parse payload");
}

if (data.v !== 1) {
throw new Error("NIP44: unknown encryption version");
}

const key = this.nip44GetSharedSecret(pubkey);
const nonce = base64.decode(data.nonce);
const ciphertext = base64.decode(data.ciphertext);
const plaintext = xchacha20(key, nonce, ciphertext);

return utf8Decoder.decode(plaintext);
}

nip44GetSharedSecret(pubkey: string) {
return sha256(
secp256k1.getSharedSecret(this.privateKey, "02" + pubkey).subarray(1, 33)
);
}

getEventHash(event: Event) {
return getEventHash(event);
}
Expand Down
19 changes: 19 additions & 0 deletions src/extension/providers/nostr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare global {

export default class NostrProvider {
nip04 = new Nip04(this);
nip44 = new Nip44(this);
enabled: boolean;
private _eventEmitter: EventEmitter;

Expand Down Expand Up @@ -89,3 +90,21 @@ class Nip04 {
return this.provider.execute("decryptOrPrompt", { peer, ciphertext });
}
}

class Nip44 {
provider: NostrProvider;

constructor(provider: NostrProvider) {
this.provider = provider;
}

async encrypt(peer: string, plaintext: string) {
await this.provider.enable();
return this.provider.execute("nip44EncryptOrPrompt", { peer, plaintext });
}

async decrypt(peer: string, ciphertext: string) {
await this.provider.enable();
return this.provider.execute("nip44DecryptOrPrompt", { peer, ciphertext });
}
}
4 changes: 3 additions & 1 deletion src/i18n/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -855,9 +855,11 @@
"invoice": "Vytvořit novou platební fakturu."
},
"nostr": {
"nip04encrypt": "Zašifrovat data.",
"getpublickey": "Přečíst svůj veřejný klíč.",
"nip04decrypt": "Dešifrovat data.",
"nip04encrypt": "Zašifrovat data.",
"nip44decrypt": "Dešifrovat data.",
"nip44encrypt": "Zašifrovat data.",
"signmessage": "Podepsat zprávu klíčem."
},
"lnc": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/da/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -802,8 +802,10 @@
"permissions": {
"nostr": {
"getpublickey": "Læs din offentlige nøgle.",
"nip04encrypt": "Krypter data.",
"nip04decrypt": "De-krypter data.",
"nip04encrypt": "Krypter data.",
"nip44decrypt": "De-krypter data.",
"nip44encrypt": "Krypter data.",
"signmessage": "Underskriv meddelelse med din nøgle."
},
"commando": {
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,8 @@
"getpublickey": "Lese deinen öffentlichen Schlüssel.",
"nip04decrypt": "Daten entschlüsseln.",
"nip04encrypt": "Daten verschlüsseln.",
"nip44decrypt": "Daten entschlüsseln.",
"nip44encrypt": "Daten verschlüsseln.",
"signmessage": "Unterschreibe deine Nachricht mit deinem Schlüssel."
},
"lnc": {
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,8 @@
"getpublickey": "Read your public key.",
"nip04encrypt": "Encrypt data.",
"nip04decrypt": "Decrypt data.",
"nip44encrypt": "Encrypt data.",
"nip44decrypt": "Decrypt data.",
"signmessage": "Sign message with your key."
},
"commando": {
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -937,10 +937,12 @@
"feerates": "Devuelve las tarifas que utilizará CLN."
},
"nostr": {
"signmessage": "Firma el mensaje con tu clave.",
"getpublickey": "Leer tu clave pública.",
"nip04decrypt": "Descifrar datos.",
"nip04encrypt": "Cifrar datos.",
"nip04decrypt": "Descifrar datos."
"nip44decrypt": "Descifrar datos.",
"nip44encrypt": "Cifrar datos.",
"signmessage": "Firma el mensaje con tu clave."
},
"lnd": {
"queryroutes": "Consultar una posible ruta.",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/fa/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1042,8 +1042,10 @@
"permissions": {
"nostr": {
"getpublickey": "کلید عمومی تان را بخوانید.",
"nip04encrypt": "رمزنگاری داده.",
"nip04decrypt": "رمزگشایی داده.",
"nip04encrypt": "رمزنگاری داده.",
"nip44decrypt": "رمزگشایی داده.",
"nip44encrypt": "رمزنگاری داده.",
"signmessage": "پیام را با کلید خود امضا کنید."
},
"commando": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,8 +808,10 @@
"permissions": {
"nostr": {
"getpublickey": "Lisez votre clé publique.",
"nip04encrypt": "Crypter les données.",
"nip04decrypt": "Déchiffrer les données.",
"nip04encrypt": "Crypter les données.",
"nip44decrypt": "Déchiffrer les données.",
"nip44encrypt": "Crypter les données.",
"signmessage": "Signez le message avec votre clé."
},
"commando": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/hi/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -766,8 +766,10 @@
"permissions": {
"nostr": {
"getpublickey": "अपनी सार्वजनिक कुंजी पढ़ें।",
"nip04encrypt": "डेटा एन्क्रिप्ट करें।",
"nip04decrypt": "डेटा डिक्रिप्ट करें।",
"nip04encrypt": "डेटा एन्क्रिप्ट करें।",
"nip44decrypt": "डेटा डिक्रिप्ट करें।",
"nip44encrypt": "डेटा एन्क्रिप्ट करें।",
"signmessage": "अपनी चाबी से संदेश पर हस्ताक्षर करें।"
},
"commando": {
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/locales/id/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -942,9 +942,11 @@
},
"permissions": {
"nostr": {
"nip04decrypt": "Dekripsi data.",
"getpublickey": "Membaca kunci publik Anda.",
"nip04encrypt": "Enkripsi data."
"nip04decrypt": "Dekripsi data.",
"nip04encrypt": "Enkripsi data.",
"nip44decrypt": "Dekripsi data.",
"nip44encrypt": "Enkripsi data."
},
"commando": {
"decode": "Decode teks bolt11/bolt12/rune."
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -878,9 +878,11 @@
"addinvoice": "Crea nuove ricevute di pagamento."
},
"nostr": {
"nip04decrypt": "Decripta i dati.",
"getpublickey": "Leggi la tua chiave pubblica.",
"nip04decrypt": "Decripta i dati.",
"nip04encrypt": "Cripta i dati.",
"nip44decrypt": "Decripta i dati.",
"nip44encrypt": "Cripta i dati.",
"signmessage": "Firma il messaggio con la tua chiave."
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/mr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -825,8 +825,10 @@
"permissions": {
"nostr": {
"getpublickey": "तुमची सार्वजनिक की वाचा.",
"nip04encrypt": "डेटा Encrypt करा.",
"nip04decrypt": "डेटा Decrypt करा.",
"nip04encrypt": "डेटा Encrypt करा.",
"nip44decrypt": "डेटा Decrypt करा.",
"nip44encrypt": "डेटा Encrypt करा.",
"signmessage": "तुमच्या किल्लीने संदेशावर स्वाक्षरी करा."
},
"commando": {
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/pl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -855,8 +855,10 @@
"permissions": {
"nostr": {
"getpublickey": "Czytanie Twojego klucza publicznego.",
"nip04encrypt": "Szyfrowanie danych.",
"nip04decrypt": "Deszyfrowanie danych.",
"nip04encrypt": "Szyfrowanie danych.",
"nip44decrypt": "Deszyfrowanie danych.",
"nip44encrypt": "Szyfrowanie danych.",
"signmessage": "Podpisywanie wiadomości Twoim kluczem."
},
"commando": {
Expand Down
Loading

0 comments on commit 0f97ae0

Please sign in to comment.