138 lines
3.3 KiB
Markdown
138 lines
3.3 KiB
Markdown
# NIP-46 (Nostr Connect) Signer
|
|
|
|
The `Nip46Signer` implements remote signing capabilities through the Nostr Connect protocol (NIP-46). It allows applications to delegate signing operations to a remote signer (like a Nostr Bunker), providing enhanced security by keeping private keys separate from the application.
|
|
|
|
## Architecture
|
|
|
|
The implementation consists of two main classes:
|
|
- `Nip46Broker`: Handles the communication with the remote signer
|
|
- `Nip46Signer`: Implements the `ISigner` interface using the broker
|
|
|
|
## Example
|
|
|
|
```typescript
|
|
import {
|
|
makeSecret,
|
|
Nip46Broker,
|
|
Nip46Signer
|
|
} from '@welshman/signer'
|
|
import { createEvent, NOTE } from '@welshman/util'
|
|
|
|
async function connectToRemoteSigner() {
|
|
// Initial setup
|
|
const clientSecret = makeSecret()
|
|
const relays = ['wss://relay.example.com']
|
|
const broker = Nip46Broker.get({ relays, clientSecret })
|
|
const signer = new Nip46Signer(broker)
|
|
|
|
// Generate connection URL
|
|
const ncUrl = await broker.makeNostrconnectUrl({
|
|
name: "My App",
|
|
description: "Testing remote signing"
|
|
})
|
|
|
|
// Show URL to user (e.g., as QR code)
|
|
displayQRCode(ncUrl)
|
|
|
|
try {
|
|
// Wait for connection
|
|
const response = await broker.waitForNostrconnect(
|
|
ncUrl,
|
|
new AbortController()
|
|
)
|
|
|
|
// Store signer info for later
|
|
const bunkerUrl = broker.getBunkerUrl()
|
|
localStorage.setItem('bunkerUrl', bunkerUrl)
|
|
|
|
// Use the signer
|
|
const event = createEvent(NOTE, {
|
|
content: "Signed with remote signer!",
|
|
tags: [["t", "test"]]
|
|
})
|
|
const signed = await signer.sign(event)
|
|
|
|
return signed
|
|
} catch (error) {
|
|
if (error?.error) {
|
|
console.warn(`Signer error: ${error.error}`)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// Reconnecting with saved bunker URL
|
|
async function reconnect() {
|
|
const bunkerUrl = localStorage.getItem('bunkerUrl')
|
|
if (!bunkerUrl) return null
|
|
|
|
const {
|
|
signerPubkey,
|
|
connectSecret,
|
|
relays
|
|
} = Nip46Broker.parseBunkerUrl(bunkerUrl)
|
|
|
|
const broker = Nip46Broker.get({
|
|
relays,
|
|
clientSecret: makeSecret(),
|
|
signerPubkey,
|
|
connectSecret
|
|
})
|
|
|
|
return new Nip46Signer(broker)
|
|
}
|
|
```
|
|
|
|
## Nip46Broker API
|
|
|
|
### Constructor and Factory
|
|
|
|
```typescript
|
|
// Recommended: use the singleton factory
|
|
const broker = Nip46Broker.get({
|
|
relays: string[],
|
|
clientSecret: string,
|
|
connectSecret?: string,
|
|
signerPubkey?: string,
|
|
algorithm?: "nip04" | "nip44"
|
|
})
|
|
|
|
// Direct instantiation (not recommended)
|
|
new Nip46Broker(params)
|
|
```
|
|
|
|
### Connection Methods
|
|
|
|
```typescript
|
|
// Generate a nostrconnect:// URL for the remote signer
|
|
broker.makeNostrconnectUrl(metadata: Record<string, string>): Promise<string>
|
|
|
|
// Wait for connection approval
|
|
broker.waitForNostrconnect(
|
|
url: string,
|
|
abort?: AbortController
|
|
): Promise<Nip46ResponseWithResult>
|
|
|
|
// Get bunker URL for later reconnection
|
|
broker.getBunkerUrl(): string
|
|
|
|
// Parse a bunker URL
|
|
Nip46Broker.parseBunkerUrl(url: string): {
|
|
signerPubkey: string,
|
|
connectSecret: string,
|
|
relays: string[]
|
|
}
|
|
```
|
|
|
|
## Nip46Signer Usage
|
|
|
|
```typescript
|
|
const signer = new Nip46Signer(broker)
|
|
|
|
// All ISigner operations are available
|
|
const pubkey = await signer.getPubkey()
|
|
const signed = await signer.sign(event)
|
|
const encrypted = await signer.nip44.encrypt(pubkey, "message")
|
|
const decrypted = await signer.nip44.decrypt(pubkey, encrypted)
|
|
```
|