* kv-proto: ticket and join wire types, LoginFinish.ticket, b64_opt.
* kv-crypto: pairing.rs (HPKE AuthPSK seal/open with the QR secret as PSK, SAS code).
* kv-sync: onboarding.rs (kestrove://setup/v1 and kestrove://join/v1 codec with
strict validation), ApiClient ticket/join calls and login_with_ticket, test
server support for tickets and rendezvous.
* kv-ffi: parse/encode_setup_payload, setup_payload_for_kit, add_device_payload,
revoke_enrollment_ticket, login_with_setup, join_start/poll/cancel,
join_approve/send. The Secret Key is kept locally, HPKE-sealed to enc_pk.
* Tests: codec round trips and rejections, AuthPSK tamper cases, end-to-end
Add-device QR and scan-to-join between two clients, forged bundles, bound
tickets; live test extended with scan-to-join.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tombstones carried no ciphertext, so the server could forge a delete (hiding
an item), and a device that pulled one accepted the next live version without
a seq comparison (delete-then-replay rollback).
* A delete is sealed like an item (same AAD and padding) with plaintext
{id, seq, deleted: true}; seq = working/base seq + 1. DELETE carries it as
the ItemPut body; the test server stores and serves it.
* replay: Base is None | LegacyTombstone | Sealed(plaintext). check_pulled
refuses a record without ciphertext, a deleted flag that disagrees with the
plaintext, or a tombstone naming another item, then requires seq to grow
for tombstones too. The `restores` exception is gone: edit beats delete
re-seals with max(edit, tombstone) + 1. next_version returns just seq.
* Legacy: a pulled tombstone without ciphertext is an integrity alarm (cannot
be told apart from a forgery). A pending legacy delete is sealed on push; a
legacy tombstone already held as base is accepted as unknown seq.
* engine: open_sealed, seal_tombstone; next_local_version -> u64. Merkle leaf
hashes a sealed tombstone's envelope like a live record's.
* kv-model Item: `restores` removed. kv-ffi delete_item seals the tombstone;
FFI surface unchanged.
* Tests: forged (unsealed and relabelled) tombstones raise an integrity alarm
(kv-sync and kv-ffi), replay after a tombstone, replays of the tombstone and
of the restore, legacy pending delete, shared item_replay.json table; the
live test now deletes an item through the real server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new device needs account_id and the KDF params to derive AUTH_INPUT
before it can build KE1, so they cannot come only from login/start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds the OpaqueSuite (Ristretto255 OPRF, TripleDH, SHA-512, identity KSF)
shared by client and server, the httpsig signature-base builders, and the
setup/credentials/account/device/SSE types for milestone M1.
Cargo.lock changes only for kv-proto's new dependencies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>