46 KiB
Amber Integration Plan: End-User Experience
⚠️ Design-only research document. This describes a planned user experience, not a shipped feature. The current system is a research prototype; it links PQ keys to an identity and anchors that link in Bitcoin, but does not by itself make the identity post-quantum secure.
Overview
This document describes the complete end-to-end user experience for migrating a Nostr identity to post-quantum security using the PQ web app and Amber signer. There are two paths depending on how the user's account was created in Amber:
- Path A (Simple): User logged into Amber with a seed phrase (mnemonic). Amber already has the seed. PQ keys are derived from that same seed. No second account needed.
- Path B (Migration): User logged into Amber with a raw nsec. No seed phrase exists. A new seed must be generated and linked to the old identity via a two-account migration event.
The web app automatically detects which path applies by asking Amber for the account's seed words (or checking if they're empty).
Critical Fact: Amber Does Not Tie Raw nsec to Seed Phrases
When a user enters a raw nsec in Amber, the seed words field is set to null (AccountStateViewModel.kt):
LocalPreferences.updatePrefsForLogin(
Amber.instance, account,
keyPair.pubKey.toHexKey(),
keyPair.privKey!!.toHexKey(),
null // ← seedWords is null for raw nsec login
)
| Login method | nsec stored | Seed words stored | Relationship |
|---|---|---|---|
Raw nsec (nsec1...) |
✅ | null (empty) |
None — nsec is random, no seed |
| Mnemonic (12/24 words) | ✅ (derived from seed via NIP-06) | ✅ | nsec IS derived from seed |
A raw nsec is 32 random bytes. You cannot derive a seed phrase from it (preimage problem — impossible). You cannot derive the existing nsec from a new seed (different key). The only bridge between them is a signed migration event.
Prerequisites
- Amber installed on Android phone (from F-Droid, GitHub Releases, or ZapStore)
- Existing Nostr account already in Amber
- Desktop or mobile browser to access the PQ web app
Phase 1: Connect to the PQ Web App (Both Paths)
Screen 1: Landing Page
┌─────────────────────────────────────────────────────┐
│ │
│ 🔒 Post-Quantum Nostr Migration │
│ │
│ Make your Nostr identity quantum-resistant. │
│ Your followers stay. Your npub doesn't change. │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Connect with Amber │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Connect with browser extension │ │
│ └─────────────────────────────────────┘ │
│ │
│ How it works | FAQ | Source code │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Connect with Amber"
Behind the scenes:
- Web app generates a random NIP-46 connection keypair
- Web app displays a
nostrconnect://URI or QR code - Web app opens a WebSocket to a relay, listening for Amber's response
Screen 2: Connect to Amber
┌─────────────────────────────────────────────────────┐
│ │
│ Connect Amber │
│ │
│ Scan this QR code with Amber, or open this │
│ link on your phone: │
│ │
│ ┌───────────────────┐ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ └───────────────────┘ │
│ │
│ nostrconnect://... │
│ │
│ Waiting for Amber... │
│ ●●○○○ │
│ │
└─────────────────────────────────────────────────────┘
User action: Opens the link on their phone (or scans QR code)
On Amber (Phone): Connection Request
┌─────────────────────────────────────────────────────┐
│ │
│ pq-nostr.app wants to connect │
│ │
│ This app is requesting to: │
│ • Read your public key │
│ • Sign events │
│ • Encrypt and decrypt messages │
│ │
│ Account: npub1abc... (Account #1) │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Approve │ │ Reject │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Approve"
Behind the scenes:
- Amber sends a NIP-46
connectresponse to the web app via the relay - Web app now has a NIP-46 session with Amber
- Web app requests
get_public_key→ receives the user's npub
Screen 3: Account Detected — Path Selection
The web app asks Amber if the account has seed words. This determines which path to show.
Behind the scenes:
- Web app requests seed words from Amber (via a new NIP-46 method, or by asking the user to check Amber's backup screen)
- If seed words exist → Path A (Simple)
- If seed words are empty → Path B (Migration)
Path A: Simple Path (User Has Seed Phrase in Amber)
This path applies to users who logged into Amber with a 12/24-word mnemonic. Amber already stores their seed phrase. PQ keys are derived from that same seed. No second account needed.
Screen A1: Seed Phrase Retrieval
┌─────────────────────────────────────────────────────┐
│ │
│ ✅ Connected to Amber │
│ │
│ Your identity: npub1abc...xyz │
│ Account name: Alice │
│ Seed phrase: ✅ Found in Amber │
│ │
│ Great! Your account already has a seed phrase. │
│ We can derive quantum-resistant keys from it │
│ without creating a new account. │
│ │
│ To proceed, we need your seed phrase to │
│ derive the post-quantum keys in your browser. │
│ The seed phrase will NOT be sent to any server. │
│ │
│ Option 1: Export from Amber │
│ Open Amber → Account → Backup → Show Seed Words │
│ Then type or paste them below. │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Enter your 12/24 seed words here... │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Continue │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Opens Amber's backup screen, copies seed words, pastes into web app
Behind the scenes:
- Seed phrase is handled entirely client-side (WASM), never transmitted
- Web app validates the mnemonic (BIP39 checksum)
- Web app verifies the derived secp256k1 pubkey matches the connected Amber account
Screen A2: Generating Quantum Keys
┌─────────────────────────────────────────────────────┐
│ │
│ Generating Quantum Keys... │
│ │
│ Deriving post-quantum keys from your seed... │
│ │
│ ✅ ML-DSA-65 (Dilithium) signature key │
│ Public key: 1952 bytes │
│ │
│ ✅ SLH-DSA-128s (SPHINCS+) signature key │
│ Public key: 32 bytes │
│ │
│ ✅ ML-KEM-768 (Kyber) encryption key │
│ Public key: 1184 bytes │
│ │
│ All keys derived from your seed phrase. │
│ Same seed → same keys, every time. │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Continue │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app uses liboqs-wasm to generate PQ keypairs deterministically from the BIP39 seed
- PQ private keys exist only in browser memory (WASM), never sent anywhere
Screen A3: Signing the NIP-QR Event
┌─────────────────────────────────────────────────────┐
│ │
│ Signing the Migration Event │
│ │
│ PQ signatures (done in browser): │
│ ✅ ML-DSA-65 signature │
│ ✅ SLH-DSA-128s signature │
│ │
│ Amber signature (need your approval): │
│ ⏳ Signing the NIP-QR event... │
│ │
│ Please approve the signing request in Amber. │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app constructs the link statement:
"Identity <npub> is linked to the following PQ keys, all derived from the same BIP39 seed. This link is established pre-quantum." - Web app PQ-signs the statement with each PQ private key (client-side WASM)
- Web app sends a
signEventrequest to Amber to sign the NIP-QR event
On Amber (Phone): Sign NIP-QR Event
┌─────────────────────────────────────────────────────┐
│ │
│ pq-nostr.app wants to sign │
│ │
│ Account: npub1abc...xyz │
│ │
│ Event kind: <NIP-QR kind> │
│ │
│ This is your quantum migration event. │
│ It links your identity to quantum-resistant │
│ keys derived from your seed phrase. │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Approve │ │ Reject │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Approve"
Behind the scenes:
- Amber signs the NIP-QR event with the secp256k1 private key
- Since the secp256k1 key and PQ keys are all derived from the same seed, this single signature links them all
- No second account needed — the existing account's signature is sufficient
Screen A4: Publishing → Go to Phase 5
The web app publishes the event and requests OpenTimestamps (see Phase 5 below).
Path B: Migration Path (User Has Raw nsec, No Seed Phrase)
This path applies to users who logged into Amber with a raw nsec. No seed phrase exists. A new seed must be generated and linked to the old identity via a two-account migration.
Screen B1: Migration Required
┌─────────────────────────────────────────────────────┐
│ │
│ ✅ Connected to Amber │
│ │
│ Your identity: npub1abc...xyz │
│ Account name: Alice │
│ Seed phrase: ❌ Not found │
│ │
│ Your account was created with a raw private key │
│ (nsec), not a seed phrase. To make your identity │
│ quantum-resistant, we need to: │
│ │
│ 1. Generate a new seed phrase (your quantum-safe │
│ backup) │
│ 2. Add it to Amber as a second account │
│ 3. Link your old identity to the new account │
│ and its quantum keys │
│ │
│ Your npub won't change. Your followers stay. │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Start Migration │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Start Migration"
Screen B2: Generate Seed Phrase
┌─────────────────────────────────────────────────────┐
│ │
│ Step 1 of 4: Your Quantum-Safe Backup │
│ │
│ We're generating a new seed phrase. This will │
│ become your quantum-safe root of trust. │
│ Write it down on paper. Never store it │
│ digitally. Never share it with anyone. │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. apple 7. river │ │
│ │ 2. garden 8. bridge │ │
│ │ 3. forest 9. mountain │ │
│ │ 4. ocean 10. sunset │ │
│ │ 5. desert 11. valley │ │
│ │ 6. meadow 12. horizon │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 📋 Copy 🖨️ Print │
│ │
│ ☐ I have written down my seed phrase │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Continue │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app generates 128 bits of random entropy (crypto.getRandomValues)
- Web app converts entropy to 12-word BIP39 mnemonic (client-side, WASM)
- Seed phrase exists only in browser memory — never sent to any server
- Web app derives the BIP39 seed:
PBKDF2-HMAC-SHA512(mnemonic, "mnemonic", 2048) - Web app derives Account #2's secp256k1 private key via NIP-06:
m/44'/1237'/0'/0/0
User action: Writes down seed phrase, checks the box, taps "Continue"
Screen B3: Verify Seed Phrase
┌─────────────────────────────────────────────────────┐
│ │
│ Step 1 of 4: Verify Your Backup │
│ │
│ Type word #3, #7, and #11 to confirm you │
│ wrote it down correctly. │
│ │
│ Word #3: [ forest ] │
│ Word #7: [ river ] │
│ Word #11: [ valley ] │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Verify │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Types the three words, taps "Verify"
Screen B4: Import Seed into Amber
┌─────────────────────────────────────────────────────┐
│ │
│ Step 2 of 4: Add to Amber │
│ │
│ You need to add this seed phrase to Amber as │
│ a new account. This creates Account #2 — │
│ your quantum-safe signing identity. │
│ │
│ On your phone, in Amber: │
│ │
│ 1. Tap the account switcher (top left) │
│ 2. Tap "+ Add Account" │
│ 3. Select "Recovery Phrase" │
│ 4. Enter your 12 words │
│ 5. Tap "Login" │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ Account #2 npub: │ │
│ │ npub1def...uvw │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ☐ I've added this account to Amber │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Continue │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app displays Account #2's npub (derived from the seed via NIP-06)
- User needs to manually import the seed into Amber as a new account
On Amber (Phone): Add Account
┌─────────────────────────────────────────────────────┐
│ │
│ Add Account │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Private Key │ │ Recovery │ │
│ │ │ │ Phrase │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Recovery Phrase", enters 12 words, taps "Login"
Behind the scenes:
- Amber derives secp256k1 key from mnemonic via NIP-06
- Amber stores the nsec AND seed words in encrypted storage
- Amber switches to Account #2
Screen B5: Connect Account #2 to Web App
┌─────────────────────────────────────────────────────┐
│ │
│ Step 2 of 4: Connect Account #2 │
│ │
│ We need Amber to sign with Account #2 as well. │
│ │
│ In Amber, switch to Account #2 (npub1def...uvw), │
│ then approve the connection request. │
│ │
│ ┌───────────────────┐ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │
│ └───────────────────┘ │
│ │
│ nostrconnect://... │
│ │
│ Waiting for Amber (Account #2)... │
│ ●●○○○ │
│ │
└─────────────────────────────────────────────────────┘
User action: Switches to Account #2 in Amber, approves connection
Behind the scenes:
- Web app opens a second NIP-46 session for Account #2
- Web app now has signing access to both Account #1 and Account #2 via Amber
Screen B6: Generating Quantum Keys
┌─────────────────────────────────────────────────────┐
│ │
│ Step 3 of 4: Generating Quantum Keys │
│ │
│ Deriving post-quantum keys from your seed... │
│ │
│ ✅ ML-DSA-65 (Dilithium) signature key │
│ Public key: 1952 bytes │
│ │
│ ✅ SLH-DSA-128s (SPHINCS+) signature key │
│ Public key: 32 bytes │
│ │
│ ✅ ML-KEM-768 (Kyber) encryption key │
│ Public key: 1184 bytes │
│ │
│ All keys derived from your seed phrase. │
│ Same seed → same keys, every time. │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Continue │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app uses liboqs-wasm to generate PQ keypairs deterministically from the BIP39 seed
- PQ private keys exist only in browser memory (WASM), never sent anywhere
Screen B7: Signing — Account #2 Signs the PQ Key Statement
┌─────────────────────────────────────────────────────┐
│ │
│ Step 3 of 4: Signing │
│ │
│ PQ signatures (done in browser): │
│ ✅ ML-DSA-65 signature │
│ ✅ SLH-DSA-128s signature │
│ │
│ Amber signatures (need your approval): │
│ ⏳ Account #2 signs the PQ key statement... │
│ │
│ Please approve the signing request in Amber. │
│ (Make sure Account #2 is active in Amber) │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app constructs the link statement:
"Identity <npub1> is migrating to successor <npub2>. All PQ keys listed below are derived from the same BIP39 seed as <npub2>. This link is established pre-quantum." - Web app PQ-signs the statement with ML-DSA-65 and SLH-DSA-128s private keys (client-side WASM)
- Web app sends a
signEventrequest to Amber (Account #2) to sign the statement
On Amber (Phone): Sign with Account #2
┌─────────────────────────────────────────────────────┐
│ │
│ pq-nostr.app wants to sign │
│ │
│ Account: npub1def...uvw (Account #2) │
│ │
│ Content: │
│ "Identity npub1abc... is migrating to │
│ successor npub1def... All PQ keys..." │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Approve │ │ Reject │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Approve"
Screen B8: Signing — Account #1 Signs the Migration Event
┌─────────────────────────────────────────────────────┐
│ │
│ Step 3 of 4: Signing │
│ │
│ ✅ ML-DSA-65 signature (browser) │
│ ✅ SLH-DSA-128s signature (browser) │
│ ✅ Account #2 signature (Amber) │
│ │
│ ⏳ Account #1 signs the migration event... │
│ │
│ Please switch to Account #1 in Amber and │
│ approve the signing request. │
│ │
└─────────────────────────────────────────────────────┘
User action: Switches to Account #1 in Amber
On Amber (Phone): Sign with Account #1
┌─────────────────────────────────────────────────────┐
│ │
│ pq-nostr.app wants to sign │
│ │
│ Account: npub1abc...xyz (Account #1) │
│ │
│ Event kind: <NIP-QR kind> │
│ │
│ This is your quantum migration event. │
│ It links your current identity to │
│ quantum-resistant keys. │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Approve │ │ Reject │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Taps "Approve"
Behind the scenes:
- Amber signs the NIP-QR event with Account #1's secp256k1 private key (the old nsec)
- This is the critical signature — the old identity authorizing the migration
- The event is now fully signed: Account #1's sig + Account #2's sig + PQ sigs
Phase 5: Publish and Timestamp (Both Paths)
Screen 9: Publishing
┌─────────────────────────────────────────────────────┐
│ │
│ Step 4 of 4: Publishing │
│ │
│ Publishing migration event to relays... │
│ │
│ ✅ wss://relay.damus.io │
│ ✅ wss://nos.lol │
│ ✅ wss://relay.primal.net │
│ │
│ Requesting OpenTimestamps attestation... │
│ ✅ Anchored to Bitcoin block 890,123 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ View Migration Event │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Behind the scenes:
- Web app assembles the complete NIP-QR event with all signatures
- Web app publishes the event to multiple relays via WebSocket
- Web app submits the event hash to OpenTimestamps (NIP-03)
- OpenTimestamps anchors the event to a Bitcoin block, proving it existed at this time
- This prevents a future quantum attacker from backdating a fraudulent migration event
Screen 10: Success
┌─────────────────────────────────────────────────────┐
│ │
│ 🎉 Migration Complete! │
│ │
│ Your Nostr identity is now quantum-resistant. │
│ │
│ What happened: │
│ • Your identity (npub1abc...xyz) is now linked │
│ to post-quantum keys │
│ • The link is timestamped on the Bitcoin │
│ blockchain (cannot be forged retroactively) │
│ • Your followers, NIP-05, and social graph │
│ are unchanged │
│ │
│ What to do now: │
│ • Keep your seed phrase safe — it's your │
│ quantum-safe backup │
│ • No other action needed — PQ-aware clients │
│ will recognize your new keys automatically │
│ │
│ Event ID: <hex event id> │
│ OTS proof: <download .ots file> │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Done │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
User action: Downloads the .ots file (optional backup), taps "Done"
Comparison: Path A vs Path B
| Aspect | Path A (Has Seed) | Path B (Raw nsec) |
|---|---|---|
| Amber accounts needed | 1 (existing) | 2 (existing + new) |
| Seed phrase generation | Not needed (already have one) | Generated by web app |
| Amber import step | Not needed | Manual import of seed as Account #2 |
| NIP-46 sessions | 1 | 2 (one per account) |
| Amber signing requests | 1 (sign NIP-QR event) | 2 (Account #2 signs statement, Account #1 signs event) |
| NIP-QR event structure | Single-key link (secp256k1 + PQ keys from same seed) | Two-key migration (old nsec → new seed-derived key → PQ keys) |
| Time required | ~2 minutes | ~5 minutes |
| User complexity | Low | Medium |
NIP-QR Event Structure Differences
Path A (Simple) — single identity, seed-derived:
{
"kind": <NIP-QR kind>,
"pubkey": "<secp256k1 pubkey (seed-derived)>",
"content": {
"statement": "Identity <npub> is linked to the following PQ keys, all derived from the same BIP39 seed. This link is established pre-quantum.",
"pq_keys": [
{ "algorithm": "ml-dsa-65", "public_key": "...", "signature": "..." },
{ "algorithm": "slh-dsa-128s", "public_key": "...", "signature": "..." },
{ "algorithm": "ml-kem-768", "public_key": "..." }
]
},
"sig": "<secp256k1 Schnorr signature over event id>"
}
Path B (Migration) — two identities, old + new:
{
"kind": <NIP-QR kind>,
"pubkey": "<Account #1 secp256k1 pubkey (old nsec)>",
"tags": [
["successor", "<Account #2 secp256k1 pubkey (seed-derived)>"]
],
"content": {
"statement": "Identity <npub1> is migrating to successor <npub2>. All PQ keys listed below are derived from the same BIP39 seed as <npub2>. This link is established pre-quantum.",
"successor_pubkey": "<Account #2 pubkey>",
"successor_signature": "<Account #2 Schnorr signature over statement+pq_keys>",
"pq_keys": [
{ "algorithm": "ml-dsa-65", "public_key": "...", "signature": "..." },
{ "algorithm": "slh-dsa-128s", "public_key": "...", "signature": "..." },
{ "algorithm": "ml-kem-768", "public_key": "..." }
]
},
"sig": "<Account #1 Schnorr signature over event id>"
}
The key difference: Path B includes a successor tag and successor_signature in the content, because the old identity (Account #1) is different from the seed-derived identity (Account #2). Path A doesn't need this because the identity IS the seed-derived key.
Post-Migration: What Changes for the User
Immediately (nothing changes)
- Their npub is the same
- Their followers, follows, NIP-05, profile — all unchanged
- Existing clients work exactly as before
- They can still sign events with their existing nsec
When PQ-aware clients adopt the NIP
- PQ-aware clients find the NIP-QR event for their npub
- Path A: clients see secp256k1 key + PQ keys, all from the same seed
- Path B: clients see Account #1 → Account #2 (successor) → PQ keys
- Clients can use their PQ encryption key (ML-KEM-768) for PQ-secure messaging
- Clients can verify PQ signatures on future events
If a quantum computer breaks secp256k1
- An attacker can forge secp256k1 signatures
- But they CANNOT:
- Forge PQ signatures (ML-DSA, SLH-DSA remain secure)
- Backdate a fraudulent NIP-QR event (OTS proof anchors the real one)
- Decrypt PQ-encrypted messages (ML-KEM remains secure)
- The user's identity is preserved through the OTS-anchored migration event
- For Path B: the user continues signing with Account #2 and PQ keys
- For Path A: the user continues signing with PQ keys (secp256k1 is broken but PQ keys are safe)
Error Handling and Edge Cases
User loses seed phrase
- Path A: PQ keys cannot be re-derived. The NIP-QR event is already published and timestamped — the link still exists. User should re-run migration with a new seed phrase (publishes an updated NIP-QR event).
- Path B: Account #2's nsec is still in Amber (can be exported via NIP-49 encrypted backup). But PQ keys cannot be re-derived without the seed. Same recovery: publish a new NIP-QR event with a new seed.
User loses phone (Amber)
- If seed phrase was written down: import into new Amber installation, re-derive PQ keys
- If seed phrase was lost too: PQ keys are unrecoverable, but the NIP-QR event still proves the link existed. User publishes a new NIP-QR event with a new seed.
User doesn't have Amber
- Fall back to NIP-07 browser extension (nos2x, etc.) for secp256k1 signing
- Seed phrase is generated and handled entirely in the browser
- Less secure (seed in browser) but still functional
- Recommend installing Amber for better security
User already has a seed phrase in Amber (Path A) but wants to use a different seed
- They can choose to generate a new seed (switching to Path B flow)
- This might be desirable if they want a dedicated PQ-only seed phrase
Technical Summary
| Component | Where it runs | What it does |
|---|---|---|
| Seed generation | Browser (WASM) | BIP39 mnemonic generation (Path B only) |
| Seed retrieval | Amber → user → browser | Manual export from Amber's backup screen |
| NIP-06 key derivation | Browser (WASM) | secp256k1 key from seed (verify it matches Amber account) |
| PQ key derivation | Browser (WASM, liboqs) | ML-DSA, SLH-DSA, ML-KEM from seed |
| PQ signing | Browser (WASM, liboqs) | Sign link statement with PQ keys |
| secp256k1 signing | Amber (Android) | Sign NIP-QR event (and successor statement for Path B) |
| Event assembly | Browser | Combine all signatures into NIP-QR event |
| Relay publishing | Browser (WebSocket) | Publish event to Nostr relays |
| OpenTimestamps | Browser → OTS API | Anchor event to Bitcoin blockchain |
| Seed phrase storage | Amber (Android encrypted storage) | BIP39 mnemonic stored encrypted |
| nsec storage | Amber (Android encrypted storage) | Account private key(s) stored encrypted |
No server-side secrets
The web app is a static site — HTML, JS, and WASM files served from a CDN or GitHub Pages. There is no backend that handles keys. The only server interactions are:
- Nostr relays (publishing the event)
- OpenTimestamps API (requesting timestamp proof)
- NIP-46 relay (communicating with Amber)
All cryptographic operations happen either in the browser (WASM) or in Amber (Android). No private key ever touches a server.