Kihagyás

Heartwood: hardveres Nostr aláíró ESP32-n

Projekt: forgesworn/heartwood-esp32 Forrás: https://github.com/forgesworn/heartwood-esp32 (SECURITY-MODEL.md, README.md, docs/nip-nonce-commitment.md) Weboldal: https://heartwood.forgesworn.dev/ Licenc: MIT · Nyelv: Rust (a repó ~90%-a) Verzió: v0.16.0 (stabil, 2026-08-14) · 0.18.0-beta.19 (2026-09-25) — a landing page még v0.15.0-t ír Aktivitás: napi commitok 2026-04 óta; a legutóbbi release-ig 204 PR Státusz: beta, önálló fejlesztői projekt (★1 a repón)

Mi ez

Nostr hardveres aláíró (NIP-46 remote signer), ami egy ESP32 dev boardon fut. A master seed a chip hardveres entrópiájából születik, egyszer megjelenik az OLED-en, és semmilyen kódúton nem hagyja el a device-ot: nem USB-n, nem relayen, nem backupban. A NIP-44 visszafejtés, a policy-kiértékelés és maga a BIP-340 aláírás is a chipen történik. A gép, amibe dugod, csak ciphertextet lát.

A modell lényege a kétfokozatú engedélyezés: egy előre telepített policy (pontos metódus- és event-kind allowlist) dönti el, mi mehet aláírás nélkül, és egy fizikai gomb minden olyat, amit az allowlist nem fed le. A policy fail-closed: amin kívül esik, azt elutasítja, és utólag semmilyen gomblenyomás nem eszkalálhatja.

Hardver

Négy board, egy Rust firmware, board a fordításkor választva. A szignált update board-id-hez kötött, ezért a device elutasítja a más boardra épített firmware-t.

Board Chip Kijelző Megjegyzés
Heltec WiFi LoRa 32 V3 ESP32-S3 128×64 OLED a „munkás” — olcsó, mindenhol kapható
Heltec WiFi LoRa 32 V4 ESP32-S3 128×64 OLED natív USB, default build target; L76K GNSS
LilyGO/TTGO/TENSTAR T-Display classic ESP32 színes TFT két gomb — recovery phrase beírása sokkal kevésbé szenvedés
Waveshare ESP32-C6 RISC-V 1.47″ színes LCD legkisebb asztali lábnyom

A LoRa (SX1262) soha nincs inicializálva — nincs használati eset aláíráshoz. BLE beépítve, de a portable mód még roadmap (nem built).

Üzemmódok

  • Home HSM — USB-n Raspberry Pi-re dugva (shipped, high-assurance default). A Pi futtatja a heartwood-bridge-et: az intézi a hálózatot (Nostr relay-ek, NIP-46 transport), az ESP32 az összes kriptográfiát. A kompromittált Pi nem tud master kulcsot kinyerni és nem tud slot-policyt szélesíteni. Ebben a módban minden rádió ki van kapcsolva.
  • WiFi-standalone — on-chip relay kliens (shipped, opt-in). A chip maga lép be WiFi-re és kapcsolódik a relay-ekhez, teljes NIP-46 loop a chipen (firmware/src/relay.rs), Pi nélkül. Ez a kényelmi szint, és nagyobb támadási felület: élő TCP/IP stack egy kulcsot tartó device-on. Az unbound relay peer nem tud belépni a 30 másodperces gomb-loopba — idegen nem tudja lekötni a shelf signert promptokkal. Csak akkor indul, ha a device SSID + relay listával van provisionálva; az USB kábel párhuzamosan használható marad, így egy rossz SSID vagy relay kábelen keresztül helyreállítható.
  • Portable signer — BLE, telefonra (roadmap, nem built). A home HSM-ből derivált gyerek kulcsot tart (purpose="device/mobile"). Ha elveszik, azt az egy ágat égeted el a HSM-en és a következő indexen újat deriválsz — a root és a többi ág érintetlen.

Kulcshierarchia (nsec-tree)

Master secret (home HSM)
├── persona/social       — publikus Nostr identitás
├── persona/forgesworn   — projekt identitás
├── client/bray          — NIP-46 kliens kulcs
├── device/mobile-0      — portable signer #0 ← gyerek kulcs él itt
├── device/mobile-1      — csere, ha #0 kompromittálódott
└── ...

Az nsec-tree determinisztikus Nostr al-identitás-derivációt ad: egy master secretből korlátlan identitás, és minden device saját ágat kap. Egy gyerek kompromittálódása sosem fenyegeti a rootot vagy a testvéreket. Egy device-on legfeljebb 8 master identity, három módban (bunker / tree-mnemonic / tree-nsec).

Threat-modell — mi ellen véd, és mi ellen nem

Ez a rész a projekt legőszintébb dokumentuma (SECURITY-MODEL.md, 58 KB).

Ami ellen VÉD

  • Távoli / hálózati támadó — resisted. Remote management operátor-hitelesített, egyszer használatos mutációs challenge-dzsel, ami tartósan rotálódik a dispatch előtt; a RAM request-id halmaz a többszörös kézbesítést kezeli élő relay-eken. Az sign_event policy-gated, authority fizikai jelenlétből vagy explicit operátor-policy-ból.
  • Kompromittált operátor kulcs vagy böngésző — bounded, recoverable. A v2 kliensek atomi, szigorú metódus + event-kind plafont kapnak.
  • Elveszett/ellopott unlock telefon — bounded; Sapwood-ból revokálható.
  • Rosszindulatú firmware — signált USB OTA. Két dolgot ellenőriz: a kép SHA-256-ját (integritás) és egy ed25519 release aláírást (autentikusság). A publikus kulcs a firmware-be égetve (ota-release-pubkey.hex → release_key.rs), az aláírást a CI készíti (release.yml), és a device kétszer ellenőrzi: OTA_BEGIN-nél (aláíratlan képet visszautasít, mielőtt a tulajdonostól kérdeznének) és OTA_FINISH-nél a flash-be ténylegesen írt bájtokból újraszámolt digesten. Az aláírt üzenet board-id-vel domain-separated, így egy board képe nem replayelhető másikra. Remote OTA nincs implementálva — egy távoli manager nem tudja az op_mgmt birtoklását kód-végrehajtássá alakítani. OTA ráadásul USB-only és 2 másodperces fizikai nyomást igényel.

Ami ellen NEM véd (a kritikus rész)

Fizikai hozzáférés — csak akkor véd, ha a device „sealed at rest”. A default konfigurációban flash encryption, NVS encryption és secure boot mind kikapcsolva (sdkconfig.defaults):

  • A master seed plaintextben van az NVS-ben — hacsak a PIN vagy a vault-key seal nincs bekapcsolva, amely esetben a flash csak ciphertextet ad. Egy „unsealed” device és egy USB kábel elég: esptool.py read_flash kihozza a 32 bájtos seedet.
  • A WiFi jelszó és a network tranzakció-jelölt plaintextben van az NVS-ben. A NIP-44 a távoli szállítást védi, nem az ESP32-n való tárolást.
  • Secure boot nincs → tetszőleges firmware flashelhető a ROM bootloaderen, megkerülve a gombos OTA-jóváhagyást teljesen.
  • A boot PIN csak az alkalmazás frame-loopját zárja, a ROM bootloadert vagy a nyers flash-olvasást nem — a seedet nyugalmi állapotban nem védi.

Tehát unsealed device esetén a fizikai birtoklás = az összes rajta lévő kulcs teljes kompromittálódása. Shelf/server signernél fizikai biztonság mögött ez elfogadható lehet; egyébként be kell kapcsolni a PIN-t vagy a vault key-t.

Döntés: az eFuse-alapú hardening kimarad (out of scope). Az ESP32-S3 tudná kriptográfiailag zárni a rést (CONFIG_SECURE_BOOT=y, CONFIG_FLASH_ENCRYPTION_ENABLED=y, CONFIG_NVS_ENCRYPTION=y). Ezt tudatosan elvetették: az eFuse-ok égetése irreverzibilis, valós brick-kockázatot hordoz, és bonyolítaná a flash/OTA/recovery workflow-t. A fizikai-access rés így elfogadott korlát, operacionálisan mérsékelve (legyen nálad a device; az elveszett device-t tekintsd kompromittált kulcsnak és forgasd újra egy új identitás flashelésével).

Az eFuse-mentes megoldás: PIN-ből derivált seed-titkosítás (P5, BUILT, opt-in)

Az egyetlen hardening-kar, ami nem nyúl eFuse-hoz. PIN beállításakor minden master seed ciphertextként tárolódik: PBKDF2-HMAC-SHA256(pin, salt) deriválja a kulcsot, ChaCha20 + HMAC-SHA256 encrypt-then-MAC titkosítja (common/src/seed_cipher.rs), a plaintext kulcs törlődik.

A törlés azonban nem felülírás. Amíg a flash szektor nincs újrahasznosítva, egy nyers esptool read_flash még mindig kihozhatja a plaintext seedet a sealing előtti állapotból. Az élő rekord ciphertext — de a szektor-törmelék nem.

Nincs tárolt PIN-hash: egy gyors hash lehetővé tenné a flash-dump támadónak a PIN olcsó brute-force-át és a lassú KDF megkerülését, ezért az AEAD tag az egyetlen PIN-ellenőrzés, így minden tipplete megfizeti a PBKDF2 költséget. 5 hibás próbálkozás törli és ellenőrzi a flash-time config forrást és a teljes NVS partíciót, hogy a régi WiFi/operator állapot ne tudja újra-seedelni magát a wipe után.

Van host-oldali vault key út is: titkosítás nyugalmi állapotban felügyelet nélküli reboottal (szemben a PIN-úttal, ahol boot-nál kézzel kell feloldani).

Ismert korlátok (a projekt maga sorolja fel)

Ezek nem találgatások — a SECURITY-MODEL.md „Known limits (accepted)” szekciója.

  • Nincs created_at frissességi ablak semmilyen inbound úton (firmware relay oldal és heartwoodd is figyelmen kívül hagyja, redelivered requestet újra feldolgoz). Ez tudatos: egy ablak eltörné a skew-elt órájú legitim klienseket, miközben a tartós challenge már minden mutációt horgonyoz. Amit egy replayelt, korábban érvényes request elérhet: mutációk → semmit (tartós egyszer-használatos challenge); sign_event → friss aláírás a azonos, már publikált eventre (a relay-ek a közös id-t dedupálják); encrypt/decrypt → a válasz az eredeti kliens pubkey-ére újrazáródik, így a replayelő támadó semmit nem olvas; gomb-gated metódusok → egy elfogott request újraéleszthet egy 30 s-os promptot (availability nuisance, sosem approval, a fizikai gomb korlátozza).
  • Nincs bekötött rate limiting. Az áprilisi design 60 req/60 s per-kliens számlálója létezik (policy.rs), de a dispatch nem konzultálja. A visszaélést a policy-gating (ismeretlen kliens = csak gomb), az unbound-peer denial halmaz és a korlátos approval gépezet fékezi. A számláló bekötése jövőbeli hardening.
  • A PBKDF2 KDF költsége identitásonként skálázódik: az unlock a PBKDF2-HMAC-SHA256 költséget (100k round) minden sealed masterre megfizeti — körülbelül 26 s három identitásra jelenlegi hardveren, ezért a boot unlock 60 s-ot enged. A per-tipp költség a lényeg (minden offline brute-force próba megfizeti), de behatárolja, hány identitást tolerál egy PIN-felhasználó. A vault-key út ugyanezt a nyugalmi védelmet nyújtja identitásonkénti humán-titok költsége nélkül.
  • A bridge-secret rotáció fizikai: a USB pairing secret csak akkor állítható/cserélhető, ha nincs autentikált session, fizikai nyomás alatt (Sapwood: „replace pairing”). Nincs remote rotáció — a bridge secret sosem megy át a relay úton, így egy kiszivárgott pairing a device-on revokálódik, nem a vezetéken.
  • A backup slot- és bridge-secreteket hordoz (seedet SOHA). BACKUP_EXPORT és BACKUP_IMPORT is autentikált bridge sessiont és fizikai nyomást igényel. Az import semmit nem állít vissza authority-ként: minden slot újravalidálódik a management policy validátoron, a signing_approved/sign_event és kind-plafonok lecsupaszodnak (minden slot újra kiérdemli az aláírást fizikailag vagy operátor-policy-ból), és a hordozott bridge secret csak a saját külön nyomása alatt települ. Az export fájl tervezés szerint plaintext secrets dump — ennek megfelelően kell óvni.
  • A legacy NIP-04 egy decryption oracle gyenge kriptóhoz. A nip04_decrypt metódus engedélyezése egy slot policy-ban a device-ot AES-256-CBC (autentikálatlan legacy titkosítás) decryption oracle-jává teszi → ahol a kliens támogatja, a NIP-44 metódusokat kell előnyben részesíteni.
  • Egy device-operátor; identitásonként delegate-ek. A device-wide op_mgmt pubkey a management root. Egy identitás delegálható a saját operátorának (set_identity_operator, device-operator only, challenge-protected, restarttal érvényesül) — de a delegate egyetlen identitásra korlátozott: nem adhat hozzá identitást, nem delegálhat tovább, nem olvas device-wide állapotot (network config, audit ring, relay topológia, storage inventory), és nem tudja felsorolni sem a tulajdonos többi identitását. Két reziduális: a Sapwood telefon-handoff a device-operátor credentialt másolja ahelyett, hogy függetlenül revokálható delegate-et regisztrálna; és a böngészők egy operátort újrahasználhatnak több signer között, növelve a kompromittálódás blast radiusát. Magát a device-operátort cserélni trusted-USB, fizikai hold marad.
  • Boundolt duplicate history; nincs freshness window: a RAM-only request-id halmaz korlátos (SEEN_MAX = 64) és reboottal nullázódik. A polled read id-k NVS-en kívül tartása napi ~43 200 írást spórol egy 4 másodperces poll intervallummal nyitott managernél.

A megoldatlan kriptográfiai rés: NIP-NONCE-COMMITMENT

Ez a legértékesebb technikai anyag a projektben, és a projekt maga nyitotta meg.

A probléma. Egy NIP-46 signer azért kapja meg az nsecet, hogy semmi másnak ne kelljen megkapnia. Egy csatorna marad nyitva: maga az aláírás. A BIP-340 engedi, hogy a signer 32 bájt auxiliáris randomit adjon a nonce-derivációjához, és a protokoll semmi módon nem korlátozza ezt a választást. Rosszindulatú firmware az aux_rand-ot csiszolhatja, amíg az így kapott aláírás néhány választott bitet kódol, és néhány tucat tökéletesen érvényes aláíráson keresztül egy teljes seedet szivárogtat ki. A felhasználó normál eseményeket lát, normálisan aláírva. Semmilyen log, kijelző vagy policy engine nem vesz észre semmit.

Nem hipotetikus. A hardware wallet világ már végigment rajta: a Blockstream Jade Anti-Exfil protokollja azért létezik, mert a nonce-on keresztül szivárogtató kompromittált aláíró device-ot reális fenyegetésnek ítélték Bitcoinon. A nyílt forrás és a reprodukálható buildek szűkítik a rosszindulatú firmware ablakát, de nem zárják be: egy verifier csak azt a buildet tudja ellenőrizni, amit verifikált, nem azt, ami leszállt.

A megoldás (sign-to-contract). A signer elkötelezi magát a nonce-pontja mellett, mielőtt megtudná a kliens által hozzáadott randomitást; a végleges nonce a committed pont, módosítva mindkettő hash-ével. A signer nem tudja csiszolni azt, amit nem tud megjósolni.

A protokoll: két további NIP-46 metódus, sign_event_commit és sign_event_reveal, amik rendes NIP-46 requestként utaznak kind 24133 envelopban. Első körben a kliens 32 friss random bájtot (rho) húz, és a hash-ét küldi el, a rho-t titokban tartva; a signer erre commitálja a nonce-pontját. A második körben a kliens felfedi a rho-t, és a végleges nonce mindkettő függvénye.

Státusz: publikálva draft community NIP-ként, kind 30817, identifier nip-nonce-commitment (docs/nip-nonce-commitment.md). A firmware-implementáció jövőbeli munka — ma a védelem a szignált, CI-buildelt release chain plusz a szándékos rollback képesség.

Ökoszisztéma

A projekt nem egy fájl, hanem egy kis fa („The whole tree”):

  • Heartwood — a mag: kulcsok a chipen, fizikai gomb mögött.
  • Sapwood (forgesworn/sapwood, TypeScript) — a management console: flash, provision, policy-k beállítása, approve, backup. Két transport: USB bootstrap/recovery Web Serial API-val, és távoli management Nostr relay-eken át a Heartwood kimenő WiFi kapcsolatára. A device nem nyit bejövő portot, nem kell cloud fiók vagy management szerver. A frame protokoll a heartwood-common/src/frame.rs TypeScript portja, 19 teszttel igazolva a bájt-szintű kompatibilitást a Rust implementációval. Saját státusza: „Untested alpha” — a teljes write-down/wipe/hardware-restore ceremónia még nem futott le, ezért csak teszt kulcsokkal, független backup mellett.
  • heartwoodd — a daemon (a heartwood-bridge oldalán).
  • nsec-tree — determinisztikus Nostr sub-identity deriváció.
  • bray — trust-aware Nostr MCP szerver AI ágensekhez (a client/bray kulcs erre utal).

A forgesworn org ~30 repót tart, szinte mindet ugyanaznap pusholva, jellemzően 0-3 csillaggal: szélességében építő egyéni fejlesztő, nem közösségi projekt. A Heartwood viszont április óta stabilan fókuszált.

Miért érdekes

A Nostr kulcsnak nincs jelszó-resetje, és a legtöbb mégis a legtámadottabb szoftverben él: egy böngésző-profilban, egy telefonos appban, egy szerver home könyvtárában. Egy phishing oldal, egy rosszindulatú update, egy elrontott backup — és az identitás véglegesen másé.

A Heartwood ezt a kulcsot egy olyan device-ra teszi, aminek az egyetlen feladata a megtartása. Amit ténylegesen kizár: a távoli és a szoftveres támadót — vagyis pont azt, ami a valóságban elviszi az nseceket. Amit nem zár ki a default konfigurációban: a fizikai támadót egy USB kábellel. Ez utóbbihoz be kell kapcsolni a PIN-t vagy a vault key-t — és akkor is egy ESP32 szintjén véd, nem eFuse-szinten.

A projekt értékét nagyban növeli, hogy ezt maga mondja ki, ugyanolyan részletességgel, mint az erősségeit.

Kapcsolódó cikkek

  • raw/articles/ — Nostr ökoszisztéma és protokoll anyagok
  • topics/nostr-ecosystem.md — NIP-46 signer réteg
  • topics/bitcoin-wallet-security.md — hardver wallet-ok, Coldcard entrópia-eset, Trezor Safe 7
  • raw/references/clink-lightning-nostr.md — Nostr-native Lightning specifikáció (analóg minta)

Források

  • https://github.com/forgesworn/heartwood-esp32 — README.md, SECURITY-MODEL.md (58 KB), docs/nip-nonce-commitment.md
  • https://heartwood.forgesworn.dev/ — projekt weboldal
  • https://github.com/forgesworn/sapwood — management console (TypeScript, „untested alpha”)
  • https://github.com/forgesworn/nsec-tree — sub-identity deriváció
  • Feldolgozva: 2026-09-26
Vissza a tetejére