Threshold signing SDK for FROSTR - Distributed key custody using Nostr relays
FROSTR (Frost Over Nostr) is a threshold cryptography protocol that enables distributed key custody using Nostr relays as the communication layer. It implements the FROST (Flexible Round-Optimized Schnorr Threshold) signature scheme.
The problem: Traditional key management creates single points of failure. If a private key is compromised, lost, or held by a single party that becomes unavailable, access to funds or signing capability is lost.
The solution: Threshold signatures split a key into multiple shares distributed among different parties. M-of-N shareholders must collaborate to produce a signature. No single party ever possesses the complete key, even during signing. The resulting signature is a standard BIP-340 Schnorr signature - indistinguishable from one produced by a single signer.
Why Nostr? Nostr provides a decentralized relay network that's already battle-tested for message passing. FROSTR leverages this existing infrastructure for peer communication, with end-to-end encryption ensuring relay operators never see message contents.
- Threshold Signing: M-of-N signing where no single party holds the complete key
- ECDH Key Exchange: Collaborative derivation of shared secrets
- End-to-End Encryption: All peer communication encrypted with ChaCha20-Poly1305
- Nostr Transport: Uses existing relay infrastructure for decentralized communication
- BIP-340 Compatible: Produces standard Schnorr signatures usable on Bitcoin and Nostr
- TypeScript First: Full type safety with strict mode enabled
npm install @frostr/bifrostimport { BifrostNode } from '@frostr/bifrost'
import { generate_dealer_package } from '@frostr/bifrost/lib'
// Generate a 2-of-3 threshold group
const { group, shares } = generate_dealer_package(2, 3)
// Create and connect a node
const node = new BifrostNode(group, shares[0], ['wss://relay.example.com'])
await node.connect()
// Exchange nonces with a peer
await node.req.ping(shares[1].pubkey)
// Request a threshold signature
const result = await node.req.sign('Hello FROSTR!')
if (!result.ok) throw new Error('signing failed!')
console.log('Signature:', result.data)See the Guide for complete examples.
| Method | Description |
|---|---|
node.req.sign(message) |
Request threshold signature |
node.req.sign_batch(messages) |
Request multiple threshold signatures |
node.req.ecdh(pubkey) |
Collaborative ECDH key exchange |
node.req.ecdh_batch(pubkeys) |
Batch ECDH key exchange for multiple pubkeys |
node.req.ping(peer) |
Ping peer and exchange nonces |
node.req.echo(message) |
Test relay connectivity |
node.req.onboard(pubkey) |
Onboard a new peer with group credentials |
See API Reference for full documentation.
BifrostNode emits typed events for lifecycle and operations:
| Event | Payload | Description |
|---|---|---|
ready |
BifrostNode |
Node connected and ready for operations |
closed |
BifrostNode |
Node disconnected |
message |
RpcMessageData |
Raw message received (for debugging) |
bounced |
[reason, msg] |
Message rejected (unauthorized or invalid) |
| Event | Payload | Description |
|---|---|---|
/sign/sender/req |
SignSessionPackage |
Signature request sent to peers |
/sign/sender/res |
PartialSigPackage[] |
Partial signatures received from peers |
/sign/sender/sig |
[SignatureEntry[], msgs] |
Final signatures aggregated |
/sign/sender/rej |
[reason, session] |
Signature request phase failed |
/sign/sender/err |
[reason, msgs] |
Signature aggregation failed |
/sign/handler/req |
msg |
Incoming sign request from peer |
/sign/handler/res |
msg |
Partial signature sent to requester |
/sign/handler/rej |
[reason, msg] |
Sign request rejected |
| Event | Payload | Description |
|---|---|---|
/ecdh/sender/res |
msgs |
ECDH shares received from peers |
/ecdh/sender/ret |
[ecdh_pk, secret] |
Shared secret derived |
/ecdh/sender/rej |
[reason, pkg] |
ECDH request phase failed |
/ecdh/sender/err |
[reason, msgs] |
ECDH derivation failed |
/ecdh/handler/req |
msg |
Incoming ECDH request from peer |
/ecdh/handler/res |
msg |
ECDH share sent to requester |
/ecdh/handler/rej |
[reason, msg] |
ECDH request rejected |
| Document | Description |
|---|---|
| Guide | Getting started with code examples and interactive demo |
| API Reference | Full API documentation |
| Security Model | Threat model, guarantees, and deployment guidance |
| Technical Docs | Architecture, protocol, and cryptography |
npm install # Install dependencies
npm test # Run test suite
npm run build # Build for productionSee the protocol in action:
npm run demo:keygen # Generate test credentials
npm run demo # Launch 4-pane tmux demo (auto-cleanup on close)See Guide for full instructions.
Contributions are welcome! Please:
- Open an issue to discuss significant changes before starting work
- Fork the repository and create a feature branch
- Write tests for new functionality
- Ensure all tests pass (
npm test) - Submit a pull request with a clear description
See SECURITY.md for:
- Threat model and security guarantees
- What FROSTR protects against (and what it doesn't)
- Secure deployment checklist
- Reporting security vulnerabilities
MIT License - see LICENSE for details.
- FROST Paper by Chelsea Komlo and Ian Goldberg
- Noble Cryptography by Paul Miller
- Nostr Protocol community