From 5c03ff3e29d8b59b6cd7e7cd477fe2172fa21826 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Mon, 13 Oct 2025 11:20:01 -0700 Subject: [PATCH] Pass pubkey to nip 55 signer to avoid mixing up sessions --- docs/signer/nip-55.md | 83 ++-------------------------- packages/app/src/session.ts | 2 +- packages/lib/src/Tools.ts | 2 +- packages/signer/src/signers/nip55.ts | 13 ++++- 4 files changed, 16 insertions(+), 84 deletions(-) diff --git a/docs/signer/nip-55.md b/docs/signer/nip-55.md index 9191635..7b05062 100644 --- a/docs/signer/nip-55.md +++ b/docs/signer/nip-55.md @@ -18,7 +18,8 @@ import { Nip55Signer, getNip55 } from '@welshman/signer' // Check for available signing apps const apps = await getNip55() if (apps.length > 0) { - const signer = new Nip55Signer(apps[0].packageName) + const optionalSavedPubkey = localStorage.getItem('my-saved-pubkey') + const signer = new Nip55Signer(apps[0].packageName, optionalSavedPubkey) } ``` @@ -40,28 +41,11 @@ interface AppInfo { ### Constructor ```typescript -constructor(packageName: string) +constructor(packageName: string, publicKey?: string) ``` Creates a new signer instance that will communicate with the specified native app. - `packageName`: The package identifier of the native signing app - -### ISigner implementation - -The `Nip55Signer` class implements the [`ISigner`](/signer/) interface - -```typescript -class Nip55Signer implements ISigner { - // Constructor - constructor(private secret: string) - - // ISigner implementation - sign: SignWithOptions - getPubkey: () => Promise - nip04: { encrypt, decrypt } - nip44: { encrypt, decrypt } -} -``` - +- `publicKey`: optional user pubkey. Recommended for resuming existing signer sessions when the signer is managing multiple user accounts. ## Complete Example @@ -104,62 +88,3 @@ async function example() { } } ``` - -## Implementation Details - -### Request Serialization - -The signer implements a lock mechanism to prevent concurrent requests: - -```typescript -class Nip55Signer implements ISigner { - #lock = Promise.resolve() - #plugin = NostrSignerPlugin - #packageName: string - #packageNameSet = false - - #then = async (f: (signer: typeof NostrSignerPlugin) => Promise) => { - const promise = this.#lock.then(async () => { - if (!this.#packageNameSet) { - await this.#initialize() - } - return f(this.#plugin) - }) - - this.#lock = promise.then(() => Promise.resolve()) - - return promise - } -} -``` - -### Public Key Caching - -The signer caches the public key to minimize native app interactions: - -```typescript -class Nip55Signer { - #npub?: string - #publicKey?: string - - getPubkey = async (): Promise => { - return this.#then(async signer => { - if (!this.#publicKey || !this.#npub) { - const {npub} = await signer.getPublicKey() - this.#npub = npub - const {data} = decode(npub) - this.#publicKey = data as string - } - return this.#publicKey - }) - } -} -``` - - -## Platform Support - -- iOS: Requires compatible signing app -- Android: Requires compatible signing app -- Operations availability depends on native app implementation -- Some features might be platform-specific diff --git a/packages/app/src/session.ts b/packages/app/src/session.ts index aefd9ba..906fb3f 100644 --- a/packages/app/src/session.ts +++ b/packages/app/src/session.ts @@ -239,7 +239,7 @@ export const getSigner = cached({ getValue: ([session]: [Session | undefined]) => { if (isNip07Session(session)) return wrapSigner(new Nip07Signer()) if (isNip01Session(session)) return wrapSigner(new Nip01Signer(session.secret)) - if (isNip55Session(session)) return wrapSigner(new Nip55Signer(session.signer)) + if (isNip55Session(session)) return wrapSigner(new Nip55Signer(session.signer, session.pubkey)) if (isNip46Session(session)) { const { secret: clientSecret, diff --git a/packages/lib/src/Tools.ts b/packages/lib/src/Tools.ts index 19453ce..6dc0fd3 100644 --- a/packages/lib/src/Tools.ts +++ b/packages/lib/src/Tools.ts @@ -622,7 +622,7 @@ export const sortBy = (f: (x: T) => any, xs: T[]) => * @param xs - Array to group * @returns Map of groups */ -export const groupBy = (f: (x: T) => K, xs: T[]) => { +export const groupBy = (f: (x: T) => K, xs: Iterable) => { const r = new Map() for (const x of xs) { diff --git a/packages/signer/src/signers/nip55.ts b/packages/signer/src/signers/nip55.ts index 1a6089b..209b7b9 100644 --- a/packages/signer/src/signers/nip55.ts +++ b/packages/signer/src/signers/nip55.ts @@ -1,5 +1,5 @@ import {NostrSignerPlugin, AppInfo} from "nostr-signer-capacitor-plugin" -import {decode} from "nostr-tools/nip19" +import * as nip19 from "nostr-tools/nip19" import {SignedEvent, StampedEvent} from "@welshman/util" import {hash, own, signWithOptions, SignOptions, ISigner} from "../util.js" @@ -16,8 +16,14 @@ export class Nip55Signer implements ISigner { #npub?: string #publicKey?: string - constructor(packageName: string) { + constructor(packageName: string, publicKey?: string) { this.#packageName = packageName + + if (publicKey) { + this.#publicKey = publicKey + this.#npub = nip19.npubEncode(publicKey) + } + this.#initialize() } @@ -53,8 +59,9 @@ export class Nip55Signer implements ISigner { if (!this.#publicKey || !this.#npub) { try { const {npub} = await signer.getPublicKey() + const {data} = nip19.decode(npub) + this.#npub = npub - const {data} = decode(npub) this.#publicKey = data as string } catch (error) { throw new Error("Failed to get public key")