9.5 KiB
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
- Request → Sorted node/edge IDs + input digests
- Merkle Tree → Compute SHA-256 root from leaves
- In-Toto Statement → Build attestation with predicate
- Canonicalize → JCS (RFC 8785) with version marker
- Sign → DSSE envelope with Ed25519/ECDSA
- 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:
public sealed record GraphRootAttestationRequest
{
public required GraphType GraphType { get; init; }
public required IReadOnlyList<string> NodeIds { get; init; }
public required IReadOnlyList<string> 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<string> 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:
{
"_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:
- Sorted node IDs (lexicographic, ordinal)
- Sorted edge IDs (lexicographic, ordinal)
- Policy digest
- Feeds digest
- Toolchain digest
- 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
var services = new ServiceCollection();
services.AddGraphRootAttestation(sp => keyId => GetSigningKey(keyId));
var provider = services.BuildServiceProvider();
var attestor = provider.GetRequiredService<IGraphRootAttestor>();
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
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
- Obtain attestation: Download DSSE envelope from storage or transparency log
- Verify signature: Check envelope signature against trusted public keys
- Extract predicate: Parse
GraphRootPredicatefrom the payload - Fetch graph data: Download nodes and edges by ID from CAS
- Recompute root: Apply Merkle tree algorithm to node/edge IDs + input digests
- 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:v1version 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 - Envelope format and signing
- Proof Chain - Overall proof chain architecture
- Canonical JSON - Canonicalization scheme