{"id":50454109,"url":"https://github.com/mizcausevic-dev/hash-attestation-rs","last_synced_at":"2026-06-01T01:05:39.350Z","repository":{"id":358118150,"uuid":"1239327609","full_name":"mizcausevic-dev/hash-attestation-rs","owner":"mizcausevic-dev","description":"Sign + verify Kinetic Gain Protocol Suite docs using ed25519 over canonical JSON hashes. The missing 'this AEO actually came from the vendor' layer.","archived":false,"fork":false,"pushed_at":"2026-05-15T19:20:04.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-15T21:53:51.287Z","etag":null,"topics":["attestation","canonical-json","cryptography","dalek","ed25519","kinetic-gain","rust","signature"],"latest_commit_sha":null,"homepage":"https://kineticgain.com/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mizcausevic-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-15T01:43:45.000Z","updated_at":"2026-05-15T19:07:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mizcausevic-dev/hash-attestation-rs","commit_stats":null,"previous_names":["mizcausevic-dev/hash-attestation-rs"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mizcausevic-dev/hash-attestation-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mizcausevic-dev%2Fhash-attestation-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mizcausevic-dev%2Fhash-attestation-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mizcausevic-dev%2Fhash-attestation-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mizcausevic-dev%2Fhash-attestation-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mizcausevic-dev","download_url":"https://codeload.github.com/mizcausevic-dev/hash-attestation-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mizcausevic-dev%2Fhash-attestation-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33755379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["attestation","canonical-json","cryptography","dalek","ed25519","kinetic-gain","rust","signature"],"created_at":"2026-06-01T01:05:39.270Z","updated_at":"2026-06-01T01:05:39.330Z","avatar_url":"https://github.com/mizcausevic-dev.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hash-attestation\n\n[![CI](https://github.com/mizcausevic-dev/hash-attestation-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/mizcausevic-dev/hash-attestation-rs/actions/workflows/ci.yml)\n[![Rust](https://img.shields.io/badge/rust-1.86%2B-orange)](https://www.rust-lang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n**Sign and verify Kinetic Gain Protocol Suite documents** using ed25519 signatures over the same canonical-hash convention every other Suite repo already uses (`sha256:\u003chex\u003e` over sorted-keys, no-whitespace JSON).\n\nThe missing \"this AEO actually came from the vendor\" layer.\n\n```rust\nuse hash_attestation::{Attestation, Attestor};\nuse ed25519_dalek::SigningKey;\nuse rand_core::OsRng;\n\nlet key = SigningKey::generate(\u0026mut OsRng);\nlet attestor = Attestor::new(key.clone(), \"https://acme.example/keys/aeo\".to_string());\n\nlet body = serde_json::json!({\n    \"aeo_version\": \"0.1\",\n    \"entity\": { \"id\": \"https://acme.example/#org\", \"name\": \"Acme\" }\n});\n\nlet signed: Attestation = attestor.sign(\u0026body)?;\nassert!(signed.verify(\u0026key.verifying_key(), \u0026body).is_ok());\n# Ok::\u003c_, hash_attestation::AttestationError\u003e(())\n```\n\n---\n\n## Why\n\nToday a consumer fetches an AEO doc (or agent-card, or decision-card) over HTTPS and trusts the bytes came from the published origin. That covers typo-grade tampering and not much else: a misconfigured CDN, a route hijack, a developer with write access who shouldn't have had it — none of them are visible to the consumer.\n\nThis crate adds a **detached signature layer**:\n\n1. The vendor signs the canonical hash with an ed25519 private key.\n2. The signature + key URL ride alongside the doc (or inline in it).\n3. The vendor publishes the matching public key at a well-known URL.\n4. The consumer fetches the doc, recomputes the hash, fetches the public key, and verifies.\n\nThe signature commits to the **canonical hash**, not the bytes the consumer received. So whitespace, key ordering, and CDN re-encoding don't break verification — but a single character change inside any field does.\n\n---\n\n## What's in the box\n\n| Type | Purpose |\n| --- | --- |\n| `canonical_hash` | `sha256:\u003chex\u003e` over canonical JSON. Identical convention to `procurement-decision-api` + `aeo-validator-service` — same input bytes, same hash, across the portfolio. |\n| `Attestor` | Wraps a `SigningKey` with the public key URL so every produced `Attestation` is self-describing. |\n| `Attestation` | Serde-serialisable envelope: `algorithm`, `signed_hash`, `signature` (base64 ed25519), `key_url`, `signed_at`. Drop it next to the doc as `\u003cdoc\u003e.sig.json` or fold it inline. |\n| `Verifier` | A trust set — `key_url -\u003e VerifyingKey`. Register keys up-front, verify by URL lookup. |\n\n---\n\n## End-to-end shape\n\n```text\nvendor side                                  consumer side\n-----------                                  -------------\nSigningKey                                   Verifier (trust set)\n   │                                            │\n   ▼                                            ▼\nAttestor::new(key, key_url)                  Verifier::trust(key_url, public_key)\n   │                                            ▲\n   ▼                                            │\n.sign(doc) → Attestation ───── published ─────► .verify(attestation, doc)\n                                                returns Ok or AttestationError\n```\n\nWhen a `Verifier::verify` call returns:\n\n- `Ok(())` — the doc is unmodified vs. the moment the vendor signed it AND the signature checks out against the trusted public key.\n- `Err(HashMismatch { … })` — the doc has changed since it was signed.\n- `Err(BadSignature)` — the signature doesn't match the key.\n- `Err(UntrustedKey(…))` — the `key_url` in the attestation isn't in your trust set.\n- `Err(UnsupportedAlgorithm(…))` — v0.1 only knows ed25519.\n\n---\n\n## Composes with\n\n- **[aeo-validator-service](https://github.com/mizcausevic-dev/aeo-validator-service)** — verifies the attestation alongside drift; tamper events surface as a structured issue.\n- **[procurement-decision-api](https://github.com/mizcausevic-dev/procurement-decision-api)** — every Decision Card can be paired with a signature so downstream policy bundles can prove provenance.\n- **[aeo-graph-explorer-rs](https://github.com/mizcausevic-dev/aeo-graph-explorer-rs)** — same canonical-hash convention means the explorer's `content_hash` field is what this crate signs.\n- **[incident-correlation-rs](https://github.com/mizcausevic-dev/incident-correlation-rs)** — if an `IncidentCard` flags \"we don't trust this vendor's AEO anymore\", removing the vendor's `key_url` from the verifier is one atomic update away.\n\n---\n\n## Algorithm note\n\nv0.1 is ed25519-only. The algorithm field is included on every attestation so a future v0.2 can add (e.g.) ECDSA-P256 without breaking existing verifiers. Unknown algorithms fail closed.\n\n---\n\n## Bench\n\n```bash\ncargo bench\n```\n\nBundled bench measures `sign` and `verify` separately so you can spot regressions in either path.\n\n---\n\n## Tests\n\n```bash\ncargo test --all-targets\ncargo test --doc\ncargo clippy --all-targets -- -Dwarnings\ncargo fmt --all -- --check\n```\n\nCI matrix: `stable`, `beta`, `1.86.0` (MSRV).\n\n---\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmizcausevic-dev%2Fhash-attestation-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmizcausevic-dev%2Fhash-attestation-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmizcausevic-dev%2Fhash-attestation-rs/lists"}