You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fourth step of the Swarm Foundation Plan v1. Once node identity is in place, every record that EVER leaves this node must be signed. This issue creates the reusable signature service that all later swarm phases (sync, hub-anchors, peer-exchange) will call.
SWARM-Sharing-Trennlinie (9139bc45-e1bf-47cb-957a-e75dc17f83f5) — what types of records will be signed (lessons, hub_anchors, node_advertisements)
JSON Canonical Form: research the simplest TypeScript-friendly approach. Two viable paths:
RFC 8785 (JCS) — there's an npm package canonicalize (~1KB, no deps)
Custom: sort keys recursively, no whitespace, UTF-8 — write yourself if avoiding deps is preferred
If unsure: WebSearch "json canonical form RFC 8785 typescript 2026" and pick the path with the smallest dependency footprint.
What this issue delivers
A new file src/services/signature.ts (or mcp-server/src/services/signature.ts — match where existing services live):
// pseudo-API, adjust to project style:exportfunctioncanonicalize(value: unknown): stringexportfunctionsign(record: object,privateKeyPem: string): string// base64 signatureexportfunctionverify(record: object,signature: string,publicKey: Uint8Array): boolean// Convenience wrapper used by the swarm endpoints:exportfunctionsignWithSelfKey(record: object): {record: object;signature: string;signed_at: string}
Use node:crypto (createSign('sha256') won't work for Ed25519 — use crypto.sign(null, data, privateKey) per Node 14+ API). Confirm with WebSearch if uncertain.
The signed payload format is: canonicalize the record EXCLUDING the signature field itself, then sign the resulting bytes. Document this clearly in the file's JSDoc.
Acceptance criteria
File signature.ts exists at the matched path
Tests under the project's existing test directory:
Round-trip: sign then verify with the matching public key returns true
Tampered record (change one byte) → verify returns false
Verify with WRONG public key → returns false
Canonical form is deterministic across reorderings ({a:1, b:2} and {b:2, a:1} produce the same signature)
Empty/missing signature field is excluded from the signed bytes
DO NOT add any networking, HTTP, or libp2p code. This issue is JUST the crypto primitives.
DO NOT integrate this into existing record-creation paths yet. That's a follow-up issue (Phase 2.5: wire signature into lesson-synthesizer).
DO NOT touch the migration files or the node-identity script — read-only dependency on the public key.
Out of scope
Wiring signatures into actual lesson creation (next issue, will be created after this is merged)
Key rotation, revocation lists (v2)
Multi-signature schemes (v2)
Dependency
Conceptually depends on #74 (spec for canonical form) and #76 (node identity exists). The crypto primitives can be built and tested in isolation though — proceed even if those aren't merged.
Why
Fourth step of the Swarm Foundation Plan v1. Once node identity is in place, every record that EVER leaves this node must be signed. This issue creates the reusable signature service that all later swarm phases (sync, hub-anchors, peer-exchange) will call.
Background — read this BEFORE starting
Recall:
b899a80a-738e-40c9-8b5d-a85e3df79922) — Phase 2 (provenance & signatures)57388366-4807-47d8-aed3-819ac21b1722) — pillar 2 (generalisierung-vor-sharing implies anything-that-leaves-must-be-attributable)9139bc45-e1bf-47cb-957a-e75dc17f83f5) — what types of records will be signed (lessons, hub_anchors, node_advertisements)JSON Canonical Form: research the simplest TypeScript-friendly approach. Two viable paths:
canonicalize(~1KB, no deps)If unsure: WebSearch "json canonical form RFC 8785 typescript 2026" and pick the path with the smallest dependency footprint.
What this issue delivers
A new file
src/services/signature.ts(ormcp-server/src/services/signature.ts— match where existing services live):Use
node:crypto(createSign('sha256')won't work for Ed25519 — usecrypto.sign(null, data, privateKey)per Node 14+ API). Confirm with WebSearch if uncertain.The signed payload format is: canonicalize the record EXCLUDING the
signaturefield itself, then sign the resulting bytes. Document this clearly in the file's JSDoc.Acceptance criteria
signature.tsexists at the matched path{a:1, b:2}and{b:2, a:1}produce the same signature)signaturefield is excluded from the signed bytesanysmuggled inHard constraints
Out of scope
Dependency
Conceptually depends on #74 (spec for canonical form) and #76 (node identity exists). The crypto primitives can be built and tested in isolation though — proceed even if those aren't merged.