Add Nostr-native architecture - Nostr as infrastructure backbone

Comprehensive redesign using Nostr for all ATM communication:

Lightning.Pub as Core Server:
- Replaces lamassu-server entirely
- Nostr-native account system wrapping LND
- Zero server config (no DNS/SSL/ports)
- CLINK native, one-line deployment
- Built-in liquidity management

Private Nostr Relay:
- NIP-42 authenticated relay (strfry/rnostr)
- Whitelist: ATM npubs + Operator npubs only
- Encrypted command/control channel
- Negentropy sync for offline reconciliation

Machine Identity:
- Each ATM has Nostr keypair (nsec/npub)
- Replaces client certificates
- Cryptographic authentication
- No PKI/CA required

Replacing SMS (KYC vector elimination):
- NIP-17 encrypted DMs for receipts
- No phone numbers collected
- User provides npub voluntarily
- Operator alerts via Nostr events

Event Schema:
- Kind 21001-21003: CLINK protocol
- Kind 30078: Machine status (replaceable)
- Kind 30079: Transaction records
- Kind 14: Encrypted receipt DMs

This is the logical extension of CLINK - if we're using
Nostr for payments, why not use it for everything?

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-01-22 15:36:10 -05:00
commit af358bf158
3 changed files with 838 additions and 2 deletions

View file

@ -668,8 +668,9 @@ Features: Machine pairing, balance check, settings
## Related Notes ## Related Notes
- [[nostr-native-architecture]] - **Nostr as infrastructure backbone**
- [[modernization-plan]] - Original tech stack decisions - [[modernization-plan]] - Original tech stack decisions
- [[lnbits-integration]] - Current LNbits docs (to be revised) - [[lnbits-integration]] - LNbits as alternative to Lightning.Pub
- [[membership-lightning-integration]] - Membership feature (simplify) - [[membership-lightning-integration]] - Membership feature (simplify)
- [[hardware-recommendations]] - Hardware choices - [[hardware-recommendations]] - Hardware choices
- [[machine-ui-modernization]] - Vue 3 UI migration - [[machine-ui-modernization]] - Vue 3 UI migration
@ -700,8 +701,14 @@ Features: Machine pairing, balance check, settings
- [BOLT Cards](https://bolt.cards/) - [BOLT Cards](https://bolt.cards/)
- [LNbits BoltCards Extension](https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltcards) - [LNbits BoltCards Extension](https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltcards)
### Nostr Infrastructure
- [strfry](https://github.com/hoytech/strfry) - High-performance relay
- [rnostr](https://github.com/rnostr/rnostr) - Rust relay with NIP-42
- [NIP-42: Auth](https://github.com/nostr-protocol/nips/blob/master/42.md)
- [NIP-44: Encryption](https://github.com/paulmillr/nip44)
- [NIP-17: Private DMs](https://nips.nostr.com/17)
### Reference Implementations ### Reference Implementations
- [FOSSA ATM](https://github.com/lnbits/fossa) - LNbits Lightning ATM - [FOSSA ATM](https://github.com/lnbits/fossa) - LNbits Lightning ATM
- [Bleskomat](https://github.com/samotari/bleskomat) - Minimal Lightning ATM - [Bleskomat](https://github.com/samotari/bleskomat) - Minimal Lightning ATM
- [RoboSats](https://github.com/RoboSats/robosats) - KYC-free P2P exchange - [RoboSats](https://github.com/RoboSats/robosats) - KYC-free P2P exchange
- [RoboSats](https://github.com/RoboSats/robosats) - KYC-free P2P exchange

View file

@ -514,6 +514,7 @@ test('user can complete transaction', async ({ page }) => {
## Related Notes ## Related Notes
- [[architecture-review]] - **KYC-free Lightning-first architecture review** - [[architecture-review]] - **KYC-free Lightning-first architecture review**
- [[nostr-native-architecture]] - **Nostr as infrastructure backbone**
- [[CLAUDE]] - Claude Code guidance - [[CLAUDE]] - Claude Code guidance
- [[admin-ui-modernization]] - Vue 3 migration for admin dashboard - [[admin-ui-modernization]] - Vue 3 migration for admin dashboard
- [[machine-ui-modernization]] - Vue 3 migration for kiosk UI - [[machine-ui-modernization]] - Vue 3 migration for kiosk UI

View file

@ -0,0 +1,828 @@
---
title: Nostr-Native ATM Architecture
created: 2026-01-22
updated: 2026-01-22
tags:
- architecture
- nostr
- lightning-pub
- kyc-free
- decentralized
status: active
priority: critical
---
# Nostr-Native ATM Architecture
> [!abstract] Summary
> A radical rethinking of ATM infrastructure where **Nostr becomes the backbone** for identity, communication, and payments. Replaces traditional server infrastructure with Lightning.Pub and a private Nostr relay, eliminating KYC vectors like phone numbers while enabling a truly decentralized, censorship-resistant system.
## Quick Links
- [[#Vision: Nostr as Infrastructure]]
- [[#Lightning.Pub as Core Server]]
- [[#Private Relay Architecture]]
- [[#Machine Identity]]
- [[#Replacing SMS with Nostr]]
- [[#Event Schema]]
---
## Vision: Nostr as Infrastructure
### The Problem with Traditional ATM Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ TRADITIONAL LAMASSU ARCHITECTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ATM ──HTTPS/WSS──► Server ──► PostgreSQL │
│ │ │
│ ├──► SMS Gateway (Twilio) │
│ ├──► Email Service │
│ ├──► KYC Provider │
│ └──► Lightning Node │
│ │
│ Problems: │
│ • Phone numbers = KYC vector │
│ • Complex server infrastructure │
│ • DNS, SSL, port forwarding required │
│ • Single point of failure │
│ • Centralized command/control │
│ │
└─────────────────────────────────────────────────────────────┘
```
### The Nostr-Native Solution
```
┌─────────────────────────────────────────────────────────────┐
│ NOSTR-NATIVE ATM ARCHITECTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ ATM 1 │ │ ATM 2 │ │ ATM N │ │
│ │ npub_1 │ │ npub_2 │ │ npub_n │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Private Nostr Relay │◄─── NIP-42 Auth │
│ │ (strfry / rnostr) │ Whitelist: ATMs + │
│ └───────────┬────────────┘ Operators only │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Lightning │ │ Operator │ │ Public │ │
│ │ Pub │ │Dashboard │ │ Relays │ │
│ │(LND wrap)│ │ (Vue 3) │ │(fallback)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Benefits: │
│ • No phone numbers (Nostr DMs instead) │
│ • Zero server config (no DNS/SSL/ports) │
│ • Decentralized communication │
│ • Cryptographic machine identity │
│ • Censorship-resistant │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Lightning.Pub as Core Server
> [!decision] Lightning.Pub Replaces lamassu-server
> A Nostr-native account system that wraps LND and eliminates traditional server complexity.
### Why Lightning.Pub?
| Aspect | Traditional Server | Lightning.Pub |
|--------|-------------------|---------------|
| Network config | DNS, SSL, ports, firewall | Zero (uses Nostr relays) |
| Deployment | Complex | One-line install |
| Communication | HTTPS/WebSocket | Nostr events (NIP-44 encrypted) |
| Account system | Custom implementation | Built-in sublayers |
| CLINK support | Must implement | Native |
| Lightning | Separate integration | Wraps LND directly |
### One-Line Deployment
```bash
# Linux
wget -qO- https://deploy.lightning.pub | bash
# macOS
curl -fsSL https://deploy.lightning.pub | bash
# Everything confined to ~/lightning_pub/
# No sudo, no root, no system changes
```
### Architecture
```
Lightning.Pub
├── LND (Lightning Network Daemon)
│ └── Neutrino (SPV Bitcoin)
├── Account System
│ ├── Application Pools (operator level)
│ └── User Accounts (ATM wallets)
├── CLINK Native
│ ├── noffer (static payment codes)
│ └── ndebit (authorized payments)
├── Nostr Communication
│ └── NIP-44 encrypted events
└── Optional: LNURL Bridge (legacy support)
```
### What Lightning.Pub Gives Us
1. **No Port Forwarding** - Nostr relays handle all communication
2. **Multi-User Accounts** - Each ATM gets its own account
3. **CLINK Native** - Static payment codes work out of the box
4. **Liquidity Management** - Auto-quotes from LSPs (Zeus, Voltage, Flashsats)
5. **Watchdog Security** - Monitors for drainage attacks
6. **Production Tested** - Years of real-world deployment
### Configuration for ATM Fleet
```bash
# ~/lightning_pub/.env
# Private relay for machine communication
NOSTR_RELAYS="wss://relay.youratm.company wss://nos.lol"
# Disable bootstrap peering for full sovereignty
DISABLE_LIQUIDITY_PROVIDER=true
# Custom LNURL domain (optional, for legacy wallets)
SERVICE_URL=https://ln.youratm.company
```
---
## Private Relay Architecture
> [!decision] Run a Restricted Nostr Relay
> NIP-42 authenticated relay that only accepts events from known machines and operators.
### Why a Private Relay?
| Concern | Public Relay | Private Relay |
|---------|--------------|---------------|
| Who can read | Anyone | Whitelisted npubs only |
| Who can write | Anyone | Whitelisted npubs only |
| Machine commands | Exposed | Encrypted, restricted |
| Fleet data | Public | Private |
| Censorship | Relay can censor | You control |
### Relay Options
| Relay | Language | NIP-42 | Performance | Notes |
|-------|----------|--------|-------------|-------|
| **strfry** | C++ | Yes | Excellent | Plugin system, negentropy sync |
| **rnostr** | Rust | Yes | Excellent | LMDB storage, inspired by strfry |
| **nostr-rs-relay** | Rust | Yes | Good | SQLite/PostgreSQL |
### NIP-42 Authentication
```
┌─────────────────────────────────────────────────────────────┐
│ NIP-42 AUTH FLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ ATM connects to relay │
│ │ │
│ ▼ │
│ Relay sends AUTH challenge │
│ ["AUTH", "<random-challenge>"] │
│ │ │
│ ▼ │
│ ATM signs challenge with its nsec │
│ { │
│ "kind": 22242, │
│ "tags": [ │
│ ["relay", "wss://relay.youratm.company"], │
│ ["challenge", "<random-challenge>"] │
│ ], │
│ "content": "", │
│ "sig": "<signature>" │
│ } │
│ │ │
│ ▼ │
│ Relay verifies npub is in whitelist │
│ │ │
│ ├── Yes → Connection allowed │
│ └── No → Connection rejected │
│ │
└─────────────────────────────────────────────────────────────┘
```
### strfry Configuration
```toml
# strfry.conf
[relay]
bind = "0.0.0.0"
port = 7777
realIpHeader = "X-Forwarded-For"
[relay.info]
name = "ATM Fleet Relay"
description = "Private relay for ATM communication"
contact = "operator@youratm.company"
# Require NIP-42 authentication
authRequired = true
[relay.writePolicy]
plugin = "./plugins/whitelist.js"
[relay.negentropy]
enabled = true
```
### Whitelist Plugin (noteguard style)
```javascript
// plugins/whitelist.js
const ALLOWED_PUBKEYS = new Set([
'npub1_atm_001...', // ATM 1
'npub1_atm_002...', // ATM 2
'npub1_operator...', // Operator
])
export function writePolicy(event, sourceInfo) {
if (!sourceInfo.authedPubkey) {
return { action: 'reject', message: 'auth-required: authenticate first' }
}
if (!ALLOWED_PUBKEYS.has(sourceInfo.authedPubkey)) {
return { action: 'reject', message: 'restricted: not authorized' }
}
return { action: 'accept' }
}
```
### NixOS Module for Relay
```nix
# relay.nix
{ config, pkgs, ... }:
{
services.strfry = {
enable = true;
settings = {
relay = {
bind = "127.0.0.1";
port = 7777;
info = {
name = "ATM Fleet Relay";
description = "Private NIP-42 authenticated relay";
};
authRequired = true;
};
};
};
# Nginx reverse proxy with SSL
services.nginx.virtualHosts."relay.youratm.company" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:7777";
proxyWebsockets = true;
};
};
}
```
---
## Machine Identity
> [!decision] Each ATM Has a Nostr Keypair
> Hardware-bound identity that replaces certificates and enables cryptographic authentication.
### Identity Model
```
┌─────────────────────────────────────────────────────────────┐
│ MACHINE IDENTITY │
├─────────────────────────────────────────────────────────────┤
│ │
│ Traditional: │
│ • Client certificate (complex PKI) │
│ • API keys (can be leaked) │
│ • IP-based auth (unreliable) │
│ │
│ Nostr-Native: │
│ • Machine has nsec (private key) │
│ • npub is machine identity │
│ • All events signed by machine │
│ • Operator whitelist controls access │
│ • No certificate authority needed │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Key Generation & Storage
```typescript
// Machine first boot - generate identity
import { generateSecretKey, getPublicKey } from 'nostr-tools'
import { writeFileSync } from 'fs'
function initMachineIdentity() {
const nsec = generateSecretKey()
const npub = getPublicKey(nsec)
// Store in secure location (TPM, encrypted file, etc.)
writeFileSync('/etc/lamassu/machine.nsec', nsec, { mode: 0o600 })
console.log(`Machine identity: ${npub}`)
console.log('Add this npub to operator whitelist')
return { nsec, npub }
}
```
### Secure Key Storage Options
| Method | Security | Complexity | Best For |
|--------|----------|------------|----------|
| Encrypted file | Medium | Low | Development |
| TPM 2.0 | High | Medium | Production |
| Secure enclave | Highest | High | High-security |
| HSM | Highest | Highest | Enterprise |
### Tauri Integration
```rust
// src-tauri/src/identity.rs
use nostr_sdk::prelude::*;
use std::fs;
pub struct MachineIdentity {
keys: Keys,
}
impl MachineIdentity {
pub fn load_or_create() -> Result<Self, Error> {
let nsec_path = "/etc/lamassu/machine.nsec";
let keys = if fs::metadata(nsec_path).is_ok() {
// Load existing
let nsec = fs::read_to_string(nsec_path)?;
Keys::parse(&nsec)?
} else {
// Generate new
let keys = Keys::generate();
fs::write(nsec_path, keys.secret_key()?.to_bech32()?)?;
keys
};
Ok(Self { keys })
}
pub fn npub(&self) -> String {
self.keys.public_key().to_bech32().unwrap()
}
pub fn sign_event(&self, event: UnsignedEvent) -> Result<Event, Error> {
event.sign(&self.keys)
}
}
```
---
## Replacing SMS with Nostr
> [!decision] Nostr DMs Replace Phone-Based Messaging
> No phone numbers = no KYC vector. Users provide npub for receipts.
### What SMS Was Used For (Old Lamassu)
| Use Case | Old Method | New Method |
|----------|------------|------------|
| Transaction receipt | SMS to phone | NIP-17 DM to npub |
| Verification code | SMS OTP | Not needed (no KYC) |
| Operator alerts | SMS/Email | Nostr events to operator npub |
| Customer notifications | SMS | Optional NIP-17 DM |
### NIP-17 Private Direct Messages
```typescript
// Send encrypted receipt to user
import { nip44, nip59 } from 'nostr-tools'
async function sendReceipt(
userNpub: string,
receipt: TransactionReceipt
) {
const content = JSON.stringify({
type: 'transaction_receipt',
txid: receipt.txid,
amount: receipt.amountSats,
timestamp: receipt.timestamp,
atmId: receipt.atmNpub,
})
// NIP-17: Encrypted gift-wrapped message
const sealedEvent = await nip59.seal(
machineKeys,
userNpub,
{
kind: 14, // Direct message
content,
tags: [],
}
)
// Publish to relay
await relay.publish(sealedEvent)
}
```
### User Flow (Optional Receipt)
```
┌─────────────────────────────────────────────────────────────┐
│ OPTIONAL RECEIPT FLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. User completes transaction │
│ │
│ 2. ATM asks: "Want a receipt?" │
│ [No Thanks] [Yes, via Nostr] │
│ │
│ 3. If yes, user provides npub: │
│ • Scan NFC card with npub │
│ • Scan QR code of npub │
│ • Type npub manually │
│ │
│ 4. ATM sends NIP-17 encrypted DM │
│ • Only user can decrypt │
│ • Contains: amount, txid, timestamp │
│ • No phone number collected! │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Operator Alerts via Nostr
```typescript
// Machine publishes alert event
async function sendOperatorAlert(
alertType: 'low_cash' | 'error' | 'offline',
details: object
) {
const event = {
kind: 30078, // Replaceable application-specific
pubkey: machineNpub,
content: nip44.encrypt(
machineNsec,
operatorNpub,
JSON.stringify({
type: alertType,
machineId: machineNpub,
timestamp: Date.now(),
details,
})
),
tags: [
['d', `alert:${machineNpub}`], // Replaceable identifier
['p', operatorNpub],
],
}
await relay.publish(signEvent(event, machineNsec))
}
```
---
## Event Schema
> [!tip] Custom Event Kinds for ATM Operations
> Define application-specific events for machine status, transactions, and commands.
### Event Kinds
| Kind | Type | Description |
|------|------|-------------|
| 21001 | CLINK | Offer Request/Response |
| 21002 | CLINK | Debit Request/Response |
| 21003 | CLINK | Management Delegation |
| 30078 | Replaceable | Machine Status |
| 30079 | Replaceable | Cash Levels |
| 14 | NIP-17 | Encrypted Receipt DM |
| 22242 | Ephemeral | NIP-42 Auth |
### Machine Status Event (Kind 30078)
```typescript
interface MachineStatusEvent {
kind: 30078
pubkey: string // Machine npub
content: string // NIP-44 encrypted JSON
tags: [
['d', 'status'], // Replaceable identifier
['p', string], // Operator npub
]
}
// Decrypted content:
interface MachineStatus {
online: boolean
lastTransaction: number // timestamp
cashLevels: {
validator: number // bills in validator
dispenser: CassetteLevel[]
}
errors: string[]
version: string
}
```
### Transaction Record Event
```typescript
interface TransactionEvent {
kind: 30079
pubkey: string // Machine npub
content: string // NIP-44 encrypted
tags: [
['d', `tx:${txid}`],
['p', string], // Operator npub
]
}
// Decrypted content:
interface TransactionRecord {
txid: string
type: 'cash_in' | 'cash_out'
amountFiat: number
amountSats: number
fee: number
timestamp: number
paymentMethod: 'lnurl_withdraw' | 'clink_offer' | 'invoice' | 'cashu'
// No user identity stored!
}
```
### Operator Command Event
```typescript
interface CommandEvent {
kind: 21003 // CLINK manage
pubkey: string // Operator npub
content: string // NIP-44 encrypted
tags: [
['p', string], // Target machine npub
]
}
// Decrypted content:
interface OperatorCommand {
command: 'restart' | 'update' | 'disable' | 'enable' | 'set_limits'
params?: object
timestamp: number
signature: string // Operator signs command
}
```
---
## Full Stack Architecture
### Component Diagram
```
┌─────────────────────────────────────────────────────────────────────┐
│ NOSTR-NATIVE ATM STACK │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ATM MACHINE │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │
│ │ │ Vue 3 UI │ │ XState v5 │ │ Rust HAL │ │ │
│ │ │ (Tauri) │ │ (State) │ │ (Bill/Dispense) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────┴────────────────┴─────────────────────┴──────────┐ │ │
│ │ │ Nostr Client │ │ │
│ │ │ • Machine nsec/npub identity │ │ │
│ │ │ • CLINK SDK for payments │ │ │
│ │ │ • NIP-44 encryption │ │ │
│ │ │ • Event publishing/subscription │ │ │
│ │ └────────────────────────┬───────────────────────────────┘ │ │
│ └───────────────────────────┼──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ PRIVATE NOSTR RELAY │ │
│ │ • strfry / rnostr │ │
│ │ • NIP-42 authentication required │ │
│ │ • Whitelist: ATM npubs + Operator npubs │ │
│ │ • Negentropy sync for offline reconciliation │ │
│ └────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ │
│ │ Lightning.Pub │ │ Operator │ │ Public Relays │ │
│ │ │ │ Dashboard │ │ (Fallback) │ │
│ │ • LND node │ │ │ │ │ │
│ │ • Accounts │ │ • Vue 3 app │ │ • nos.lol │ │
│ │ • CLINK native│ │ • Subscribe │ │ • relay.damus.io │ │
│ │ • Liquidity │ │ to events │ │ • For CLINK with │ │
│ └───────────────┘ │ • Send cmds │ │ external users │ │
│ └───────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### Data Flow: Cash-In Transaction
```mermaid
sequenceDiagram
participant User
participant ATM
participant Relay as Private Relay
participant LPub as Lightning.Pub
participant LN as Lightning Network
User->>ATM: Insert $50 cash
ATM->>ATM: Validate bills (HAL)
ATM->>Relay: Publish status event
ATM->>ATM: Generate CLINK offer (variable price)
ATM->>ATM: Display QR code
User->>User: Scan with ShockWallet
User->>Relay: CLINK offer request (Kind 21001)
Relay->>ATM: Forward request
ATM->>LPub: Request invoice (amount calculated)
LPub->>ATM: BOLT11 invoice
ATM->>Relay: CLINK response with invoice
Relay->>User: Forward response
User->>LN: Pay invoice
LN->>LPub: Payment received
LPub->>Relay: Payment confirmation event
Relay->>ATM: Forward confirmation
ATM->>ATM: Transaction complete
ATM->>Relay: Publish transaction record
opt User provided npub
ATM->>Relay: Send NIP-17 receipt DM
end
```
---
## Migration Path
### Phase 1: Add Nostr Layer
```
Existing Lamassu ──► Add Nostr client
Add private relay
Keep existing server (parallel)
```
### Phase 2: Lightning.Pub Integration
```
Add Lightning.Pub ──► Route payments through LPub
CLINK offers enabled
Account system active
```
### Phase 3: Full Migration
```
Remove old server ──► Nostr-only communication
NIP-17 receipts (no SMS)
Private relay primary
```
### Phase 4: Optional Enhancements
```
Advanced features ──► Cashu ecash integration
Fedimint support
Multi-relay redundancy
```
---
## Security Considerations
### Threat Model
| Threat | Mitigation |
|--------|------------|
| Relay compromise | NIP-44 encryption (relay can't read) |
| Key theft | TPM/HSM storage, key rotation |
| Replay attacks | Timestamps, nonces in events |
| Rogue operator | Multi-sig commands (future) |
| Network sniffing | WebSocket over TLS, NIP-44 |
### Key Rotation
```typescript
// Periodic key rotation for machines
async function rotateMachineKey(oldNsec: string) {
const newKeys = generateKeys()
// Publish key rotation event (signed by old key)
const rotationEvent = {
kind: 30078,
content: nip44.encrypt(oldNsec, operatorNpub, JSON.stringify({
type: 'key_rotation',
oldPubkey: getPublicKey(oldNsec),
newPubkey: newKeys.npub,
timestamp: Date.now(),
})),
tags: [
['d', 'key_rotation'],
['p', operatorNpub],
],
}
await relay.publish(signEvent(rotationEvent, oldNsec))
// Operator must update whitelist
// Then switch to new key
}
```
---
## Comparison: Old vs Nostr-Native
| Aspect | Old Lamassu | Nostr-Native |
|--------|-------------|--------------|
| Communication | HTTPS/WebSocket | Nostr events |
| Authentication | Client certs | NIP-42 + npub whitelist |
| Encryption | TLS | NIP-44 (content-level) |
| Identity | PKI certificates | Nostr keypairs |
| Receipts | SMS (phone = KYC) | NIP-17 DMs (npub) |
| Alerts | Email/SMS | Nostr events |
| Server config | DNS, SSL, ports | Zero config |
| Deployment | Complex | One-line |
| Censorship | Server can be seized | Relay-agnostic |
| Privacy | Phone numbers leaked | Pseudonymous npubs |
---
## Open Questions
1. **Relay redundancy** - Should machines connect to multiple relays?
2. **Offline operation** - How long can machine operate without relay?
3. **Key escrow** - How to recover if machine key is lost?
4. **Multi-operator** - Can multiple operators share a fleet?
5. **Cashu over Nostr** - Use Nostr for ecash token delivery?
---
## Related Notes
- [[architecture-review]] - Overall KYC-free architecture
- [[lnbits-integration]] - LNbits as alternative backend
- [[hardware-recommendations]] - Hardware choices
- [[machine-ui-modernization]] - Vue 3 UI migration
---
## References
### Lightning.Pub
- [Lightning.Pub GitHub](https://github.com/shocknet/Lightning.Pub)
- [ShockWallet](https://github.com/shocknet/wallet2)
- [CLINK Protocol](https://github.com/shocknet/CLINK)
### Nostr Relays
- [strfry](https://github.com/hoytech/strfry)
- [rnostr](https://github.com/rnostr/rnostr)
- [nostr-rs-relay](https://sr.ht/~gheartsfield/nostr-rs-relay/)
- [noteguard](https://github.com/damus-io/noteguard) - strfry plugin system
### NIPs
- [NIP-42: Authentication](https://github.com/nostr-protocol/nips/blob/master/42.md)
- [NIP-44: Versioned Encryption](https://github.com/paulmillr/nip44)
- [NIP-17: Private Direct Messages](https://nips.nostr.com/17)
- [NIP-59: Gift Wraps](https://github.com/nostr-protocol/nips/blob/master/59.md)