Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ jobs:
# nor the cross-language vectors the SDK suites reconcile against.
- run: cargo test -p contextgraph-types --features attestation

features:
name: contextgraph-types feature matrix
runs-on: ubuntu-latest
# `test` above builds with default features, and every feature this crate
# has is off by default — so until this job existed, the attestation and
# record-hashing code, and the vectors that pin their wire format, compiled
# nowhere in CI. A cryptographic surface no job builds is a surface nothing
# defends.
#
# Each combination is built on its own rather than only `--all-features`:
# the point of the split is that a consumer of one layer does not drag in
# the other's dependencies, and only a build with exactly one feature on can
# catch a `cfg` that silently relies on the other being enabled too.
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p contextgraph-types --all-features --all-targets -- -D warnings
- run: cargo test -p contextgraph-types --all-features
- run: cargo test -p contextgraph-types --features attestation
- run: cargo test -p contextgraph-types --features record-hash
- run: cargo test -p contextgraph-types --features record-attestation

msrv:
name: msrv (rust-version from Cargo.toml)
runs-on: ubuntu-latest
Expand Down
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,49 @@ text lands without a human merge.
## [Unreleased]

### Added
- **Record content addressing and record attestation, implemented (lifecycle
profile `LH1`/`LC3`;
[ADR 0017](./docs/adr/0017-record-hash-and-record-attestation.md)).** The
profile has always defined `record_hash` as `sha256:<hex>` over the RFC 8785
(JCS) canonicalization of a record with its own `record_hash` removed, and
`RecordAttestation` as a detached Ed25519 signature over it. Both were prose
and a struct: the only hashing code in the workspace was a private helper
inside the conformance suite, so the suite proved the fixtures agreed with the
suite. `contextgraph_types::record_attest` makes the rule callable —
`record_hash`, `record_hash_preimage` (the exact canonical bytes, because a
digest cannot say *where* two implementations diverged), `record_hash_of` for
a typed record, and signing and verification for the attestation.
Verification recomputes the record's hash rather than reading the stored
member, so editing a record and then rewriting its `record_hash` to match is
caught as a mismatch instead of passing.
- **`contextgraph-types` gains `record-hash` and `record-attestation`
features.** `record-hash` adds `serde_json` and `serde_json_canonicalizer`
(RFC 8785, delegated rather than hand-rolled — JCS number serialization is
ECMAScript `Number::toString`, and its exponent thresholds are exactly where
reimplementations diverge in silence). `record-attestation` adds Ed25519 on
top. Both off by default, so a frame-only consumer never pays for a JSON
canonicalizer and the crate's zero-dependency promise is untouched.
- **The record attestation signs a domain-separated message.** A frame
commitment is domain-bound by construction; a `record_hash` is a plain
SHA-256 over a JSON document that any number of unrelated systems also
compute. The signed bytes are therefore `"contextgraph/attest/1/record"`
followed by the digest's 32 raw bytes, so a signature from another layer
cannot be presented as a record attestation. Additive to `LC3`, which named
no preimage because nothing had implemented signing.
- **Golden vectors that can be reproduced and refuted (`LF1`).**
`tests/fixtures/record-hash-vectors.json` publishes the exact JCS preimage
text of every record fixture beside its hash;
`tests/fixtures/record-attestation.json` carries a real Ed25519 signature in
place of the 49 bytes of DER-shaped filler it used to carry; and
`tests/fixtures/record-attestation-key.json` publishes the test key that
signs it. `contextgraph-types/tests/record_vectors.rs` carries the same
values inline so they travel inside the published crate. The record hashes
themselves are unchanged — the library reproduces the rule the fixtures
already followed.
- **RFC 8785 conformance is checked against the RFC's own vectors.** §3.2.4's
hexadecimal byte listing, §3.2.3's property-sorting data, and Appendix B's
table of IEEE 754 bit patterns and their required ECMAScript text, including
the `-0` case and the `1e+21` / `0.000001` exponent thresholds.
- **Provenance attestation (`SPEC.md` §6.5, F6–F9;
[ADR 0010](./docs/adr/0010-provenance-attestation.md)).** A digest is
tamper-evident only to someone who already trusts whoever recorded it; the
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus
futures-util = "0.3"
sha2 = "0.10"
ed25519-dalek = "2"
clap = { version = "4", features = ["derive", "env"] }
# RFC 8785 (JCS) canonicalization, used by the record layer's `record_hash` and
# by the conformance suite's golden vectors. One pin here so the library and the
# suite that checks it can never canonicalize with two different versions.
serde_json_canonicalizer = "0.3.2"
clap ={ version = "4", features = ["derive", "env"] }
colored = "3"
libc = "0.2"
9 changes: 8 additions & 1 deletion contextgraph-conformance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,11 @@ name = "contextgraph-example-docs"
path = "src/bin/contextgraph-example-docs.rs"

[dev-dependencies]
serde_json_canonicalizer = "0.3.2"
serde_json_canonicalizer.workspace = true
# The lifecycle-profile suite checks the *library's* record hashing and record
# attestation rather than a private copy of the rule, so a fixture and
# `contextgraph_types::record_attest` can never describe two different hashes
# (profile LF3). Repeating the dependency here adds only the feature.
contextgraph-types = { path = "../contextgraph-types", version = ">=2.0.0", features = [
"record-attestation",
] }
Loading
Loading