Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented nip04 #1777

Merged
merged 9 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/extension/background-script/actions/nostr/decryptOrPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import state from "~/extension/background-script/state";
import { MessageDecryptGet } from "~/types";

const decryptOrPrompt = async (message: MessageDecryptGet) => {
if (!("host" in message.origin)) {
console.error("error", message.origin);
return;
}

const result = await prompt(message);

return result;
};

const prompt = async (message: MessageDecryptGet) => {
try {
// TODO: Add prompt & permissions

const response = {
data: state
.getState()
.getNostr()
.decrypt(message.args.peer, message.args.ciphertext),
};

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

export default decryptOrPrompt;
35 changes: 35 additions & 0 deletions src/extension/background-script/actions/nostr/encryptOrPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import state from "~/extension/background-script/state";
import { MessageEncryptGet } from "~/types";

const encryptOrPrompt = async (message: MessageEncryptGet) => {
if (!("host" in message.origin)) {
console.error("error", message.origin);
return;
}

const result = await prompt(message);

return result;
};

const prompt = async (message: MessageEncryptGet) => {
try {
// TODO: Add prompt & permissions

const response = {
data: state
.getState()
.getNostr()
.encrypt(message.args.peer, message.args.plaintext),
};

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

export default encryptOrPrompt;
4 changes: 4 additions & 0 deletions src/extension/background-script/actions/nostr/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import decryptOrPrompt from "./decryptOrPrompt";
import encryptOrPrompt from "./encryptOrPrompt";
import generatePrivateKey from "./generatePrivateKey";
import getPrivateKey from "./getPrivateKey";
import getPublicKeyOrPrompt from "./getPublicKeyOrPrompt";
Expand All @@ -12,4 +14,6 @@ export {
getPublicKeyOrPrompt,
getRelays,
signEventOrPrompt,
encryptOrPrompt,
decryptOrPrompt,
};
29 changes: 29 additions & 0 deletions src/extension/background-script/nostr/__test__/nostr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Nostr from "~/extension/background-script/nostr";

const alice = {
privateKey:
"9ab5b12ade1d9c27207ff0264e9fb155c77c9361c9b6a27c865fce1b2c0ddf0e",
publicKey: "0bf50e2fdc927853c12b64c06f6a703cfad8086e79b18b1eb864f3fab7fc6f74",
};

const bob = {
privateKey:
"b7eab8ab34aac491217a31059ec017e51c63d09c828e39ee3a40a016bc9d0cbf",
publicKey: "519f5ae2cd7d4b970c4edadb2efc947c9b803838de918d1c5bfd4b9c1a143b72",
};

describe("nostr", () => {
test("encrypt & decrypt", async () => {
const nostr = new Nostr();
nostr.getPrivateKey = jest.fn().mockReturnValue(alice.privateKey);

const message = "Secret message that is sent from Alice to Bob";
const encrypted = nostr.encrypt(bob.publicKey, message);

nostr.getPrivateKey = jest.fn().mockReturnValue(bob.privateKey);

const decrypted = nostr.decrypt(alice.publicKey, encrypted);

expect(decrypted).toMatch(message);
});
});
35 changes: 35 additions & 0 deletions src/extension/background-script/nostr/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import * as secp256k1 from "@noble/secp256k1";
import { Buffer } from "buffer";
import { AES } from "crypto-js";
import * as CryptoJS from "crypto-js";
import Base64 from "crypto-js/enc-base64";
import Hex from "crypto-js/enc-hex";
import Utf8 from "crypto-js/enc-utf8";
import { decryptData, encryptData } from "~/common/lib/crypto";
import { Event } from "~/extension/ln/nostr/types";

Expand Down Expand Up @@ -36,6 +42,35 @@ class Nostr {
event.sig = signature;
return event;
}

encrypt(pubkey: string, text: string) {
const key = secp256k1.getSharedSecret(this.getPrivateKey(), "02" + pubkey);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this 02?

const normalizedKey = Buffer.from(key.slice(1, 33));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this doing?
should this be a compressed key?
https://www.npmjs.com/package/@noble/secp256k1#getsharedsecretprivatekeya-publickeyb

isCompressed = false determines whether to return compact (33-byte), or full (65-byte) key

const hexNormalizedKey = secp256k1.utils.bytesToHex(normalizedKey);
const hexKey = Hex.parse(hexNormalizedKey);

const encrypted = AES.encrypt(text, hexKey, {
iv: CryptoJS.lib.WordArray.random(16),
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
});

return `${encrypted.toString()}?iv=${encrypted.iv.toString(
CryptoJS.enc.Base64
)}`;
}

decrypt(pubkey: string, ciphertext: string) {
const [cip, iv] = ciphertext.split("?iv=");
const key = secp256k1.getSharedSecret(this.getPrivateKey(), "02" + pubkey);
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
const normalizedKey = Buffer.from(key.slice(1, 33));
const hexNormalizedKey = secp256k1.utils.bytesToHex(normalizedKey);
const hexKey = Hex.parse(hexNormalizedKey);

const decrypted = AES.decrypt(cip, hexKey, {
iv: Base64.parse(iv),
});

return Utf8.stringify(decrypted);
}
}

export default Nostr;
2 changes: 2 additions & 0 deletions src/extension/background-script/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const routes = {
getPublicKeyOrPrompt: nostr.getPublicKeyOrPrompt,
signEventOrPrompt: nostr.signEventOrPrompt,
getRelays: nostr.getRelays,
encryptOrPrompt: nostr.encryptOrPrompt,
decryptOrPrompt: nostr.decryptOrPrompt,
},
},
};
Expand Down
2 changes: 2 additions & 0 deletions src/extension/content-script/onendnostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const nostrCalls = [
"nostr/getPublicKeyOrPrompt",
"nostr/signEventOrPrompt",
"nostr/getRelays",
"nostr/encryptOrPrompt",
"nostr/decryptOrPrompt",
];
let callActive = false;

Expand Down
4 changes: 2 additions & 2 deletions src/extension/ln/nostr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ class Nip04 {
}

async encrypt(peer: string, plaintext: string): Promise<string> {
throw new Error("Nip04 is not yet implemented.");
return this.provider.execute("encryptOrPrompt", { peer, plaintext });
}

async decrypt(peer: string, ciphertext: string): Promise<string> {
throw new Error("Nip04 is not yet implemented.");
return this.provider.execute("decryptOrPrompt", { peer, ciphertext });
}
}
16 changes: 16 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,22 @@ export interface MessageSignEvent extends MessageDefault {
action: "signEvent";
}

export interface MessageEncryptGet extends MessageDefault {
args: {
peer: string;
plaintext: string;
};
action: "encrypt";
}

export interface MessageDecryptGet extends MessageDefault {
args: {
peer: string;
ciphertext: string;
};
action: "decrypt";
}

export interface LNURLChannelServiceResponse {
uri: string; // Remote node address of form node_key@ip_address:port_number
callback: string; // a second-level URL which would initiate an OpenChannel message from target LN node
Expand Down