Add pomade signer

This commit is contained in:
Jon Staab
2025-12-23 16:28:10 -08:00
parent e9b1172d15
commit 282b20c460
8 changed files with 361 additions and 21 deletions
+6 -2
View File
@@ -23,8 +23,8 @@
"@types/throttle-debounce": "^5.0.2",
"@welshman/feeds": "workspace:*",
"@welshman/lib": "workspace:*",
"@welshman/router": "workspace:*",
"@welshman/net": "workspace:*",
"@welshman/router": "workspace:*",
"@welshman/signer": "workspace:*",
"@welshman/store": "workspace:*",
"@welshman/util": "workspace:*",
@@ -32,8 +32,12 @@
"svelte": "^4.2.18",
"throttle-debounce": "^5.0.2"
},
"peerDependencies": {
"@pomade/core": "^0.0.4"
},
"devDependencies": {
"rimraf": "~6.0.0",
"typescript": "~5.8.0"
"typescript": "~5.8.0",
"@pomade/core": "^0.0.4"
}
}
+28
View File
@@ -1,3 +1,4 @@
import {Client, ClientOptions} from "@pomade/core"
import {derived, writable} from "svelte/store"
import {cached, randomId, append, omit, equals, assoc} from "@welshman/lib"
import {withGetter} from "@welshman/store"
@@ -13,6 +14,7 @@ import {
import {
Nip59,
WrappedSigner,
PomadeSigner,
Nip46Broker,
Nip46Signer,
Nip07Signer,
@@ -28,6 +30,7 @@ export enum SessionMethod {
Nip07 = "nip07",
Nip46 = "nip46",
Nip55 = "nip55",
Pomade = "pomade",
Pubkey = "pubkey",
Anonymous = "anonymous",
}
@@ -59,6 +62,12 @@ export type SessionNip55 = {
signer: string
}
export type SessionPomade = {
method: SessionMethod.Pomade
pubkey: string
clientOptions: ClientOptions
}
export type SessionPubkey = {
method: SessionMethod.Pubkey
pubkey: string
@@ -73,6 +82,7 @@ export type SessionAnyMethod =
| SessionNip07
| SessionNip46
| SessionNip55
| SessionPomade
| SessionPubkey
| SessionAnonymous
@@ -109,6 +119,10 @@ export const dropSession = (_pubkey: string) => {
$signer.broker.cleanup()
}
if ($signer instanceof PomadeSigner) {
$signer.client.rpc.stop()
}
pubkey.update($pubkey => ($pubkey === _pubkey ? undefined : $pubkey))
sessions.update($sessions => omit([_pubkey], $sessions))
}
@@ -150,6 +164,12 @@ export const makeNip55Session = (pubkey: string, signer: string): SessionNip55 =
signer,
})
export const makePomadeSession = (pubkey: string, clientOptions: ClientOptions): SessionPomade => ({
method: SessionMethod.Pomade,
pubkey,
clientOptions,
})
export const makePubkeySession = (pubkey: string): SessionPubkey => ({
method: SessionMethod.Pubkey,
pubkey,
@@ -169,6 +189,9 @@ export const isNip46Session = (session?: Session): session is SessionNip46 =>
export const isNip55Session = (session?: Session): session is SessionNip55 =>
session?.method === SessionMethod.Nip55
export const isPomadeSession = (session?: Session): session is SessionPomade =>
session?.method === SessionMethod.Pomade
export const isPubkeySession = (session?: Session): session is SessionPubkey =>
session?.method === SessionMethod.Pubkey
@@ -188,6 +211,9 @@ export const loginWithNip46 = (
export const loginWithNip55 = (pubkey: string, signer: string) =>
addSession(makeNip55Session(pubkey, signer))
export const loginWithPomade = (pubkey: string, clientOptions: ClientOptions) =>
addSession(makePomadeSession(pubkey, clientOptions))
export const loginWithPubkey = (pubkey: string) => addSession(makePubkeySession(pubkey))
// Other stuff
@@ -250,6 +276,8 @@ export const getSigner = cached({
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, session.pubkey))
if (isPomadeSession(session))
return wrapSigner(new PomadeSigner(new Client(session.clientOptions)))
if (isNip46Session(session)) {
const {
secret: clientSecret,
+6 -4
View File
@@ -28,13 +28,15 @@
"@welshman/util": "workspace:*",
"nostr-tools": "^2.18.2"
},
"peerDependencies": {
"nostr-signer-capacitor-plugin": "~0.0.4",
"@pomade/core": "^0.0.4"
},
"devDependencies": {
"@capacitor/core": "^7.2.0",
"nostr-signer-capacitor-plugin": "~0.0.4",
"rimraf": "~6.0.0",
"typescript": "~5.8.0"
},
"peerDependencies": {
"nostr-signer-capacitor-plugin": "~0.0.4"
"typescript": "~5.8.0",
"@pomade/core": "^0.0.4"
}
}
+1
View File
@@ -4,3 +4,4 @@ export * from "./signers/nip01.js"
export * from "./signers/nip07.js"
export * from "./signers/nip46.js"
export * from "./signers/nip55.js"
export * from "./signers/pomade.js"
+68
View File
@@ -0,0 +1,68 @@
import * as nt44 from "nostr-tools/nip44"
import type {Client} from "@pomade/core"
import type {StampedEvent} from "@welshman/util"
import {thrower, hexToBytes} from "@welshman/lib"
import {ISigner, SignOptions, signWithOptions} from "../util.js"
export class PomadeSigner implements ISigner {
#pubkey: string
#sharedSecretCache = new Map<string, Uint8Array<ArrayBuffer>>()
constructor(readonly client: Client) {
this.#pubkey = client.userPubkey
}
private getSharedSecret = async (pubkey: string) => {
let sharedSecret = this.#sharedSecretCache.get(pubkey)
if (!sharedSecret) {
const hexSharedSecret = await this.client.getConversationKey(pubkey)
if (hexSharedSecret) {
sharedSecret = hexToBytes(hexSharedSecret)
this.#sharedSecretCache.set(pubkey, sharedSecret)
}
}
return sharedSecret
}
getPubkey = async () => this.#pubkey
sign = (event: StampedEvent, options: SignOptions = {}) => {
const promise = this.client.sign(event).then(r => {
if (!r.event) {
throw new Error(r.messages[0]?.payload.message || "Failed to sign event")
}
return r.event
})
return signWithOptions(promise, options)
}
nip04 = {
encrypt: thrower("PomadeSigner does not support nip44"),
decrypt: thrower("PomadeSigner does not support nip44"),
}
nip44 = {
encrypt: async (pubkey: string, message: string) => {
const sharedSecret = await this.getSharedSecret(pubkey)
if (!sharedSecret) {
throw new Error("Failed to get shared secret")
}
return nt44.v2.encrypt(message, sharedSecret)
},
decrypt: async (pubkey: string, message: string) => {
const sharedSecret = await this.getSharedSecret(pubkey)
if (!sharedSecret) {
throw new Error("Failed to get shared secret")
}
return nt44.v2.decrypt(message, sharedSecret)
},
}
}
+4
View File
@@ -32,6 +32,8 @@ export type RelayProfile = {
// Utils related to bare urls
export const LOCAL_RELAY_URL = "local://welshman.relay/"
export const isRelayUrl = (url: string) => {
if (!url.includes("://")) {
url = "wss://" + url
@@ -65,6 +67,8 @@ export const isIPAddress = (url: string) => Boolean(url.match(/\d+\.\d+\.\d+\.\d
export const isShareableRelayUrl = (url: string) => Boolean(isRelayUrl(url) && !isLocalUrl(url))
export const normalizeRelayUrl = (url: string) => {
if (url === LOCAL_RELAY_URL) return url
const prefix = url.match(/^wss?:\/\//)?.[0] || (isOnionUrl(url) ? "ws://" : "wss://")
// Use our library to normalize