Pear Baby Rooms 2: who said that?
Lesson 1's room has a flaw you can feel immediately: nothing ties a message to
a sender. Anyone can write alice: dinner is cancelled. In P2P there is no
server to ask who is who — so we do what the grown-up stack does:
every message is signed, and your keypair is your name.
(Identity is a keypair is the theory version.)
Add identity
const crypto = require('hypercore-crypto')
const fs = require('fs')
// Keep the same identity across restarts: persist the keypair.
function loadIdentity (path) {
if (fs.existsSync(path)) {
const seed = Buffer.from(fs.readFileSync(path, 'utf8'), 'hex')
return crypto.keyPair(seed)
}
const seed = crypto.randomBytes(32)
fs.writeFileSync(path, seed.toString('hex'), { mode: 0o600 })
return crypto.keyPair(seed)
}
const me = loadIdentity('./identity.key')
console.log('you are', me.publicKey.toString('hex').slice(0, 8))
Sign everything you send
function send (text) {
const payload = Buffer.from(JSON.stringify({ text, ts: Date.now() }))
const wire = JSON.stringify({
from: me.publicKey.toString('hex'),
payload: payload.toString('hex'),
sig: crypto.sign(payload, me.secretKey).toString('hex')
}) + '\n'
for (const p of peers) p.write(wire)
}
Verify everything you receive
function receive (line) {
const m = JSON.parse(line)
const payload = Buffer.from(m.payload, 'hex')
const ok = crypto.verify(payload, Buffer.from(m.sig, 'hex'),
Buffer.from(m.from, 'hex'))
if (!ok) return console.log('!! dropped a forged message')
const { text } = JSON.parse(payload.toString())
console.log(m.from.slice(0, 8) + ':', text)
}
What you just learned
- Verify on receive, always. The socket being encrypted says nothing
about authorship — encryption is the envelope, the signature is the
handwriting.
- The 8-char hex prefix is a real name: stable across restarts, impossible to
forge without the secret file. Nicknames can come later as signed
"profile" messages — decoration on top of the key, never a replacement.
- Messages still vanish when the room empties. Next: