feat: nsecbunker
This commit is contained in:
@ -27,64 +27,93 @@ interface Nip46Request {
|
||||
interface Nip46Response {
|
||||
id: string
|
||||
result: any
|
||||
error?: string
|
||||
error: string
|
||||
}
|
||||
|
||||
interface QueueObj {
|
||||
resolve: (o: Nip46Response) => void;
|
||||
resolve: (o: any) => void;
|
||||
reject: (e: Error) => void;
|
||||
}
|
||||
|
||||
export class Nip46Signer implements EventSigner {
|
||||
#conn?: Connection;
|
||||
#relay: string;
|
||||
#target: string;
|
||||
#localPubkey: string;
|
||||
#remotePubkey?: string;
|
||||
#token?: string;
|
||||
#insideSigner: EventSigner;
|
||||
#commandQueue: Map<string, QueueObj> = new Map();
|
||||
#log = debug("NIP-46");
|
||||
#proto: string;
|
||||
#didInit: boolean = false;
|
||||
|
||||
constructor(config: string, insideSigner?: EventSigner) {
|
||||
const u = new URL(config);
|
||||
this.#target = u.pathname.substring(2);
|
||||
this.#proto = u.protocol;
|
||||
this.#localPubkey = u.pathname.substring(2);
|
||||
|
||||
if (u.hash.length > 1) {
|
||||
this.#token = u.hash.substring(1);
|
||||
}
|
||||
if (this.#target.startsWith("npub")) {
|
||||
this.#target = bech32ToHex(this.#target);
|
||||
if (this.#localPubkey.startsWith("npub")) {
|
||||
this.#localPubkey = bech32ToHex(this.#localPubkey);
|
||||
}
|
||||
|
||||
this.#relay = unwrap(u.searchParams.get("relay"));
|
||||
this.#insideSigner = insideSigner ?? new PrivateKeySigner(secp256k1.utils.randomPrivateKey())
|
||||
}
|
||||
|
||||
get relays() {
|
||||
return [this.#relay];
|
||||
}
|
||||
|
||||
async init() {
|
||||
const isBunker = this.#proto === "bunker:";
|
||||
if (isBunker) {
|
||||
this.#remotePubkey = this.#localPubkey;
|
||||
this.#localPubkey = await this.#insideSigner.getPubKey();
|
||||
}
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
this.#conn = new Connection(this.#relay, { read: true, write: true });
|
||||
this.#conn.OnEvent = async (sub, e) => {
|
||||
await this.#onReply(e);
|
||||
}
|
||||
this.#conn.OnConnected = async () => {
|
||||
const insidePubkey = await this.#insideSigner.getPubKey();
|
||||
this.#conn!.QueueReq(["REQ", "reply", {
|
||||
kinds: [NIP46_KIND],
|
||||
authors: [this.#target],
|
||||
"#p": [insidePubkey]
|
||||
"#p": [this.#localPubkey]
|
||||
}], () => { });
|
||||
|
||||
const rsp = await this.#connect(insidePubkey);
|
||||
if (rsp === "ack") {
|
||||
if (isBunker) {
|
||||
await this.#connect(unwrap(this.#remotePubkey));
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
this.#commandQueue.set("connect", {
|
||||
reject,
|
||||
resolve
|
||||
})
|
||||
}
|
||||
}
|
||||
this.#conn.Connect();
|
||||
this.#didInit = true;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.#conn) {
|
||||
await this.#disconnect();
|
||||
this.#conn.CloseReq("reply");
|
||||
this.#conn.Close();
|
||||
this.#conn = undefined;
|
||||
this.#didInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
async describe() {
|
||||
return await this.#rpc<Array<string>>("describe", []);
|
||||
}
|
||||
|
||||
async getPubKey() {
|
||||
return await this.#rpc<string>("get_public_key", []);
|
||||
}
|
||||
@ -98,7 +127,12 @@ export class Nip46Signer implements EventSigner {
|
||||
}
|
||||
|
||||
async sign(ev: NostrEvent) {
|
||||
return await this.#rpc<NostrEvent>("nip04_decrypt", [ev]);
|
||||
const evStr = await this.#rpc<string>("sign_event", [JSON.stringify(ev)]);
|
||||
return JSON.parse(evStr);
|
||||
}
|
||||
|
||||
async #disconnect() {
|
||||
return await this.#rpc("disconnect", []);
|
||||
}
|
||||
|
||||
async #connect(pk: string) {
|
||||
@ -114,10 +148,21 @@ export class Nip46Signer implements EventSigner {
|
||||
throw new Error("Unknown event kind");
|
||||
}
|
||||
|
||||
const decryptedContent = await this.#insideSigner.nip4Decrypt(e.content, this.#target);
|
||||
const reply = JSON.parse(decryptedContent) as Nip46Response;
|
||||
const decryptedContent = await this.#insideSigner.nip4Decrypt(e.content, e.pubkey);
|
||||
const reply = JSON.parse(decryptedContent) as Nip46Request | Nip46Response;
|
||||
|
||||
const pending = this.#commandQueue.get(reply.id);
|
||||
let id = reply.id;
|
||||
this.#log("Recv: %O", reply);
|
||||
if ("method" in reply && reply.method === "connect") {
|
||||
this.#remotePubkey = reply.params[0];
|
||||
await this.#sendCommand({
|
||||
id: reply.id,
|
||||
result: "ack",
|
||||
error: ""
|
||||
}, unwrap(this.#remotePubkey));
|
||||
id = "connect";
|
||||
}
|
||||
const pending = this.#commandQueue.get(id);
|
||||
if (!pending) {
|
||||
throw new Error("No pending command found");
|
||||
}
|
||||
@ -127,32 +172,38 @@ export class Nip46Signer implements EventSigner {
|
||||
}
|
||||
|
||||
async #rpc<T>(method: string, params: Array<any>) {
|
||||
if (!this.#didInit) {
|
||||
await this.init();
|
||||
}
|
||||
if (!this.#conn) throw new Error("Connection error");
|
||||
|
||||
const id = uuid();
|
||||
const payload = {
|
||||
id,
|
||||
id: uuid(),
|
||||
method,
|
||||
params,
|
||||
} as Nip46Request;
|
||||
this.#log("Request: %O", payload);
|
||||
|
||||
const eb = new EventBuilder();
|
||||
eb.kind(NIP46_KIND as EventKind)
|
||||
.content(await this.#insideSigner.nip4Encrypt(JSON.stringify(payload), this.#target))
|
||||
.tag(["p", this.#target]);
|
||||
|
||||
const evCommand = await eb.buildAndSign(this.#insideSigner);
|
||||
await this.#conn.SendAsync(evCommand);
|
||||
|
||||
this.#sendCommand(payload, unwrap(this.#remotePubkey));
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
this.#commandQueue.set(id, {
|
||||
this.#commandQueue.set(payload.id, {
|
||||
resolve: async (o: Nip46Response) => {
|
||||
this.#log("Reply: %O", o);
|
||||
resolve(o.result as T);
|
||||
},
|
||||
reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async #sendCommand(payload: Nip46Request | Nip46Response, target: string) {
|
||||
if (!this.#conn) return;
|
||||
|
||||
const eb = new EventBuilder();
|
||||
eb.kind(NIP46_KIND as EventKind)
|
||||
.content(await this.#insideSigner.nip4Encrypt(JSON.stringify(payload), target))
|
||||
.tag(["p", target]);
|
||||
|
||||
this.#log("Send: %O", payload);
|
||||
const evCommand = await eb.buildAndSign(this.#insideSigner);
|
||||
await this.#conn.SendAsync(evCommand);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user