first commit
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# `LogChainSigner`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Append-only signed log chain. Each entry carries the SHA-256 hash of the previous entry's bytes plus the current payload, signed under an Ed25519 key. `verify()` walks the chain from suaveplan and raises `CHAIN_TAMPERED` on the first hash mismatch or signature failure — guaranteeing that any tamper to a leaf is detectable as a chain break.
|
||||
|
||||
## Features
|
||||
|
||||
- suaveplan hash is 32 zero bytes; the signature on the first entry covers the zero hash plus the suaveplan payload.
|
||||
- `verify()` reports the offending sequence number in `error.context.seq` so consumers locate the corruption without re-walking the chain.
|
||||
- `chain()` returns a copy of the entry buffer so subscribers cannot mutate the in-memory chain by side-effect.
|
||||
- Injectable clock for deterministic tests.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { ed25519 } from "@suaveplan/crypto-curve";
|
||||
import { LogChainSigner } from "@suaveplan/network-plugin-crypto-messages";
|
||||
|
||||
const priv = ed25519.randomPrivateKey();
|
||||
const pub = ed25519.getPublicKey(priv);
|
||||
const signer = new LogChainSigner();
|
||||
signer.append({ event: "join", peer: "alice" }, { publicKey: pub, privateKey: priv });
|
||||
signer.append({ event: "leave", peer: "bob" }, { publicKey: pub, privateKey: priv });
|
||||
signer.verify(); // ok
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { LogChainSigner } from "@suaveplan/network-plugin-crypto-messages";
|
||||
|
||||
const signer = new LogChainSigner({ clock: () => 1_700_000_000_000 });
|
||||
for (const event of events) {
|
||||
signer.append(event, hostKeyPair);
|
||||
}
|
||||
|
||||
const replicated = signer.chain();
|
||||
// Ship `replicated` to a sibling host; receiver replays via a fresh
|
||||
// LogChainSigner-equivalent verifier.
|
||||
signer.verify();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class LogChainSigner(config?)`
|
||||
- `append(payload, keyPair): LogChainEntry<TPayload>`.
|
||||
- `chain(): ReadonlyArray<LogChainEntry>`.
|
||||
- `verify(): void` — throws `CHAIN_TAMPERED` on first inconsistency.
|
||||
- `reset(): void` — clears the chain (test helper).
|
||||
- `interface LogChainEntry<TPayload>` — `{ seq, previousHash, payload, signature, signerPublicKey, recordedAt }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `previousHash` is the SHA-256 of the previous entry's `(previousHash || canonicalSerialise(payload))` bytes — NOT the previous signature. Hashing the bytes covered by the signature is sufficient for tamper-evidence and avoids a circular dependency where the next entry's hash depends on the current entry's signature.
|
||||
- `signerPublicKey` is recorded per entry so verifiers can roll signing keys mid-chain (multiple hosts contributing to the same chain) without breaking earlier entries.
|
||||
- The signer never zeroizes or persists `keyPair` — that is the caller's responsibility. Production deployments wire a secure-key manager.
|
||||
- `entryBytes` returns the canonical bytes for both signing and hash recomputation so signers and verifiers agree.
|
||||
Reference in New Issue
Block a user