@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit
125 lines (113 loc) • 4.26 kB
text/typescript
import { getEventHash } from "nostr-tools";
import type { NDKSigner } from "../signers/index.js";
import { NDKPrivateKeySigner } from "../signers/private-key";
import type { NDKEncryptionScheme } from "../types.js";
import { NDKUser } from "../user/index.js";
import { NDKEvent, type NDKRawEvent, type NostrEvent } from "./index.js";
import { NDKKind } from "./kinds/index.js";
export type GiftWrapParams = {
scheme?: NDKEncryptionScheme;
rumorKind?: number;
wrapTags?: string[][];
};
/**
* Instantiate a new (Nip59 gift wrapped) NDKEvent from any NDKEvent
* @param event
* @param recipient
* @param signer
* @param params
* @returns
*/
export async function giftWrap(
event: NDKEvent,
recipient: NDKUser,
signer?: NDKSigner,
params: GiftWrapParams = {}
): Promise<NDKEvent> {
let _signer = signer;
params.scheme ??= "nip44";
if (!_signer) {
if (!event.ndk) throw new Error("no signer available for giftWrap");
_signer = event.ndk.signer;
}
if (!_signer) throw new Error("no signer");
if (!_signer.encryptionEnabled || !_signer.encryptionEnabled(params.scheme))
throw new Error("signer is not able to giftWrap");
const rumor = getRumorEvent(event, params?.rumorKind);
const seal = await getSealEvent(rumor, recipient, _signer, params.scheme);
const wrap = await getWrapEvent(seal, recipient, params);
return new NDKEvent(event.ndk, wrap);
}
/**
* Instantiate a new (Nip59 un-wrapped rumor) NDKEvent from any gift wrapped NDKEvent
* @param event
*/
export async function giftUnwrap(
event: NDKEvent,
sender?: NDKUser,
signer?: NDKSigner,
scheme: NDKEncryptionScheme = "nip44"
): Promise<NDKEvent> {
const _sender = sender || new NDKUser({ pubkey: event.pubkey });
let _signer = signer;
if (!_signer) {
if (!event.ndk) throw new Error("no signer available for giftUnwrap");
_signer = event.ndk.signer;
}
if (!signer) throw new Error("no signer");
try {
const seal = JSON.parse(await signer.decrypt(_sender, event.content, scheme)) as NostrEvent;
if (!seal) throw new Error("Failed to decrypt wrapper");
if (!new NDKEvent(undefined, seal).verifySignature(false))
throw new Error("GiftSeal signature verification failed!");
const rumorSender = new NDKUser({ pubkey: seal.pubkey });
const rumor = JSON.parse(await signer.decrypt(rumorSender, seal.content, scheme));
if (!rumor) throw new Error("Failed to decrypt seal");
if (rumor.pubkey !== seal.pubkey)
throw new Error("Invalid GiftWrap, sender validation failed!");
return new NDKEvent(event.ndk, rumor as NostrEvent);
} catch (_e) {
return Promise.reject("Got error unwrapping event! See console log.");
}
}
function getRumorEvent(event: NDKEvent, kind?: number): NDKEvent {
const rumor = event.rawEvent() as Partial<NDKRawEvent>;
rumor.kind = kind || rumor.kind || NDKKind.PrivateDirectMessage;
rumor.sig = undefined;
rumor.id = getEventHash(rumor as any);
return new NDKEvent(event.ndk, rumor);
}
async function getSealEvent(
rumor: NDKEvent,
recipient: NDKUser,
signer: NDKSigner,
scheme: NDKEncryptionScheme = "nip44"
): Promise<NDKEvent> {
const seal = new NDKEvent(rumor.ndk);
seal.kind = NDKKind.GiftWrapSeal;
seal.created_at = approximateNow(5);
seal.content = JSON.stringify(rumor.rawEvent());
await seal.encrypt(recipient, signer, scheme);
await seal.sign(signer);
return seal;
}
async function getWrapEvent(
sealed: NDKEvent,
recipient: NDKUser,
params?: GiftWrapParams,
scheme: NDKEncryptionScheme = "nip44"
): Promise<NDKEvent> {
const signer = NDKPrivateKeySigner.generate();
const wrap = new NDKEvent(sealed.ndk);
wrap.kind = NDKKind.GiftWrap;
wrap.created_at = approximateNow(5);
if (params?.wrapTags) wrap.tags = params.wrapTags;
wrap.tag(recipient);
wrap.content = JSON.stringify(sealed.rawEvent());
await wrap.encrypt(recipient, signer, scheme);
await wrap.sign(signer);
return wrap;
}
function approximateNow(drift = 0) {
return Math.round(Date.now() / 1000 - Math.random() * 10 ** drift);
}