Add vitepress docs

This commit is contained in:
Ticruz
2025-02-04 14:43:40 +01:00
committed by Jon Staab
parent 43255bcb74
commit 94375a56ec
84 changed files with 10821 additions and 139 deletions
+23
View File
@@ -0,0 +1,23 @@
# @welshman/signer
A comprehensive Nostr signing implementation that supports multiple authentication methods and encryption standards.
It provides a unified interface for working with different signing mechanisms while maintaining compatibility with various Nostr Implementation Possibilities (NIPs).
## What's Included
- **ISigner Interface** - Unified API across all authentication methods
- **NIP-01 Signer** - Core implementation using key-pair cryptography
- **NIP-07 Signer** - Browser extension support (nos2x, Alby, etc.)
- **NIP-46 Signer** - Remote signing with Nostr Connect protocol
- **NIP-55 Signer** - Native app integration via Capacitor
- **NIP-59 Utils** - Gift Wrap protocol for secure event encryption
## Installation
```bash
npm install @welshman/signer
```
+23
View File
@@ -0,0 +1,23 @@
# ISigner Interface
A basic interface that each signer must implement.
It includes methods for signing messages, verifying signatures, and encrypting/decrypting data.
```typescript
interface ISigner {
// Core signing functionality
sign: (event: StampedEvent) => Promise<SignedEvent>
getPubkey: () => Promise<string>
// Encryption capabilities
nip04: {
encrypt: (pubkey: string, message: string) => Promise<string>
decrypt: (pubkey: string, message: string) => Promise<string>
}
nip44: {
encrypt: (pubkey: string, message: string) => Promise<string>
decrypt: (pubkey: string, message: string) => Promise<string>
}
}
```
+41
View File
@@ -0,0 +1,41 @@
# NIP-01 Signer
The `Nip01Signer` class implements the `ISigner` interface and extends it with additional static utility methods:
```typescript
class Nip01Signer implements ISigner {
// Constructor
constructor(private secret: string)
// ISigner implementation
sign: (event: StampedEvent) => Promise<SignedEvent>
getPubkey: () => Promise<string>
nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt }
// Additional static utility methods
static fromSecret(secret: string): Nip01Signer
static ephemeral(): Nip01Signer
}
```
### Additional Methods
The NIP-01 implementation extends the base interface with two static utility methods:
- `static fromSecret(secret: string)`: Alternative constructor for creating a signer from an existing private key
- `static ephemeral()`: Creates a new signer with a randomly generated private key
### Usage Example
```typescript
import { ISigner } from './interfaces'
import { Nip01Signer } from './signers/nip01'
// Using the standard interface
const signer: ISigner = new Nip01Signer(mySecret)
// Using NIP-01 specific utilities
const ephemeralSigner = Nip01Signer.ephemeral()
const fromExistingKey = Nip01Signer.fromSecret(mySecret)
```
+91
View File
@@ -0,0 +1,91 @@
# NIP-07 Signer
The `Nip07Signer` implements the `ISigner` interface by delegating signing operations to a NIP-07 compatible browser extension (like nos2x or Alby). It provides a way to interact with user's keys that are securely stored in their browser extension.
## Browser Detection
```typescript
import { getNip07 } from '@welshman/signer'
// Check if a NIP-07 provider is available
if (getNip07()) {
// Browser has a compatible extension installed
}
```
## Usage
```typescript
import { Nip07Signer } from '@welshman/signer'
// Create a new signer instance
const signer = new Nip07Signer()
// The extension will prompt the user for permission
// when operations are performed
```
## Complete Example
```typescript
import { Nip07Signer, getNip07 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util'
async function example() {
// Check for NIP-07 provider
if (!getNip07()) {
throw new Error('No NIP-07 provider found. Please install a Nostr browser extension.')
}
// Create signer
const signer = new Nip07Signer()
try {
// Get public key (will prompt user)
const pubkey = await signer.getPubkey()
console.log('Public key:', pubkey)
// Create and sign an event (will prompt user)
const event = createEvent(NOTE, {
content: "Hello via browser extension!",
tags: [["t", "test"]]
})
const signedEvent = await signer.sign(event)
console.log('Signed event:', signedEvent)
// Encrypt a message (will prompt user)
const recipientPubkey = "..."
const encrypted = await signer.nip44.encrypt(recipientPubkey, "Secret message")
console.log('Encrypted message:', encrypted)
} catch (error) {
// Handle user rejection or other errors
console.error('Operation failed:', error)
}
}
```
## Request Serialization
The signer implements a lock mechanism to prevent concurrent calls to the extension:
```typescript
class Nip07Signer implements ISigner {
#lock = Promise.resolve()
#then = async <T>(f: (ext: Nip07) => T | Promise<T>) => {
const promise = this.#lock.then(() => {
const ext = getNip07()
if (!ext) throw new Error("Nip07 is not enabled")
return f(ext)
})
// Reset lock after completion or error
this.#lock = promise.then(
() => undefined,
() => undefined
)
return promise
}
}
```
+153
View File
@@ -0,0 +1,153 @@
# 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
## Getting Started
```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[]
}
```
### Remote Operations
```typescript
// Basic operations
broker.ping(): Promise<string>
broker.getPublicKey(): Promise<string>
broker.connect(connectSecret?: string, perms?: string): Promise<string>
// Signing and encryption
broker.signEvent(event: StampedEvent): Promise<SignedEvent>
broker.nip04Encrypt(pk: string, message: string): Promise<string>
broker.nip04Decrypt(pk: string, message: string): Promise<string>
broker.nip44Encrypt(pk: string, message: string): Promise<string>
broker.nip44Decrypt(pk: string, message: string): Promise<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)
```
+165
View File
@@ -0,0 +1,165 @@
# NIP-55 (Native App) Signer
The `Nip55Signer` implements the `ISigner` interface by communicating with native mobile signing applications through the Capacitor plugin system. This implementation is particularly useful for mobile applications that want to leverage native Nostr signing capabilities.
## Prerequisites
The signer requires the Capacitor plugin to be installed:
```bash
npm install nostr-signer-capacitor-plugin
```
## Getting Started
```typescript
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)
}
```
## API Reference
### Detecting Available Signers
```typescript
// Returns information about installed signing apps
getNip55(): Promise<AppInfo[]>
interface AppInfo {
name: string
packageName: string
// Other app-specific information
}
```
### Constructor
```typescript
constructor(packageName: 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: (event: StampedEvent) => Promise<SignedEvent>
getPubkey: () => Promise<string>
nip04: { encrypt, decrypt }
nip44: { encrypt, decrypt }
}
```
## Complete Example
```typescript
import { Nip55Signer, getNip55 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util'
async function example() {
try {
// Get available signing apps
const apps = await getNip55()
if (apps.length === 0) {
throw new Error('No native signing apps available')
}
// Create signer with first available app
const signer = new Nip55Signer(apps[0].packageName)
// Get public key
const pubkey = await signer.getPubkey()
console.log('Public key:', pubkey)
// Sign an event
const event = createEvent(NOTE, {
content: "Hello from native app!",
tags: [["t", "test"]]
})
const signedEvent = await signer.sign(event)
console.log('Signed event:', signedEvent)
// Encrypt a message
const encrypted = await signer.nip44.encrypt(
recipientPubkey,
"Secret message"
)
console.log('Encrypted:', encrypted)
} catch (error) {
console.error('Native signer error:', error)
}
}
```
## 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 <T>(f: (signer: typeof NostrSignerPlugin) => Promise<T>) => {
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<string> => {
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
+151
View File
@@ -0,0 +1,151 @@
# NIP-59 (Gift Wrap) Implementation
The `Nip59` class provides utilities for implementing the Gift Wrap protocol (NIP-59), allowing secure event wrapping and unwrapping. This implementation works with any signer that supports encryption, making it versatile for different authentication methods.
## Key Features
- Event wrapping (encryption) for specific recipients
- Event unwrapping (decryption) of received wrapped events
- Automatic ephemeral wrapper generation
- Caching of previously unwrapped events
- Compatible with all signer implementations
## Basic Usage
```typescript
import { Nip59 } from '@welshman/signer'
import { createEvent, DIRECT_MESSAGE } from '@welshman/util'
// Create a NIP-59 instance from any signer
const nip59 = Nip59.fromSigner(mySigner)
// Wrap an event
const rumor = await nip59.wrap(
recipientPubkey,
createEvent(DIRECT_MESSAGE, {
content: "Secret message",
tags: [["p", recipientPubkey]]
})
)
// The wrapped event to publish
const wrappedEvent = rumor.wrap
// Unwrap a received event
const unwrapped = await nip59.unwrap(receivedWrappedEvent)
```
### Wrapping Process
The wrapping process involves multiple steps:
1. Create the rumor (original event)
2. Create the seal (encrypted rumor)
3. Create the wrap (encrypted seal)
```typescript
export const wrap = async (
signer: ISigner,
wrapper: ISigner,
pubkey: string,
template: StampedEvent,
tags: string[][] = []
) => {
const rumor = await getRumor(signer, template)
const seal = await getSeal(signer, pubkey, rumor)
const wrap = await getWrap(wrapper, pubkey, seal, tags)
return Object.assign(rumor, {wrap})
}
```
## API Reference
### Constructor & Factory Methods
```typescript
class Nip59 {
// Constructor
constructor(signer: ISigner, wrapper?: ISigner)
// Factory Methods
static fromSigner(signer: ISigner): Nip59
static fromSecret(secret: string): Nip59
// Instance Methods
/**
* Wraps an event for a specific recipient
* @param pubkey Recipient's public key
* @param template The event to wrap
* @param tags Additional tags for the wrap event (optional)
* @returns Promise<UnwrappedEvent> Original event and its wrapped version
*/
wrap(
pubkey: string,
template: StampedEvent,
tags?: string[][]
): Promise<UnwrappedEvent>
/**
* Unwraps a received wrapped event
* @param event The wrapped event to decrypt
* @returns Promise<UnwrappedEvent> The original unwrapped event
*/
unwrap(event: SignedEvent): Promise<UnwrappedEvent>
/**
* Creates a new instance with a specific wrapper signer
* @param wrapper Signer to use for wrapping events
* @returns Nip59 New instance with the specified wrapper
*/
withWrapper(wrapper: ISigner): Nip59
}
```
## Detailed Examples
### Basic Wrapping & Unwrapping
```typescript
import { Nip59, Nip01Signer } from '@welshman/signer'
import { createEvent, DIRECT_MESSAGE } from '@welshman/util'
async function example() {
// Create NIP-59 instance
const signer = new Nip01Signer(mySecret)
const nip59 = Nip59.fromSigner(signer)
// Create and wrap an event
const event = createEvent(DIRECT_MESSAGE, {
content: "Secret message",
tags: [["p", recipientPubkey]]
})
const rumor = await nip59.wrap(recipientPubkey, event)
// rumor contains:
// - The original event (rumor)
// - The wrapped version to publish (rumor.wrap)
// Later, unwrap a received event
const unwrapped = await nip59.unwrap(receivedEvent)
}
```
### Custom Wrapper Signer
```typescript
import { Nip59, Nip01Signer } from '@welshman/signer'
// Create with specific wrapper
const nip59 = new Nip59(
mainSigner,
Nip01Signer.ephemeral() // Custom wrapper
)
// Or add wrapper to existing instance
const nip59WithWrapper = nip59.withWrapper(
Nip01Signer.ephemeral()
)
```