End-to-end join: user runs `claudemesh join ic://join/<base64>` and walks away with a signed member record + persistent keypair. new modules: - src/crypto/keypair.ts: libsodium ed25519 keypair generation. Format is crypto_sign_keypair raw bytes, hex-encoded (32-byte pub, 64-byte secret = seed || pub). Same format libsodium will need in Step 18 for sign/verify. - src/invite/parse.ts: ic://join/<base64url(JSON)> parser with Zod shape validation + expiry check. encodeInviteLink helper for tests. - src/invite/enroll.ts: POST /join to broker, converts ws:// to http:// transparently. rewritten join command wires them together: 1. parse invite → 2. generate keypair → 3. POST /join → 4. persist config → 5. print success. state/config.ts: saveConfig now chmods the file to 0600 after write, since it holds ed25519 secret keys. No-op on Windows. signature verification (step 18) + invite-token one-time-use tracking are deferred. For now the invite link is a plain bearer token; any client with the link can join. verified end-to-end via apps/cli/scripts/join-roundtrip.ts: build invite → run join subprocess → load new config → connect as new member → send A→B → receive push. Flow passes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
37 lines
898 B
TypeScript
37 lines
898 B
TypeScript
/**
|
|
* Ed25519 keypair generation using libsodium.
|
|
*
|
|
* We use libsodium-wrappers even in Step 17 (pre-crypto) so the key
|
|
* format matches what Step 18's signing/encryption code will expect —
|
|
* no migration needed later.
|
|
*/
|
|
|
|
import sodium from "libsodium-wrappers";
|
|
|
|
let ready = false;
|
|
|
|
export async function ensureSodium(): Promise<typeof sodium> {
|
|
if (!ready) {
|
|
await sodium.ready;
|
|
ready = true;
|
|
}
|
|
return sodium;
|
|
}
|
|
|
|
export interface Ed25519Keypair {
|
|
/** 32-byte public key, hex-encoded. */
|
|
publicKey: string;
|
|
/** 64-byte secret key (seed || publicKey), hex-encoded. */
|
|
secretKey: string;
|
|
}
|
|
|
|
/** Generate a fresh ed25519 keypair. */
|
|
export async function generateKeypair(): Promise<Ed25519Keypair> {
|
|
const s = await ensureSodium();
|
|
const kp = s.crypto_sign_keypair();
|
|
return {
|
|
publicKey: s.to_hex(kp.publicKey),
|
|
secretKey: s.to_hex(kp.privateKey),
|
|
};
|
|
}
|