Skip to content

Commit 924bccd

Browse files
author
Achille Wasque
authored
feat(gitlawb-attest): External Attestation v1 for ref-update certs (#20)
Adds an optional `attestations` field on ref-update certs plus a small crate to sign and verify them. An envelope with no attestations serializes to identical bytes as a bare cert, so nothing on the wire changes for nodes that don't opt in. Each attestation carries a `type` discriminator (`covenant/exec/v1`, `slsa/v1.0`, `sigstore/dsse/v1`, ...), an opaque payload, a `cert_hash` that binds it to one specific cert, a `did:key` signer, and a base64url ed25519 signature over a domain-separated JCS-encoded input. ## Wire shape cert_hash = SHA-256(JCS(cert_body \ {signatures, attestations})) encoded as lowercase hex. signing_input = b"gitlawb-attest-sig/v1\n" || JCS({type, payload, cert_hash}) sig = base64url-no-pad(Ed25519(signing_input)) The cert hash is reproducible across implementations because JCS pins key order, number formatting, and string escaping (RFC 8785). Lowercase hex is required on the wire: the JCS signing input includes `cert_hash` verbatim, so accepting mixed case would force every signer and verifier to normalize before computing the signing bytes — an easy spec rule to miss across language ports. The domain tag lives in the byte stream, not in the canonical JSON, so an attestation cannot be confused with a same-shape JCS document signed for a different protocol that uses the same Ed25519 key. Signers are `did:key` over Ed25519 with the multibase base58btc (`z`) prefix. Non-base58btc encodings of the same public key are rejected on verify so allowlists and signer-pinning policies stay consistent across peers. ## Verification `Registry` maps type to verifier. `Policy::AcceptKnown` (default) lets unknown types pass without trust so adoption stays incremental. `Policy::RequireAll` enforces a per-repo allowlist but stays lenient on unknown types in the batch — anyone can attach an attestation, so short-circuiting on an unrelated `attacker/spam/v1` would be a DoS vector. `Policy::RejectUnknown` is strict. `AttestedRefUpdateCert::verify_attestations(&Registry)` is the one-call convenience over `registry.verify_all(&env.attestations, env.cert_hash()?)`. ## Canonical test vectors Ed25519, JCS, and SHA-256 are all deterministic, so a fixed seed plus a fixed cert body produce exactly the same `cert_hash`, `signer` DID, and `sig` across implementations. `tests/canonical_vectors.rs` pins these values so a port to Go, JS, Python, or any other language has a concrete oracle to check against without reading Rust. If a vector drifts, that is a wire-protocol break. ## Tests 42 pass: 33 unit + 4 cross-crate integration against the real `gitlawb_core::cert::RefUpdateCert` + 4 canonical vector pins + 1 rustdoc quick-start doctest. `cargo fmt --all -- --check` clean. `cargo clippy --workspace --all-targets -- -D warnings` clean. `cargo test --workspace` green; the new crate breaks no existing suite. ## Not in this PR - Node integration: storage, GraphQL, register flow. - UI surfacing. - Trust-score weighting policy for typed attestations. - Rollout ordering: old nodes silently drop the new `attestations` field (`RefUpdateBody` does not set `deny_unknown_fields`); signers and verifiers roll out independently. - wasm / no_std: not targeted; ed25519-dalek and serde_json are std-only. ## Reference implementation End-to-end demo at open-covenant/covenant@64ec780, `agent-os/examples/gitlawb-attest-demo/` (`covenant/exec/v1`). Closes #7.
1 parent a9f208a commit 924bccd

10 files changed

Lines changed: 2218 additions & 299 deletions

File tree

Cargo.lock

Lines changed: 355 additions & 299 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"crates/gitlawb-node",
66
"crates/gl",
77
"crates/git-remote-gitlawb",
8+
"crates/gitlawb-attest",
89
]
910

1011
[workspace.package]
@@ -20,6 +21,7 @@ tokio = { version = "1", features = ["full"] }
2021
# serialization
2122
serde = { version = "1", features = ["derive"] }
2223
serde_json = "1"
24+
serde_jcs = "0.2"
2325
# errors
2426
thiserror = "2"
2527
anyhow = "1"

crates/gitlawb-attest/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "gitlawb-attest"
3+
description = "External Attestation v1: pluggable provenance attachments for gitlawb ref-update certs"
4+
version.workspace = true
5+
edition.workspace = true
6+
license.workspace = true
7+
8+
[dependencies]
9+
serde = { workspace = true }
10+
serde_json = { workspace = true }
11+
serde_jcs = { workspace = true }
12+
thiserror = { workspace = true }
13+
ed25519-dalek = { workspace = true }
14+
sha2 = { workspace = true }
15+
hex = { workspace = true }
16+
base64 = { workspace = true }
17+
multibase = { workspace = true }
18+
19+
[dev-dependencies]
20+
gitlawb-core = { path = "../gitlawb-core" }
21+
rand = { workspace = true }

0 commit comments

Comments
 (0)