# Graph Root Attestation ## Overview Graph root attestation is a mechanism for creating cryptographically signed, content-addressed proofs of graph state. It enables offline verification that replayed graphs match the original attested state by computing a Merkle root from sorted node/edge IDs and input digests, then wrapping it in a DSSE envelope with an in-toto statement. ## Purpose Graph root attestations solve the problem of proving graph authenticity without reconstructing the entire proof chain. They enable: - **Offline Verification**: Download an attestation, recompute the root from stored nodes/edges, compare - **Audit Snapshots**: Point-in-time proof of graph state for compliance - **Evidence Linking**: Reference attested roots (not transient IDs) in evidence chains - **Transparency**: Optional Rekor publication for public auditability ## Architecture ### Components ``` ┌─────────────────────────────────────────────────────────────────┐ │ GraphRootAttestor │ ├─────────────────────────────────────────────────────────────────┤ │ AttestAsync(request) → GraphRootAttestationResult │ │ VerifyAsync(envelope, nodes, edges) → VerificationResult │ └─────────────────┬───────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ IMerkleRootComputer │ ├─────────────────────────────────────────────────────────────────┤ │ ComputeRoot(leaves) → byte[] │ │ Algorithm → "sha256" │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ EnvelopeSignatureService │ ├─────────────────────────────────────────────────────────────────┤ │ Sign(payload, key) → EnvelopeSignature │ └─────────────────────────────────────────────────────────────────┘ ``` ### Data Flow 1. **Request** → Sorted node/edge IDs + input digests 2. **Merkle Tree** → Compute SHA-256 root from leaves 3. **In-Toto Statement** → Build attestation with predicate 4. **Canonicalize** → JCS (RFC 8785) with version marker 5. **Sign** → DSSE envelope with Ed25519/ECDSA 6. **Store/Publish** → CAS storage + optional Rekor ## Graph Types The `GraphType` enum identifies what kind of graph is being attested: | GraphType | Description | |-----------|-------------| | `Unknown` | Unspecified graph type | | `CallGraph` | Function/method call relationships | | `DependencyGraph` | Package/library dependencies (SBOM) | | `SbomGraph` | SBOM component graph | | `EvidenceGraph` | Linked evidence records | | `PolicyGraph` | Policy decision trees | | `ProofSpine` | Proof chain spine segments | | `ReachabilityGraph` | Code reachability analysis | | `VexLinkageGraph` | VEX statement linkages | | `Custom` | Application-specific graph | ## Models ### GraphRootAttestationRequest Input to the attestation service: ```csharp public sealed record GraphRootAttestationRequest { public required GraphType GraphType { get; init; } public required IReadOnlyList NodeIds { get; init; } public required IReadOnlyList EdgeIds { get; init; } public required string PolicyDigest { get; init; } public required string FeedsDigest { get; init; } public required string ToolchainDigest { get; init; } public required string ParamsDigest { get; init; } public required string ArtifactDigest { get; init; } public IReadOnlyList EvidenceIds { get; init; } = []; public bool PublishToRekor { get; init; } = false; public string? SigningKeyId { get; init; } } ``` ### GraphRootAttestation (In-Toto Statement) The attestation follows the in-toto Statement/v1 format: ```json { "_type": "https://in-toto.io/Statement/v1", "subject": [ { "name": "sha256:abc123...", "digest": { "sha256": "abc123..." } }, { "name": "sha256:artifact...", "digest": { "sha256": "artifact..." } } ], "predicateType": "https://stella-ops.org/attestation/graph-root/v1", "predicate": { "graphType": "DependencyGraph", "rootHash": "sha256:abc123...", "rootAlgorithm": "sha256", "nodeCount": 1247, "edgeCount": 3891, "nodeIds": ["node-a", "node-b", ...], "edgeIds": ["edge-1", "edge-2", ...], "inputs": { "policyDigest": "sha256:policy...", "feedsDigest": "sha256:feeds...", "toolchainDigest": "sha256:tools...", "paramsDigest": "sha256:params..." }, "evidenceIds": ["ev-1", "ev-2"], "canonVersion": "stella:canon:v1", "computedAt": "2025-12-26T10:30:00Z", "computedBy": "stellaops/attestor/graph-root", "computedByVersion": "1.0.0" } } ``` ## Merkle Root Computation The root is computed from leaves in this deterministic order: 1. **Sorted node IDs** (lexicographic, ordinal) 2. **Sorted edge IDs** (lexicographic, ordinal) 3. **Policy digest** 4. **Feeds digest** 5. **Toolchain digest** 6. **Params digest** Each leaf is SHA-256 hashed, then combined pairwise until a single root remains. ``` ROOT / \ H(L12) H(R12) / \ / \ H(n1) H(n2) H(e1) H(policy) ``` ## Usage ### Creating an Attestation ```csharp var services = new ServiceCollection(); services.AddGraphRootAttestation(sp => keyId => GetSigningKey(keyId)); var provider = services.BuildServiceProvider(); var attestor = provider.GetRequiredService(); var request = new GraphRootAttestationRequest { GraphType = GraphType.DependencyGraph, NodeIds = graph.Nodes.Select(n => n.Id).ToList(), EdgeIds = graph.Edges.Select(e => e.Id).ToList(), PolicyDigest = "sha256:abc...", FeedsDigest = "sha256:def...", ToolchainDigest = "sha256:ghi...", ParamsDigest = "sha256:jkl...", ArtifactDigest = imageDigest }; var result = await attestor.AttestAsync(request); Console.WriteLine($"Root: {result.RootHash}"); Console.WriteLine($"Nodes: {result.NodeCount}"); Console.WriteLine($"Edges: {result.EdgeCount}"); ``` ### Verifying an Attestation ```csharp var envelope = LoadEnvelope("attestation.dsse.json"); var nodes = LoadNodes("nodes.ndjson"); var edges = LoadEdges("edges.ndjson"); var result = await attestor.VerifyAsync(envelope, nodes, edges); if (result.IsValid) { Console.WriteLine($"✓ Verified: {result.ComputedRoot}"); Console.WriteLine($" Nodes: {result.NodeCount}"); Console.WriteLine($" Edges: {result.EdgeCount}"); } else { Console.WriteLine($"✗ Failed: {result.FailureReason}"); Console.WriteLine($" Expected: {result.ExpectedRoot}"); Console.WriteLine($" Computed: {result.ComputedRoot}"); } ``` ## Offline Verification Workflow 1. **Obtain attestation**: Download DSSE envelope from storage or transparency log 2. **Verify signature**: Check envelope signature against trusted public keys 3. **Extract predicate**: Parse `GraphRootPredicate` from the payload 4. **Fetch graph data**: Download nodes and edges by ID from CAS 5. **Recompute root**: Apply Merkle tree algorithm to node/edge IDs + input digests 6. **Compare**: Computed root must match `predicate.RootHash` ## Determinism Guarantees Graph root attestations are fully deterministic: - **Sorting**: All IDs sorted lexicographically (ordinal comparison) - **Canonicalization**: RFC 8785 JCS with `stella:canon:v1` version marker - **Hashing**: SHA-256 only - **Timestamps**: UTC ISO-8601 (not included in root computation) Same inputs always produce the same root hash, enabling replay verification. ## Security Considerations - **Signature verification**: Always verify DSSE envelope signatures before trusting attestations - **Key management**: Use short-lived signing keys; rotate regularly - **Transparency**: Publish to Rekor for tamper-evident audit trail - **Input validation**: Validate all digests are properly formatted before attestation ## Related Documentation - [DSSE Envelopes](./dsse-envelopes.md) - Envelope format and signing - [Proof Chain](./proof-chain.md) - Overall proof chain architecture - [Canonical JSON](../../modules/platform/canonical-json.md) - Canonicalization scheme