Skip to content

Commit e28dfa2

Browse files
vveerrggclaude
andcommitted
feat: add NIP-46 remote signing, restore NIP-44/NIP-49
Add NIP-46 (Nostr Connect) pure protocol layer for remote signing — bunker URI handling, ephemeral session management, JSON-RPC messages, kind 24133 event wrapping/unwrapping, and convenience request creators. No WebSocket or I/O; consumers provide their own transport. Restore NIP-44 (versioned encrypted payloads) and NIP-49 (ncryptsec) which were lost from git during security refactoring but remained in the published v0.5.0 npm package. Also adds getPublicKeySync(), finalizeEvent(), subpath exports (nostr-crypto-utils/nip44, /nip46, /nip49), and 52 new tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7bb15c7 commit e28dfa2

108 files changed

Lines changed: 3046 additions & 99 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.5.1] - 2026-03-02
9+
10+
### Added
11+
- **NIP-46 (Nostr Connect / Remote Signing):** Pure protocol layer for remote signing
12+
- Bunker URI parsing, creation, and validation
13+
- Session management with ephemeral keypairs and NIP-44 conversation keys
14+
- JSON-RPC request/response creation and parsing
15+
- Kind 24133 event wrapping (encrypt + sign) and unwrapping (decrypt + parse)
16+
- Convenience request creators for all NIP-46 methods
17+
- Response filter helper for relay subscriptions
18+
- Full TypeScript types: `Nip46Method`, `BunkerURI`, `Nip46Request`, `Nip46Response`, `Nip46Session`
19+
- `getPublicKeySync()` — synchronous public key derivation from private key
20+
- `finalizeEvent()` — one-step event creation + signing utility
21+
- Subpath exports: `nostr-crypto-utils/nip44`, `nostr-crypto-utils/nip46`, `nostr-crypto-utils/nip49`
22+
23+
### Restored
24+
- **NIP-44 (Versioned Encrypted Payloads):** Restored from published v0.5.0 (lost during security refactoring)
25+
- `getConversationKey()`, `encrypt()`, `decrypt()`, `calcPaddedLen()`, `v2` API object
26+
- **NIP-49 (Private Key Encryption / ncryptsec):** Restored from published v0.5.0
27+
- `encrypt()` and `decrypt()` for ncryptsec bech32 strings
28+
29+
### Dependencies
30+
- Added `@noble/ciphers` (chacha20, xchacha20poly1305 for NIP-44/49)
31+
- Added `@scure/base` (base64 for NIP-44, bech32 for NIP-49)
32+
833
## [Unreleased]
934

1035
## [0.4.16] - 2025-02-19

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ This library provides essential cryptographic operations and utilities required
1010
- Key management and validation
1111
- Event signing and verification
1212
- Encrypted direct messages (NIP-04)
13+
- Versioned encrypted payloads (NIP-44)
14+
- Remote signing / Nostr Connect (NIP-46)
15+
- Private key encryption / ncryptsec (NIP-49)
1316
- Bech32-encoded entities (NIP-19)
1417
- Delegated event signing (NIP-26)
1518
- Authentication protocol (NIP-42)
@@ -37,6 +40,9 @@ This library provides essential cryptographic operations and utilities required
3740
- NIP-19: Bech32-Encoded Entities
3841
- NIP-26: Delegated Event Signing
3942
- NIP-42: Authentication Protocol
43+
- NIP-44: Versioned Encrypted Payloads
44+
- NIP-46: Nostr Connect (Remote Signing)
45+
- NIP-49: Private Key Encryption (ncryptsec)
4046

4147
## Project Structure
4248

@@ -114,6 +120,9 @@ This library implements the following Nostr Implementation Possibilities (NIPs):
114120
| [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) | bech32-encoded Entities | Human-readable encoding for keys, events, and other entities | ✅ Complete |
115121
| [NIP-26](https://github.com/nostr-protocol/nips/blob/master/26.md) | Delegated Event Signing | Create and verify delegated event signing capabilities | ✅ Complete |
116122
| [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) | Authentication | Client-relay authentication protocol | ✅ Complete |
123+
| [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) | Versioned Encrypted Payloads | Modern encryption replacing NIP-04 (ChaCha20 + HMAC) | ✅ Complete |
124+
| [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md) | Nostr Connect (Remote Signing) | Protocol layer for remote signing via bunker | ✅ Complete |
125+
| [NIP-49](https://github.com/nostr-protocol/nips/blob/master/49.md) | Private Key Encryption | ncryptsec password-encrypted key storage | ✅ Complete |
117126

118127
### NIP-01 Features
119128
- Event creation and serialization
@@ -210,6 +219,61 @@ try {
210219
}
211220
```
212221

222+
### NIP-44 Usage (Encrypted Payloads)
223+
224+
```typescript
225+
import { nip44 } from 'nostr-crypto-utils';
226+
// Or via subpath: import * as nip44 from 'nostr-crypto-utils/nip44';
227+
228+
// Derive a conversation key from ECDH
229+
const conversationKey = nip44.getConversationKey(myPrivKeyBytes, theirPubkeyHex);
230+
231+
// Encrypt
232+
const encrypted = nip44.encrypt('Hello!', conversationKey);
233+
234+
// Decrypt
235+
const plaintext = nip44.decrypt(encrypted, conversationKey);
236+
```
237+
238+
### NIP-46 Usage (Remote Signing)
239+
240+
```typescript
241+
import { nip46 } from 'nostr-crypto-utils';
242+
// Or via subpath: import * as nip46 from 'nostr-crypto-utils/nip46';
243+
244+
// Parse a bunker:// URI
245+
const bunker = nip46.parseBunkerURI('bunker://pubkey...?relay=wss://relay.example.com&secret=abc');
246+
247+
// Create a session (ephemeral keypair + NIP-44 conversation key)
248+
const session = nip46.createSession(bunker.remotePubkey);
249+
250+
// Build a request
251+
const req = nip46.connectRequest(bunker.remotePubkey, bunker.secret);
252+
253+
// Wrap into a kind 24133 encrypted event (ready to publish to relay)
254+
const event = await nip46.wrapEvent(req, session, bunker.remotePubkey);
255+
256+
// On receiving a response event, unwrap it
257+
const response = nip46.unwrapEvent(responseEvent, session);
258+
259+
// Create a filter for subscribing to responses
260+
const filter = nip46.createResponseFilter(session.clientPubkey);
261+
```
262+
263+
### NIP-49 Usage (ncryptsec Key Encryption)
264+
265+
```typescript
266+
import { nip49 } from 'nostr-crypto-utils';
267+
// Or via subpath: import * as nip49 from 'nostr-crypto-utils/nip49';
268+
269+
// Encrypt a private key with a password
270+
const ncryptsec = nip49.encrypt(secretKeyBytes, 'my-password');
271+
// Returns: 'ncryptsec1...'
272+
273+
// Decrypt it back
274+
const secretKey = nip49.decrypt(ncryptsec, 'my-password');
275+
```
276+
213277
### Type System
214278

215279
#### Event Types

dist/browser/116.nostr-crypto-utils.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */

0 commit comments

Comments
 (0)