Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion onomancy_keyhive/src/carriage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
use keyhive_core::event::static_event::StaticEvent;
use onomancy_core::delegation_chain::{DelegationChain, SignedDelegationBytes};

/// The envelope version tag for Keyhive 0.5 bincode encoding.
/// The envelope version tag for Keyhive 0.5 bincode encoding with
/// `[u8; 32]` content references — Keyhive's default `ContentRef`, a
/// content hash.
///
/// Keyhive is generic over its content-reference type, and bincode
/// encodes `[u8; 32]` bare but `Vec<u8>` length-prefixed, so a peer
/// instantiating Keyhive over a variable-width reference emits a
/// different wire format under the same Keyhive version. Such entries
/// are undecodable here: `kh0` names Keyhive's default, and every peer
/// whose proofs must verify here instantiates `[u8; 32]`.
///
/// Keyhive is pre-alpha; when its event encoding changes, this tag
/// bumps (`kh1`, …) and old entries fail loudly instead of misparsing.
Expand Down
18 changes: 18 additions & 0 deletions onomancy_keyhive/tests/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Carriage Fixtures

Frozen authority carriages exercised by the integration tests in `tests/`. One `kh0` entry per line, hex-encoded. These are real-world captures, not generated: do not regenerate, recapture deliberately if ever needed.

## Catalog

| File | Test | Expectation |
|------|------|-------------|
| `variable_width_content_ref_carriage.hex` | `tests/variable_width_content_ref.rs` | invalid — `UndecodableEvent { index: 0 }`; authorizes nobody |

## `variable_width_content_ref_carriage.hex`

Captured 2026-09-08 from the `onomancy-demo` browser app (keyhive_wasm 0.1.0-alpha.8, automerge-repo-keyhive 0.5.0-alpha.6) while minting a certificate for `brooklynzelenka.com`. Eight entries:

- entry 0 — `Delegated`: root-issued, `Admin`, delegate = the certificate's signer, issuer = the document. Signature verifies. Its `after_content` carries a length-prefixed 10-byte reference: the peer instantiated Keyhive over `Vec<u8>` content references, where `kh0` is Keyhive's default `[u8; 32]`.
- entries 1–7 — `PrekeysExpanded` by the signer. No content references, so they decode identically under either instantiation.

The two wire formats differ only when `after_content` is non-empty, which minted carriages never are, so no generated fixture can detect an instantiation mismatch. This capture is the only thing that turns red if the crate is ever switched to variable-width references. Replace with a positive fixture once the browser stack emits `[u8; 32]` references.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
6b683003000000030000000020000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff30000000000000000010000000000000020000000000000004d9a7593eaacded04a9773d34564a3a4cc280bab3e57eed908018250ba14243e01000000000000000a000000000000006623e915b1a66588610520000000000000004d9a7593eaacded04a9773d34564a3a4cc280bab3e57eed908018250ba14243ee135ee1e22086050bbd8ba867b14e18cac8f5cfe765906618d1f6ee0bfe693aab475ae5aec113388c3eac36486f4823e5fa71795234b44b929e680f41d101505
6b6830000000006c49845e20a152c4771edf5f90ec027583fad1fb231b36805b8bccbae8acc75a20000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff38641c0afac58c4fe0ed26a521b3749feb116b6a07b3924a9fa89bf09a5884682d0b2e0fc2e772134fe136a7dfa9b461a9e9af69b551b9e8dbdb9771e4f1b110a
6b683000000000a0b282a271f554c3dfc00aec0bd58a25549dfd12ed1c350f53be1d0e0799d53320000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff38b821ff4c561cb11320f6be47ac80379697adc522e39e8154879786ceac98c2429a1fbd4c9e790ff0ebf3d969253c83410ae049f124482a2a7e818bc6f0de805
6b683000000000f3b3d80a7b20736d4f8ee0b79856cd56bdae02d981427d37336794f4a7df550720000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff31c74303db4118fc620e7d4e698cecfb6c5cefdb5c505d7c5de4cfbcb0b6de3ed927edcdb9d05606c51ec50d0bc9f4cc7b00800cb9f8822462570685a2a286a0e
6b683000000000d18e594f8815b21a2dee703870de1c22206e4021125a3d4ae2566fdc6986483620000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff33c86931f700c08e38510d4f5f1340debdc2f58212b118c35273dcc05de17dc24e4268e48d3dc2435384efd7c8ef04124d57427f0c8e6c8a1568fed681ac0a502
6b683000000000655d6db5ea9afdc9f6ea42b45f48381b77221599752502a4011ef58d6a83c24120000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff3d1ce7ba0f110fc1de91c2b3837a5f096ec9dae8e00f7270fa9e2a6562bdf5af9bab0792a7c983623d133abf8287841372a309aef266525936f032c4b48a1ad0d
6b6830000000006c30b5f2eb94699c7572da9a3db228b7401dac040ac76cacd0a641a7cec2760220000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff36f7960b6f39ca345972e46f6c71059838d564d3d8ffcc422bc54cffbe9ca089ce2ea2d8cc911c3eb5818be7ee623712c742440d55cd079297c2e78305b6b2d0f
6b683000000000cdc0f9c49a68b5830a20d9c76192f7b81f142d33127dd7a5283d4151d4605e4f20000000000000001423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff34e93a0950bb91dbbdc0aa62852907f940862b49da01d15ae3d604940b5172095d98038776d40c1da68c680355c1766c4b1e97f9575be803afce15ae7d5b43e08
88 changes: 88 additions & 0 deletions onomancy_keyhive/tests/variable_width_content_ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! A carriage from a peer that instantiated Keyhive over a
//! variable-width content reference (`Vec<u8>`) instead of the `[u8; 32]`
//! hash `kh0` names. Its one delegation is otherwise sound — root-issued,
//! Admin, to the signer — but `after_content` carries a length-prefixed
//! 10-byte reference where a bare 32-byte hash belongs, so the entry
//! cannot be read as a `kh0` event.
//!
//! The wire formats differ only when `after_content` is non-empty, which
//! minted carriages never are. This fixture pins the refusal: a proof in
//! the wrong encoding is undecodable, and undecodable vouches nothing —
//! it is never misread into an acceptance.

use core::str;

use ed25519_dalek::VerifyingKey;
use onomancy_core::{
anchor::doc::DocAnchor,
delegation_chain::{DelegationChain, SignedDelegationBytes},
};
use onomancy_dnssec::txt::generation_key::GenerationKey;
use onomancy_keyhive::{
authority::KeyhiveAuthority,
carriage::{Carriage, ParseCarriageError},
};
use onomancy_protocol::verifier::state::authority_verifier::AuthorityVerifier;
use testresult::TestResult;

const CARRIAGE: &str = include_str!("fixtures/variable_width_content_ref_carriage.hex");
const DOCUMENT: &str = "4d9a7593eaacded04a9773d34564a3a4cc280bab3e57eed908018250ba14243e";
const SIGNER: &str = "1423beeb0d6e8780f34ad031441269712c52345470bece360e256d642d66fff3";

fn unhex(hex: &str) -> TestResult<Vec<u8>> {
let digits = hex.as_bytes();
if !digits.len().is_multiple_of(2) {
return Err("odd-length hex".into());
}

digits
.chunks_exact(2)
.map(|pair| Ok(u8::from_str_radix(str::from_utf8(pair)?, 16)?))
.collect()
}

fn key(hex: &str) -> TestResult<VerifyingKey> {
let bytes: [u8; 32] = unhex(hex)?
.try_into()
.map_err(|_| "key fixture is not 32 bytes")?;
Ok(VerifyingKey::from_bytes(&bytes)?)
}

fn carriage() -> TestResult<DelegationChain> {
let entries = CARRIAGE
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| unhex(line.trim()).map(SignedDelegationBytes::from))
.collect::<TestResult<Vec<_>>>()?;
Ok(DelegationChain::from(entries))
}

/// The delegation is entry 0; the prekey introductions after it carry
/// no content references and would decode under either instantiation.
#[test]
fn the_delegation_is_undecodable_and_named() -> TestResult {
assert!(matches!(
Carriage::parse(&carriage()?),
Err(ParseCarriageError::UndecodableEvent { index: 0, .. })
));
Ok(())
}

#[test]
fn an_undecodable_carriage_authorizes_nobody() -> TestResult {
let root = DocAnchor::from(key(DOCUMENT)?);

assert!(
!KeyhiveAuthority.authorizes(&root, &key(SIGNER)?, &carriage()?),
"the signer really is the delegate, and it still proves nothing"
);
Ok(())
}

#[test]
fn an_undecodable_carriage_puts_nothing_on_the_path() -> TestResult {
let generation = GenerationKey::from(key(SIGNER)?);

assert!(!KeyhiveAuthority.on_path(&carriage()?, &generation));
Ok(())
}
Loading