snort/packages/system/src/signer.ts

94 lines
3.0 KiB
TypeScript
Raw Normal View History

2023-08-17 18:54:14 +00:00
import { bytesToHex } from "@noble/curves/abstract/utils";
import { getPublicKey } from "@snort/shared";
import { EventExt } from "./event-ext";
import { Nip4WebCryptoEncryptor } from "./impl/nip4";
import { XChaCha20Encryptor } from "./impl/nip44";
2023-09-24 12:33:12 +00:00
import { MessageEncryptorVersion, decodeEncryptionPayload, encodeEncryptionPayload } from "./index";
2024-04-22 13:38:14 +00:00
import { NostrEvent, NotSignedNostrEvent } from "./nostr";
2023-08-17 18:54:14 +00:00
import { base64 } from "@scure/base";
2023-09-05 13:57:50 +00:00
export type SignerSupports = "nip04" | "nip44" | string;
2023-08-17 18:54:14 +00:00
export interface EventSigner {
init(): Promise<void>;
getPubKey(): Promise<string> | string;
nip4Encrypt(content: string, key: string): Promise<string>;
nip4Decrypt(content: string, otherKey: string): Promise<string>;
nip44Encrypt(content: string, key: string): Promise<string>;
nip44Decrypt(content: string, otherKey: string): Promise<string>;
2024-04-22 13:38:14 +00:00
sign(ev: NostrEvent | NotSignedNostrEvent): Promise<NostrEvent>;
2023-09-05 13:57:50 +00:00
get supports(): Array<SignerSupports>;
2023-08-17 18:54:14 +00:00
}
export class PrivateKeySigner implements EventSigner {
#publicKey: string;
#privateKey: string;
constructor(privateKey: string | Uint8Array) {
if (typeof privateKey === "string") {
this.#privateKey = privateKey;
} else {
this.#privateKey = bytesToHex(privateKey);
}
this.#publicKey = getPublicKey(this.#privateKey);
}
2023-09-05 13:57:50 +00:00
get supports(): string[] {
2023-09-05 14:16:50 +00:00
return ["nip04", "nip44"];
2023-09-05 13:57:50 +00:00
}
2023-08-17 18:54:14 +00:00
get privateKey() {
return this.#privateKey;
}
init(): Promise<void> {
return Promise.resolve();
}
getPubKey(): string {
return this.#publicKey;
}
async nip4Encrypt(content: string, key: string) {
const enc = new Nip4WebCryptoEncryptor();
const secret = enc.getSharedSecret(this.privateKey, key);
const data = await enc.encryptData(content, secret);
return `${base64.encode(data.ciphertext)}?iv=${base64.encode(data.nonce)}`;
}
async nip4Decrypt(content: string, otherKey: string) {
const enc = new Nip4WebCryptoEncryptor();
const secret = enc.getSharedSecret(this.privateKey, otherKey);
const [ciphertext, iv] = content.split("?iv=");
return await enc.decryptData(
{
ciphertext: base64.decode(ciphertext),
nonce: base64.decode(iv),
v: MessageEncryptorVersion.Nip4,
},
2023-08-17 23:35:48 +00:00
secret,
2023-08-17 18:54:14 +00:00
);
}
async nip44Encrypt(content: string, key: string) {
const enc = new XChaCha20Encryptor();
const shared = enc.getSharedSecret(this.#privateKey, key);
const data = enc.encryptData(content, shared);
2023-09-24 12:33:12 +00:00
return encodeEncryptionPayload(data);
2023-08-17 18:54:14 +00:00
}
async nip44Decrypt(content: string, otherKey: string) {
2023-09-24 12:33:12 +00:00
const payload = decodeEncryptionPayload(content);
2023-08-17 18:54:14 +00:00
if (payload.v !== MessageEncryptorVersion.XChaCha20) throw new Error("Invalid payload version");
const enc = new XChaCha20Encryptor();
const shared = enc.getSharedSecret(this.#privateKey, otherKey);
return enc.decryptData(payload, shared);
}
sign(ev: NostrEvent): Promise<NostrEvent> {
EventExt.sign(ev, this.#privateKey);
return Promise.resolve(ev);
}
}