Files
attesto-go/provenance_parity_test.go
T
CodexandClaude Opus 5 f80b28c3b5 feat(attesto3): register the REVIEW-02 disclosure v2 and ZK range domains
Sprint 1 recovery, first item. §5.2 of REVIEW-02 adds three domains the registry
did not carry: attesto.disclosure.v2, attesto.zk.range.v1.statement and
attesto.zk.range.v1.transcript.

They are not in the attesto.provenance.v1. namespace, and that is deliberate:
disclosure v2 and the ZK range protocol are separate protocols with their own
versions, so a preimage space is named after the protocol that owns it rather
than the one it happens to travel with.

That namespace difference meant the parity contract could not see them at all —
its pattern matched attesto.provenance.v1.* only, so three new domains would have
been silently unguarded. The pattern now names each protocol explicitly rather
than loosening to a prefix wildcard: a looser first attempt also matched prose
that mentions a namespace without a terminal segment and reported it as an
unknown domain.

All four registry locations updated together with the golden vector, and all
three SDK parity suites plus the contract are green. The Go failure message was
also corrected: it printed "rust=21 go=21" while failing on a third hardcoded
expectation it never named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 10:01:24 +02:00

396 lines
12 KiB
Go

package attesto
// Go parity against the Rust-normative provenance corpus.
//
// Rust (edge/) produced golden-vectors/provenance-v0.1-dev/. Go conforms iff it
// reproduces every "valid" vector byte-for-byte and refuses every "invalid" one.
// A corpus of only positive cases would prove the implementations can agree,
// not that either can refuse.
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func provenanceVectorDir(t *testing.T) string {
t.Helper()
dir := filepath.Join("..", "..", "golden-vectors", "provenance-v0.1-dev")
if _, err := os.Stat(dir); err != nil {
t.Fatalf("provenance vectors missing at %s: %v", dir, err)
}
return dir
}
func loadProvenanceVector(t *testing.T, name string) map[string]any {
t.Helper()
path := filepath.Join(provenanceVectorDir(t), name+".json")
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
var vector map[string]any
if err := json.Unmarshal(raw, &vector); err != nil {
t.Fatalf("parse %s: %v", path, err)
}
return vector
}
func TestProvenanceCorpusIsPresentAndTyped(t *testing.T) {
entries, err := filepath.Glob(filepath.Join(provenanceVectorDir(t), "*.json"))
if err != nil || len(entries) == 0 {
t.Fatalf("no provenance vectors: %v", err)
}
invalid := 0
for _, entry := range entries {
raw, err := os.ReadFile(entry)
if err != nil {
t.Fatalf("read %s: %v", entry, err)
}
var vector map[string]any
if err := json.Unmarshal(raw, &vector); err != nil {
t.Fatalf("parse %s: %v", entry, err)
}
if vector["protocol"] != ProvenanceProtocol {
t.Fatalf("%s: wrong protocol %v", entry, vector["protocol"])
}
switch vector["expectation"] {
case "valid":
case "invalid":
invalid++
default:
t.Fatalf("%s: bad expectation %v", entry, vector["expectation"])
}
}
if invalid < 5 {
t.Fatalf("corpus carries only %d negative vectors", invalid)
}
}
func TestProvenancePinnedRandomizerReproducesRustDigest(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-commitment-valid")
digest, err := ProvenanceCommitmentDigest(
vector["domain"].(string),
vector["value"],
vector["randomizer"].(string),
)
if err != nil {
t.Fatalf("commit: %v", err)
}
if digest != vector["expected_digest"].(string) {
t.Fatalf("digest mismatch:\n got %s\nwant %s", digest, vector["expected_digest"])
}
ok, err := VerifyProvenanceCommitment(
vector["domain"].(string),
vector["value"],
vector["randomizer"].(string),
vector["expected_digest"].(string),
)
if err != nil || !ok {
t.Fatalf("verify failed: ok=%v err=%v", ok, err)
}
}
func TestProvenanceSameValueDifferentRandomizerIsUnlinkable(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-commitment-randomizer-diff")
seen := map[string]bool{}
for _, entry := range vector["cases"].([]any) {
testCase := entry.(map[string]any)
digest, err := ProvenanceCommitmentDigest(
vector["domain"].(string), vector["value"], testCase["randomizer"].(string),
)
if err != nil {
t.Fatalf("commit: %v", err)
}
if digest != testCase["expected_digest"].(string) {
t.Fatalf("digest mismatch: got %s want %s", digest, testCase["expected_digest"])
}
if seen[digest] {
t.Fatal("two randomizers produced the same commitment")
}
seen[digest] = true
}
}
func TestProvenanceSameValueAcrossDomainsDoesNotCollide(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-commitment-cross-domain")
seen := map[string]bool{}
for _, entry := range vector["cases"].([]any) {
testCase := entry.(map[string]any)
digest, err := ProvenanceCommitmentDigest(
testCase["domain"].(string), vector["value"], vector["randomizer"].(string),
)
if err != nil {
t.Fatalf("commit: %v", err)
}
if digest != testCase["expected_digest"].(string) {
t.Fatalf("digest mismatch: got %s want %s", digest, testCase["expected_digest"])
}
if seen[digest] {
t.Fatal("two domains produced the same commitment")
}
seen[digest] = true
}
}
func TestProvenanceUnknownDomainIsRefused(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-commitment-invalid-domain")
if _, err := ProvenanceCommitmentDigest(
vector["domain"].(string), vector["value"], vector["randomizer"].(string),
); err == nil {
t.Fatal("an unknown domain must not resolve to a fallback")
}
}
func TestProvenanceMalformedRandomizersAreRefused(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-commitment-invalid-randomizer")
for _, entry := range vector["cases"].([]any) {
testCase := entry.(map[string]any)
if _, err := ProvenanceCommitmentDigest(
vector["domain"].(string), vector["value"], testCase["randomizer"].(string),
); err == nil {
t.Fatalf("randomizer %q must be refused", testCase["randomizer"])
}
}
}
func TestProvenanceDomainRegistryMatchesRust(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-domain-registry")
domains := vector["domains"].([]any)
// Eighteen provenance domains plus the three REVIEW-02 protocols that own
// their own preimage spaces: disclosure v2 and the ZK range statement and
// transcript.
const expected = 21
if len(domains) != len(ProvenanceDomains) || len(domains) != expected {
t.Fatalf(
"registry size mismatch: vector=%d go=%d expected=%d",
len(domains), len(ProvenanceDomains), expected,
)
}
for _, domain := range domains {
if _, ok := ProvenanceDomains[domain.(string)]; !ok {
t.Fatalf("missing domain %s", domain)
}
}
for _, rejected := range vector["rejected"].([]any) {
if _, ok := ProvenanceDomains[rejected.(string)]; ok {
t.Fatalf("domain %s must not be in the registry", rejected)
}
}
}
func leafInputs(t *testing.T, raw any) []SubtreeLeafInput {
t.Helper()
encoded, err := json.Marshal(raw)
if err != nil {
t.Fatalf("marshal leaves: %v", err)
}
var leaves []SubtreeLeafInput
if err := json.Unmarshal(encoded, &leaves); err != nil {
t.Fatalf("unmarshal leaves: %v", err)
}
return leaves
}
func buildSubtree(t *testing.T, subtree string, raw any) (string, []string) {
t.Helper()
ordered, err := OrderSubtreeLeaves(leafInputs(t, raw))
if err != nil {
t.Fatalf("order %s: %v", subtree, err)
}
merkle, err := SubtreeMerkleRoot(subtree, ordered)
if err != nil {
t.Fatalf("merkle %s: %v", subtree, err)
}
root, err := SubtreeRoot(subtree, merkle, len(ordered))
if err != nil {
t.Fatalf("root %s: %v", subtree, err)
}
return root, ordered
}
func TestProvenanceCapsuleRootReproducesRustForest(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-capsule-root-valid")
expected := vector["expected"].(map[string]any)
claimsRoot, claimsOrdered := buildSubtree(t, "claims", vector["claims"])
for index, want := range expected["claims_ordered_leaves"].([]any) {
if claimsOrdered[index] != want.(string) {
t.Fatalf("claims leaf %d mismatch", index)
}
}
if claimsRoot != expected["claims_root"].(string) {
t.Fatalf("claims_root mismatch:\n got %s\nwant %s", claimsRoot, expected["claims_root"])
}
evidenceRoot, _ := buildSubtree(t, "evidence", vector["evidence"])
if evidenceRoot != expected["evidence_root"].(string) {
t.Fatalf("evidence_root mismatch")
}
policyRoot, _ := buildSubtree(t, "policy_results", vector["policy_results"])
if policyRoot != expected["policy_results_root"].(string) {
t.Fatalf("policy_results_root mismatch")
}
randomizers := vector["top_randomizers"].(map[string]any)
commitments := map[string]string{
"subject_commitment": vector["subject_commitment"].(string),
"claims_root": claimsRoot,
"evidence_root": evidenceRoot,
"policy_results_root": policyRoot,
"attestation_commitment": vector["attestation_commitment"].(string),
"vault_identity_commitment": vector["vault_identity_commitment"].(string),
}
digests := make([]string, 0, len(TopLeafRoles))
for _, role := range TopLeafRoles {
digest, err := TopLeafDigest(role, commitments[role], randomizers[role].(string))
if err != nil {
t.Fatalf("top leaf %s: %v", role, err)
}
digests = append(digests, digest)
}
for index, want := range expected["top_leaf_digests"].([]any) {
if digests[index] != want.(string) {
t.Fatalf("top leaf digest %d mismatch", index)
}
}
root, err := CapsuleRoot(digests)
if err != nil {
t.Fatalf("capsule root: %v", err)
}
if root != expected["capsule_root"].(string) {
t.Fatalf("capsule_root mismatch:\n got %s\nwant %s", root, expected["capsule_root"])
}
}
func TestProvenanceOddNodesArePromotedNotDuplicated(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-capsule-root-promoted-odd-node")
if vector["rule"] != "promote-odd-node" {
t.Fatalf("unexpected rule %v", vector["rule"])
}
seen := map[string]bool{}
for _, entry := range vector["cases"].([]any) {
testCase := entry.(map[string]any)
ordered, err := OrderSubtreeLeaves(leafInputs(t, testCase["leaves"]))
if err != nil {
t.Fatalf("order: %v", err)
}
merkle, err := SubtreeMerkleRoot("claims", ordered)
if err != nil {
t.Fatalf("merkle: %v", err)
}
if merkle != testCase["expected_merkle_root"].(string) {
t.Fatalf("merkle mismatch for %v leaves", testCase["leaf_count"])
}
if seen[merkle] {
t.Fatal("two tree sizes shared a root — odd node was duplicated")
}
seen[merkle] = true
}
}
func TestProvenanceLeafOrderIsIndependentOfCallerOrder(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-capsule-root-valid")
leaves := leafInputs(t, vector["claims"])
reversed := make([]SubtreeLeafInput, len(leaves))
for index, leaf := range leaves {
reversed[len(leaves)-1-index] = leaf
}
forward, err := OrderSubtreeLeaves(leaves)
if err != nil {
t.Fatalf("order forward: %v", err)
}
backward, err := OrderSubtreeLeaves(reversed)
if err != nil {
t.Fatalf("order reversed: %v", err)
}
for index := range forward {
if forward[index] != backward[index] {
t.Fatal("leaf ordering depended on caller order")
}
}
}
func loadProof(t *testing.T, name string) (TwoHopProof, bool) {
t.Helper()
vector := loadProvenanceVector(t, name)
encoded, err := json.Marshal(vector["proof"])
if err != nil {
t.Fatalf("marshal proof: %v", err)
}
var proof TwoHopProof
if err := json.Unmarshal(encoded, &proof); err != nil {
t.Fatalf("unmarshal proof: %v", err)
}
return proof, vector["expected_verified"].(bool)
}
func TestProvenanceValidDisclosureVerifies(t *testing.T) {
proof, expected := loadProof(t, "provenance-disclosure-valid")
ok, err := VerifyTwoHop(proof)
if err != nil {
t.Fatalf("verify: %v", err)
}
if ok != expected {
t.Fatalf("verification mismatch: got %v want %v", ok, expected)
}
}
func TestProvenanceInvalidDisclosuresAreRefused(t *testing.T) {
for _, name := range []string{
"provenance-disclosure-cross-tree-leaf",
"provenance-disclosure-invalid-inclusion",
"provenance-disclosure-foreign-leaf",
} {
proof, expected := loadProof(t, name)
if expected {
t.Fatalf("%s should be a negative vector", name)
}
ok, err := VerifyTwoHop(proof)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if ok {
t.Fatalf("%s verified but must not", name)
}
}
}
func TestProvenanceClaimLeafCannotBeReaimedAtEvidenceRoot(t *testing.T) {
valid, _ := loadProof(t, "provenance-disclosure-valid")
cross, _ := loadProof(t, "provenance-disclosure-cross-tree-leaf")
if cross.Leaf != valid.Leaf {
t.Fatal("the vector must reuse the same claim leaf")
}
if cross.Subtree != "evidence" {
t.Fatalf("expected the evidence side, got %s", cross.Subtree)
}
ok, err := VerifyTwoHop(cross)
if err != nil {
t.Fatalf("verify: %v", err)
}
if ok {
t.Fatal("a claim leaf verified against the evidence root")
}
}
func TestProvenanceCanonicalJSONMatchesRust(t *testing.T) {
vector := loadProvenanceVector(t, "provenance-canonical-json")
for _, entry := range vector["accepted"].([]any) {
testCase := entry.(map[string]any)
rendered, err := CanonicalJSON(testCase["value"])
if err != nil {
t.Fatalf("canonical: %v", err)
}
if string(rendered) != testCase["canonical_json"].(string) {
t.Fatalf("canonical mismatch:\n got %s\nwant %s", rendered, testCase["canonical_json"])
}
}
}