Skip to main content

hash_attestation/
lib.rs

1//! # hash-attestation
2//!
3//! **Sign and verify Kinetic Gain Protocol Suite documents** using ed25519
4//! signatures over the same canonical-hash convention every other Suite
5//! repo already uses (`sha256:<hex>` over sorted-keys, no-whitespace JSON).
6//!
7//! ## The missing layer
8//!
9//! Right now a consumer fetches an AEO doc (or agent-card, or decision-card)
10//! over HTTPS and trusts that the bytes came from the published origin. That
11//! works for typo-grade tampering but breaks the moment a CDN is misconfigured
12//! or an MITM lands. This crate adds a detached-signature layer: vendors sign
13//! the canonical hash with an ed25519 private key, publish the public key at a
14//! well-known URL, and consumers verify the [`Attestation`] before they trust
15//! the doc.
16//!
17//! ## At-a-glance
18//!
19//! ```
20//! use hash_attestation::{Attestation, Attestor, canonical_hash};
21//! use ed25519_dalek::SigningKey;
22//! use rand_core::OsRng;
23//!
24//! let key = SigningKey::generate(&mut OsRng);
25//! let attestor = Attestor::new(key.clone(), "https://acme.example/keys/aeo".to_string());
26//!
27//! let body = serde_json::json!({
28//!     "aeo_version": "0.1",
29//!     "entity": { "id": "https://acme.example/#org", "name": "Acme" }
30//! });
31//!
32//! let signed: Attestation = attestor.sign(&body).unwrap();
33//! assert!(signed.verify(&key.verifying_key(), &body).is_ok());
34//! ```
35//!
36//! ## What's in the box
37//!
38//! - [`canonical_hash`] — `sha256:<hex>` over canonical JSON. Same convention
39//!   as `procurement-decision-api` + `aeo-validator-service`.
40//! - [`Attestor`] — wraps a `SigningKey` with the key URL so calls always
41//!   produce a self-describing [`Attestation`].
42//! - [`Attestation`] — serde-serialisable signature record. Drop it next to
43//!   the source doc as `<doc>.sig.json`, or include it inline.
44//! - [`Verifier`] — convenience for verifying with a list of trusted keys.
45//!
46//! ## Composes with
47//!
48//! - **[aeo-validator-service](https://github.com/mizcausevic-dev/aeo-validator-service)**
49//!   — verifies the attestation alongside its drift check; mismatches surface
50//!   as a top-level `Attestation::Tampered` issue.
51//! - **[procurement-decision-api](https://github.com/mizcausevic-dev/procurement-decision-api)**
52//!   — every published Decision Card can be paired with a signature so policy
53//!   bundles can prove provenance.
54//! - **[aeo-crawler](https://github.com/mizcausevic-dev/aeo-crawler)** — emits
55//!   the canonical hash for every fetched doc, ready for offline verification.
56
57#![warn(missing_docs)]
58#![warn(rust_2018_idioms)]
59#![warn(clippy::pedantic)]
60#![allow(clippy::module_name_repetitions)]
61#![allow(clippy::missing_errors_doc)]
62#![allow(clippy::missing_panics_doc)]
63#![allow(clippy::must_use_candidate)]
64#![allow(clippy::doc_markdown)]
65#![allow(clippy::cast_possible_truncation)]
66#![allow(clippy::cast_possible_wrap)]
67#![allow(clippy::cast_sign_loss)]
68
69pub mod attestation;
70pub mod attestor;
71pub mod error;
72pub mod hash;
73
74/// Optional audit-stream-py producer. Gated behind the `audit-stream`
75/// Cargo feature so the core crypto crate stays sync and HTTP-free.
76#[cfg(feature = "audit-stream")]
77pub mod audit_stream;
78
79pub use attestation::Attestation;
80pub use attestor::Attestor;
81pub use error::AttestationError;
82pub use hash::canonical_hash;
83
84// Verifier holds a small set of trusted keys.
85pub use attestor::Verifier;