docs consolidation
This commit is contained in:
238
docs/modules/attestor/graph-root-attestation.md
Normal file
238
docs/modules/attestor/graph-root-attestation.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# 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<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:
|
||||
|
||||
```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<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
|
||||
|
||||
```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
|
||||
Reference in New Issue
Block a user