release(v4.0.0): Shade GA — V3.x consolidation + audit prep
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
V3.1 → V3.12 consolidated and tagged for the first GA release. Wire format unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. The version bump is semantic: audit-cycle complete, opt-in surface fully exposed, threat model refreshed for every new surface. Highlights: - All 24 @shade/* packages bumped to 4.0.0 in lockstep. - CHANGELOG 4.0.0 section is the canonical manifest of what landed. - THREAT-MODEL extended (§10 fingerprint gates, §11 WebRTC P2P, §12 Web-Worker boundary) + residual-risks table refreshed. - OpenAPI now covers all 27 routes: prekey, transfer, KT, inbox, bridge, observer, /metrics, /healthz, /ready. - MIGRATION 0.3.x → 4.0 documented + smoke-tested against shade migrate-storage on a real SQLite DB. - docs/audit/REVIEW-BUNDLE.md + SCOPE.md ready for external reviewer. - scripts/soak.ts harness for the GA-stable 2-week soak window. - All V*.md plans archived under docs/archive/ with Status: Done. - Voice/Video carved out into V5.0; 4.0 audit focuses on the frozen non-realtime stack. Tests: TS 1000/1000 + Kotlin 11/11 cross-platform vectors green. Docker: gt.zyon.no/stian/shade-prekey:4.0.0 builds and reports version 4.0.0 on /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
packages/shade-recovery/src/store.ts
Normal file
91
packages/shade-recovery/src/store.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Guardian-side persistence for received shares.
|
||||
*
|
||||
* Each guardian holds, per protected user, exactly one record per
|
||||
* `setupId`:
|
||||
*
|
||||
* {
|
||||
* originalAddress, // the protected user's Shade address
|
||||
* setupId, // disambiguates if a user re-runs setup
|
||||
* shareIndex, // their assigned x-coordinate
|
||||
* shareBytes, // base64-encoded Shamir share
|
||||
* shareSecretCiphertext, // base64 — encrypted backup payload
|
||||
* shareSecretNonce, // base64 — AES-GCM nonce
|
||||
* setupFingerprint, // safety number at deposit time
|
||||
* guardianCount, threshold, // informational
|
||||
* receivedAt
|
||||
* }
|
||||
*
|
||||
* The interface is deliberately minimal so consumers can back it with
|
||||
* whatever they already use for app state — IndexedDB, AsyncStorage,
|
||||
* SQLite, etc. The package ships an in-memory implementation suitable
|
||||
* for tests and small one-device demos. Guardian apps that survive
|
||||
* restart MUST supply a persistent implementation.
|
||||
*/
|
||||
|
||||
export interface GuardianShareEntry {
|
||||
originalAddress: string;
|
||||
setupId: string;
|
||||
shareIndex: number;
|
||||
shareBytes: string;
|
||||
shareSecretCiphertext: string;
|
||||
shareSecretNonce: string;
|
||||
setupFingerprint: string;
|
||||
guardianCount: number;
|
||||
threshold: number;
|
||||
receivedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimal contract a guardian-side share store must implement.
|
||||
*
|
||||
* Shapes:
|
||||
* - `save` is upsert by (originalAddress, setupId). If a user re-runs
|
||||
* setup with a different setupId, both entries persist; the guardian
|
||||
* can choose which to release based on the recovery-request's
|
||||
* setupId. If the same (originalAddress, setupId) is saved twice,
|
||||
* the second write wins (idempotent re-deposit).
|
||||
* - `get` returns the deposit for the (originalAddress, setupId) pair,
|
||||
* or `null` when none exists.
|
||||
* - `list` enumerates everything (used by guardian-UX widgets).
|
||||
* - `delete` removes a single deposit; the guardian-UX exposes this so
|
||||
* a user can prune deposits from people they no longer wish to
|
||||
* protect.
|
||||
*/
|
||||
export interface RecoveryStore {
|
||||
save(entry: GuardianShareEntry): Promise<void>;
|
||||
get(originalAddress: string, setupId: string): Promise<GuardianShareEntry | null>;
|
||||
list(): Promise<GuardianShareEntry[]>;
|
||||
delete(originalAddress: string, setupId: string): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-local in-memory store. Suitable for tests and one-shot demos;
|
||||
* LOSES STATE ON RESTART. Production guardian apps must supply a
|
||||
* persistent `RecoveryStore` (see `docs/recovery.md` for backing-store
|
||||
* recommendations).
|
||||
*/
|
||||
export class MemoryRecoveryStore implements RecoveryStore {
|
||||
private readonly entries = new Map<string, GuardianShareEntry>();
|
||||
|
||||
async save(entry: GuardianShareEntry): Promise<void> {
|
||||
this.entries.set(keyOf(entry.originalAddress, entry.setupId), { ...entry });
|
||||
}
|
||||
|
||||
async get(originalAddress: string, setupId: string): Promise<GuardianShareEntry | null> {
|
||||
const found = this.entries.get(keyOf(originalAddress, setupId));
|
||||
return found === undefined ? null : { ...found };
|
||||
}
|
||||
|
||||
async list(): Promise<GuardianShareEntry[]> {
|
||||
return Array.from(this.entries.values()).map((e) => ({ ...e }));
|
||||
}
|
||||
|
||||
async delete(originalAddress: string, setupId: string): Promise<void> {
|
||||
this.entries.delete(keyOf(originalAddress, setupId));
|
||||
}
|
||||
}
|
||||
|
||||
function keyOf(originalAddress: string, setupId: string): string {
|
||||
return `${originalAddress} ${setupId}`;
|
||||
}
|
||||
Reference in New Issue
Block a user