Initial prototype: passkey PRF → HKDF → Nostr key
Full pipeline proving a WebAuthn PRF assertion can anchor a stable Nostr keypair. Core derivation in nostr_passkey/derivation.py (pure, unit-tested), WebAuthn ceremony glue in webauthn_flow.py, FastAPI surface in app.py, single-page WebAuthn client in web/index.html, and an end-to-end simulation in scripts/demo.py for running without a real authenticator. Verified working against Firefox 149 + macOS Touch ID over HTTPS on https://localhost:8000 with a self-signed loopback cert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
54
scripts/demo.py
Normal file
54
scripts/demo.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""End-to-end simulation of the passkey -> Nostr key derivation flow.
|
||||
|
||||
Without a real WebAuthn authenticator and browser handy, this script
|
||||
stubs the 32-byte PRF output with a deterministic hash so you can watch
|
||||
the rest of the pipeline work: HKDF stretches the entropy, the result
|
||||
becomes a Nostr private key, and nsec/npub fall out the other side.
|
||||
|
||||
Run it with:
|
||||
python -m scripts.demo
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
from nostr_passkey.derivation import derive_nostr_key
|
||||
|
||||
|
||||
def fake_prf_output(seed: str) -> bytes:
|
||||
"""Stand-in for ``clientExtensionResults.prf.results.first``."""
|
||||
return hashlib.sha256(seed.encode("utf-8")).digest()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
prf = fake_prf_output("pretend-passkey-credential")
|
||||
print(f"Simulated PRF output (hex): {prf.hex()}")
|
||||
|
||||
ident_none = derive_nostr_key(prf)
|
||||
print("\n-- No salt --")
|
||||
print(f" privkey_hex: {ident_none.privkey_hex}")
|
||||
print(f" nsec: {ident_none.nsec}")
|
||||
print(f" npub: {ident_none.npub}")
|
||||
|
||||
ident_work = derive_nostr_key(prf, salt="work")
|
||||
print("\n-- salt='work' --")
|
||||
print(f" nsec: {ident_work.nsec}")
|
||||
print(f" npub: {ident_work.npub}")
|
||||
|
||||
ident_personal = derive_nostr_key(prf, salt="personal")
|
||||
print("\n-- salt='personal' --")
|
||||
print(f" nsec: {ident_personal.nsec}")
|
||||
print(f" npub: {ident_personal.npub}")
|
||||
|
||||
# Invariants the whole design rests on:
|
||||
ident_work_again = derive_nostr_key(prf, salt="work")
|
||||
assert ident_work.nsec == ident_work_again.nsec, "derivation must be deterministic"
|
||||
assert ident_work.nsec != ident_none.nsec, "salt must change the output"
|
||||
assert ident_work.nsec != ident_personal.nsec, "different salts must diverge"
|
||||
|
||||
print("\nDeterminism and salt separation verified.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user