| … | … | @@ -9,13 +9,14 @@ function deriveTopic (serverKey, repoName) { |
| 9 | 9 | const keyBuf = typeof serverKey === 'string' |
| 10 | 10 | ? Buffer.from(serverKey, 'hex') |
| 11 | 11 | : serverKey; |
| 12 | | // MUST match git-remote-punch's deriveTopicFromKey (crypto.hash, not |
| 13 | | // crypto.data — different domain-separation prefix). Using crypto.data |
| 14 | | // here produces a topic the client never joins; clients silently land |
| 15 | | // on the daemon's identity topic via existing pre-established |
| 16 | | // connection, receive non-git bytes, and error with "bad line length |
| 17 | | // character" + double-free during cleanup. Discovered 2026-04-20. |
| 18 | | return crypto.hash(Buffer.concat([keyBuf, Buffer.from(repoName, 'utf-8')])).subarray(0, 32); |
| 12 | // crypto.data (NOT crypto.hash) — has a domain-separation prefix so |
| 13 | // per-repo topics can't collide with crypto.hash-based topics |
| 14 | // elsewhere (daemon identity swarm, hyper-svc swarm-RPC, etc.). |
| 15 | // Without the prefix, repos named the same string used in those |
| 16 | // topics' inputs (e.g. 'pear-git-v3') land on the same swarm topic, |
| 17 | // breaking all three. git-remote-punch must also use crypto.data |
| 18 | // to match — see git-remote-punch/lib/derive-topic.js. |
| 19 | return crypto.data(Buffer.concat([keyBuf, Buffer.from(repoName, 'utf-8')])); |
| 19 | 20 | } |
| 20 | 21 | |
| 21 | 22 | module.exports = { deriveTopic }; |