Ver código fonte

tor: correct what tor reads from onion.key

moos 5 horas atrás
pai
commit
97aa431f68

+ 2 - 1
engine/crates/moos-p2p/src/lib.rs

@@ -170,7 +170,8 @@ pub async fn read_message<S: AsyncRead + Unpin>(
 /// Compiled-in production seed bootnode multiaddrs. The permanent seed is
 /// THIS laptop's bootnode: a systemd user unit (`moos-bootnode.service`,
 /// tor-only) whose onion key is persisted under
-/// `rundir-bootnode/bootnode/` — the key IS the onion address, so the
+/// `rundir-bootnode/bootnode/` — tor derives the service id from the first
+/// 32 bytes of that `onion.key` (see `moos-tor/src/onion_key.rs`), so the
 /// multiaddr below is stable across restarts. To replace the seed: run a
 /// new bootnode with `--tor` (prints its `paddr /onion3/...`), back up the
 /// onion.key, then swap the entry here and cut a new release.

+ 4 - 6
engine/crates/moos-tor/src/control.rs

@@ -20,12 +20,10 @@ pub enum OnionKey {
     /// `ADD_ONION NEW:BEST` — ephemeral, service dies with tor. Testing only.
     NewBest,
     /// `ADD_ONION ED25519-V3:<base64>` — stable address across restarts
-    /// (default). We emit 64 bytes laid out seed || pub, padded standard
-    /// base64. Verified against real tor 0.4.9.13 (tests/live_tor.rs):
-    /// imports of seed||pub are accepted and the minted ServiceID derives
-    /// from the seed/pub pair; note tor's own PrivateKey EXPORT uses a
-    /// different first half (not the raw seed) — blobs are not symmetric,
-    /// but import+address derivation is deterministic and correct.
+    /// (default). We emit 64 bytes, padded standard base64; tor uses the
+    /// first 32 verbatim as the secret scalar and ignores the rest, so the
+    /// ServiceID is `base32(key[0..32]*B ‖ checksum ‖ 0x03)` — stable only
+    /// because the persisted file is (see `onion_key.rs`).
     Ed25519V3([u8; 64]),
 }
 

+ 13 - 7
engine/crates/moos-tor/src/onion_key.rs

@@ -3,13 +3,19 @@
 //! Persisted v3 onion key: load-or-create at
 //! `<data-dir>/onion.key`, same 0600 file / 0700 parent discipline as the
 //! daemon's `net.key` handling (audit R1 F-02, mirroring `net.rs
-//! load_or_create_key` and `perms.rs`). The file is hex(seed || pub) — a
-//! 64-byte blob accepted verbatim by `ADD_ONION ED25519-V3:`, minting the
-//! standard address of the seed's public key (verified live against tor
-//! 0.4.9.13, see tests/live_tor.rs; tor's own PrivateKey EXPORT blob is
-//! laid out differently, but import semantics are deterministic).
+//! load_or_create_key` and `perms.rs`). The file is hex of 64 bytes, handed
+//! verbatim to `ADD_ONION ED25519-V3:`.
 //!
-//! The keypair public half is derived at creation with ed25519-dalek (the
+//! Tor reads ONLY `key[0..32]`: verbatim as the ed25519 secret scalar (no
+//! hashing, no clamping) and derives the service id from `key[0..32]*B`.
+//! `key[32..64]` is never read — it is ed25519-dalek's public key of the
+//! random seed, kept as material, and is NOT the address's public key.
+//! Verified against tor 0.4.9.13 on 2026-10-29 by re-deriving a live
+//! bootnode id from its persisted file; tor's own PrivateKey EXPORT is laid
+//! out differently again, so these blobs are not symmetric — the first 32
+//! bytes alone decide the address (and are what a backup must preserve).
+//!
+//! The stored public half is derived at creation with ed25519-dalek (the
 //! only vendored curve implementation; already locked via libp2p identity).
 //! getrandom 0.2 seeds it.
 
@@ -107,7 +113,7 @@ pub fn load_or_create_onion_key(path: &std::path::Path) -> Result<OnionKey, TorE
         let key: [u8; 64] = bytes.try_into().map_err(|v: Vec<u8>| {
             invalid(
                 path,
-                format!("onion key must be 64 bytes (seed || pub), got {}", v.len()),
+                format!("onion key must be 64 bytes (scalar + pub), got {}", v.len()),
             )
         })?;
         moos_log::mlog_info!("tor", "onion key loaded path={}", path.display());