From db3e5dfe4907d959c78859187f8f0a14612dd2ff Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 15 Aug 2026 04:18:21 -0700 Subject: [PATCH] Document: bind Echo and Keep content identities --- crates/echo-cas/tests/keep_identity_bridge.rs | 65 +++++++++++++++++++ .../echo-keep-physical-content-boundary.md | 42 ++++++++++-- 2 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 crates/echo-cas/tests/keep_identity_bridge.rs diff --git a/crates/echo-cas/tests/keep_identity_bridge.rs b/crates/echo-cas/tests/keep_identity_bridge.rs new file mode 100644 index 00000000..556e1ae3 --- /dev/null +++ b/crates/echo-cas/tests/keep_identity_bridge.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// © James Ross Ω FLYING•ROBOTS +//! Same-source Echo and pinned Keep identity-vector witness. +//! +//! The raw Echo vectors were generated independently with `b3sum` 1.8.5. The +//! Keep vectors and preimage are pinned to Keep commit +//! `3bf7b9179db41e90620e6d1875c2d40222a2330b`. + +use std::num::TryFromIntError; + +use echo_cas::blob_hash; + +const KEEP_BLOB_DATA_MAGIC: &[u8; 16] = b"KEEP:BLOB:DATA\0\0"; +const KEEP_BLOB_IDENTITY_VERSION: [u8; 2] = 1_u16.to_be_bytes(); +const KEEP_BLOB_HASH_ALGORITHM: [u8; 1] = [1]; + +#[test] +fn same_exact_sources_reproduce_echo_and_pinned_keep_identity_vectors( +) -> Result<(), TryFromIntError> { + assert_identity_pair( + &[], + "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", + "c0074a279c09f9d019dc10e4c821f79f1450cfb8541ab4627132ab9f3c75e33f", + )?; + assert_identity_pair( + b"Keep exact bytes.\n", + "3d4f69a42f4cde6629970fe61d548f60d475a0f9ac26a24be5fab204b6eb030a", + "af75d70e4993121254ac71f16c5edd02410a36f94d795e4d6064ed3122b7967d", + )?; + + let byte_ramp = (u8::MIN..=u8::MAX).collect::>(); + assert_identity_pair( + &byte_ramp, + "4a495ba42461748eca8fdad618f976aa726cc2903de9fcb40735a786ac1c196b", + "e782f90f48483f6a8520c9b05eca57ace1647374dd9456b9e41aadccacd10f12", + )?; + Ok(()) +} + +fn assert_identity_pair( + bytes: &[u8], + expected_echo: &str, + expected_keep: &str, +) -> Result<(), TryFromIntError> { + let echo = blob_hash(bytes); + let keep = keep_blob_digest_v1(bytes)?; + assert_eq!( + blake3::Hash::from_bytes(*echo.as_bytes()).to_hex().as_str(), + expected_echo + ); + assert_eq!(keep.to_hex().as_str(), expected_keep); + assert_ne!(echo.as_bytes(), keep.as_bytes()); + Ok(()) +} + +fn keep_blob_digest_v1(bytes: &[u8]) -> Result { + let logical_length = u64::try_from(bytes.len())?; + let mut hasher = blake3::Hasher::new(); + hasher.update(KEEP_BLOB_DATA_MAGIC); + hasher.update(&KEEP_BLOB_IDENTITY_VERSION); + hasher.update(&KEEP_BLOB_HASH_ALGORITHM); + hasher.update(bytes); + hasher.update(&logical_length.to_be_bytes()); + Ok(hasher.finalize()) +} diff --git a/docs/architecture/echo-keep-physical-content-boundary.md b/docs/architecture/echo-keep-physical-content-boundary.md index 0ab8507f..85e23b6d 100644 --- a/docs/architecture/echo-keep-physical-content-boundary.md +++ b/docs/architecture/echo-keep-physical-content-boundary.md @@ -10,7 +10,7 @@ implemented on this branch. - **Refines:** [Retained reading storage and proof boundary](../adr/0020-retained-reading-storage-and-proof-boundary.md) - **Depends on:** [Durable external-action settlement](../adr/0026-durable-external-action-settlement.md) -- **Related:** [Keep authenticated reconstruction contract](https://github.com/flyingrobots/keep/blob/3bf7b9179db41e90620e6d1875c2d40222a2330b/docs/architecture/authenticated-reconstruction-contract.md) +- **Related:** [Keep authenticated reconstruction contract](https://github.com/flyingrobots/keep/blob/3bf7b9179db41e90620e6d1875c2d40222a2330b/docs/invariants/authenticated-reconstruction/README.md) ## Decision @@ -205,11 +205,36 @@ Echo may admit an evidenced refusal as an observation only under an Echo law that explicitly accepts that refusal class and its physical aperture. A Rust error alone is not a witnessed refusal. -## Identity bridge +## Identity bridge decision -Echo `BlobHash` and Keep `BlobId` are distinct typed identities. They must not -be cast, substituted, or equated because both currently use 32-byte BLAKE3 -digests. +Echo `BlobHash` and Keep `BlobId` are distinct typed identities. The +experimental bridge retains both; it defines no digest cast or implicit +conversion. + +| Question | Accepted experimental decision | +| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is Echo's current content identity? | [`BlobHash`](../../crates/echo-cas/src/lib.rs) contains the 32-byte output of raw `BLAKE3(P)` for exact content bytes `P`. Its version and algorithm are implicit in the current API. | +| Does Echo identity include logical length? | No. [`RetainedBlobDescriptor`](../../crates/echo-cas/src/retention.rs) and CAS-addressed [WSC references](../../crates/warp-core/src/wsc/store.rs) carry byte length beside the hash, not inside `BlobHash`. | +| What is Keep's current logical identity? | Keep `BlobId` v1 contains identity version 1, algorithm 1, logical length, and the differently framed digest specified below under the [pinned Keep identity contract](https://github.com/flyingrobots/keep/blob/3bf7b9179db41e90620e6d1875c2d40222a2330b/docs/adr/0001-exact-logical-byte-identity.md). | +| Can Echo identity become Keep identity without bytes? | No. Even an Echo hash plus an asserted length cannot calculate Keep's differently framed digest. The adapter needs the exact bytes or a previously admitted binding witnessed from those bytes. | +| Is the conversion one-to-one? | No structural conversion is admitted. One exact stream deterministically produces one Echo-and-Keep pair under the stated hash laws, subject to the collision-resistance assumption, but neither digest output proves a unique preimage. | +| Which identity remains in Echo WAL and WSC? | Echo's existing raw content hash remains unchanged. Keep identity accompanies it only in the adapter's physical-evidence and binding posture. | +| What happens when a version or algorithm differs? | The adapter reports a typed identity-contract incompatibility before publication. It never reinterprets or truncates either coordinate. | +| Are both identities retained during experiment and migration? | Yes, together with exact logical length and the identity-contract versions used to establish their relation. | + +The two exact preimages are therefore: + +```text +Echo: BLAKE3(P) + +Keep: BLAKE3( + "KEEP:BLOB:DATA\0\0" + || 0x0001 + || 0x01 + || P + || len(P) as u64 big-endian +) +``` The adapter establishes their relation by applying both identity laws to one exact source stream and then verifying reconstruction: @@ -228,6 +253,13 @@ Keep's `stage_expected` can verify an expected Keep `BlobId`; it cannot by itself prove an Echo-to-Keep identity relation. The adapter owns the second identity calculation and the binding witness. +The initial [same-source identity witness](../../crates/echo-cas/tests/keep_identity_bridge.rs) +checks externally generated Echo hashes and the pinned Keep v1 golden vectors +for the same bytes. It does not execute Keep, reconstruct retained content, or +prove the route-independent half of the bridge. Issue #722 retains that gate: +reconstruction through the experimental Keep adapter must reproduce the Echo +hash and exact length before the bridge is conforming. + ## Semantic observation and physical evidence Ordinary Echo semantics and Keep realization provenance remain separate: