chain

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package chain defines the canonical wire format of a spine event and the verification layers over a canonical event stream, from structural checks up to independent cryptographic proof. It is the cross-language verifiable encoding the tamper-evident spine commits to: a deterministic CBOR serialization (RFC 8949 Core Deterministic Encoding) of the event envelope, domain-separated, so an implementation in any language can reproduce the exact bytes that are hashed and signed.

The format is hybrid: a proof carries the exact canonical bytes of each event and a verifier rehashes the bytes it is given, while the canonicalization rule is also fully specified so a verifier can re-derive the bytes from the logical event and confirm they match. That removes any dependence on one language reproducing another's serializer while still letting a verifier check that carried bytes agree with their logical content.

The package layers three concerns:

  • Structure: Verifier.VerifyStream checks that a stream of canonical event bytes is well formed, in canonical form, and strictly ordered by Seq. On its own this proves shape and order, not tamper-evidence.
  • Log: Tree is an append-only RFC 6962 Merkle log over event leaf hashes, producing a head root and inclusion and consistency proofs.
  • Signature: an Ed25519 RootSigner signs the Merkle head as a COSE_Sign1 checkpoint that a RootKeyring verifies, so a root is attributable and revocable by key.

Builder composes the three: it accumulates a run's events and seals them into a SealedRun, a signed checkpoint plus every event's canonical bytes. VerifyRun checks a sealed run end to end against a keyring: the checkpoint signature, the canonical form and ordering of every event, and that the events rebuild exactly the signed root. Passing it means an independent party trusting only the signing key can rely on the whole sequence being authentic and untampered. EventProof and VerifyEventProof give the same guarantee for a single event without the rest of the run.

Use VerifyRun or VerifyEventProof for the cryptographic guarantee. VerifyStream is the structural layer they build on and is not, by itself, a tamper-evidence check.

Index

Constants

View Source
const (
	CodeNonCanonicalCBOR = "enc.non_canonical_cbor"
	CodeDuplicateMapKey  = "enc.duplicate_map_key"
	CodeIndefiniteLength = "enc.indefinite_length"
	CodeTrailingBytes    = "enc.trailing_bytes"
	CodeDecode           = "enc.decode"
	CodeEncode           = "enc.encode"
	CodeTimeRange        = "enc.time_out_of_range"
	CodeInvalidUTF8      = "enc.invalid_utf8"
)

Failure codes are stable, dotted identifiers. They are the codes a conforming verifier reports, so a rejection names which check failed rather than just "invalid". They match the published Provetrail conformance registry.

View Source
const (
	GovStart    = "dispatch.start"
	GovEnd      = "dispatch.end"
	GovRejected = "dispatch.rejected"
	GovCallKey  = "call"
)

The governance vocabulary a record carries: the lifecycle event types the execution waist emits for every governed action. An action is admitted before it runs (GovStart), then completes (GovEnd), or is refused before it runs (GovRejected); GovCallKey is the payload key holding the correlation id that pairs one invocation's events. These string values are the wire contract a producer writes and this verifier reads; an integration test asserts a producer agrees with them, so the two cannot silently drift.

View Source
const (
	CodeUnadmittedAction  = "gov.unadmitted_action"
	CodeDeniedButExecuted = "gov.admission_denied_but_executed"
)

Governance failure codes, matching the published Provetrail registry.

View Source
const (
	// OutcomeRecorded is a claimed result of a run or step.
	OutcomeRecorded = "outcome.recorded"
	// CheckRecorded is the verdict of a verification.
	CheckRecorded = "check.recorded"

	// OutcomeResultKey holds the claimed result; ResultSuccess is the positive claim
	// that must be grounded in a passing check.
	OutcomeResultKey = "result"
	// ResultSuccess is the claimed result that requires a backing passing check.
	ResultSuccess = "success"
	// CheckRefKey holds, on an outcome, the id of the check that grounds it, and on a
	// check, that check's own id.
	CheckRefKey = "check"
	// CheckPassedKey holds a check's boolean verdict.
	CheckPassedKey = "passed"
)

The ground-truth vocabulary a record carries. A run that claims an outcome records it as an OutcomeRecorded event; a verification that backs an outcome records its verdict as a CheckRecorded event. The outcome references the check that grounds it by id. These string values are the wire contract a producer writes and this verifier reads.

View Source
const (
	CodeMissingNode        = "merkle.missing_node"
	CodeInclusionInvalid   = "merkle.inclusion_invalid"
	CodeConsistencyInvalid = "merkle.consistency_invalid"
)

Merkle failure codes, matching the published Provetrail conformance registry.

View Source
const (
	CodeEmptyRecord  = "record.empty"
	CodeSizeMismatch = "record.size_mismatch"
	CodeRootMismatch = "record.root_mismatch"
	CodeRecordDecode = "record.decode"
	CodeIndexRange   = "record.index_out_of_range"
	CodeNonCanonical = "record.non_canonical"
)

Record-composition failure codes, matching the published Provetrail registry.

View Source
const (
	// RetentionStream is the reserved stream carrying tier-lifecycle events. It is an
	// ordinary spine stream - ordered, checkpointable, verifiable like any other - named
	// so it never collides with a run or resource stream.
	RetentionStream = "flynn.retention"

	// RetentionArchived records that a set of sealed payload bodies was relocated from the
	// hot tier to a colder one. Its payload names the action, the destination tier, the
	// counts of bodies and bytes, and a manifest digest committing to exactly which bodies
	// moved (see RetentionManifest).
	RetentionArchived = "retention.archived"
)

The retention vocabulary: the lifecycle events the storage tier emits when it relocates sealed history between tiers. Tiering moves where a payload body is stored, never what the log recorded - a body is addressed by its own hash and the envelope commits to that hash, so relocation changes nothing the tree signed. To keep the move from being a silent mutation, every relocation is itself an event on a reserved stream: the action that thins the hot tier is part of the same append-only, verifiable record it operates on. These string values are the wire contract a producer writes and a reader parses; an integration test asserts the producer agrees with them, so the two cannot drift.

View Source
const (
	RetentionKeyAction    = "action"
	RetentionKeyTier      = "tier"
	RetentionKeyMoved     = "moved"
	RetentionKeyHotBytes  = "hot_bytes"
	RetentionKeyWarmBytes = "warm_bytes"
	RetentionKeyManifest  = "manifest"
)

Retention payload keys. The value at RetentionKeyManifest is the checksum that makes the event tamper-evident about its own effect: it commits to the precise set of content ids the action moved, so a record cannot claim to have archived one set while having moved another.

View Source
const (
	RetentionActionArchive = "archive"
	RetentionTierWarm      = "warm"
)

The enumerable values RetentionKeyAction and RetentionKeyTier take. Archive is the hot-to-warm relocation; further actions (export to cold, verify) extend this vocabulary as those tiers land.

View Source
const (
	CodeSignerEmptyKeyID = "sign.empty_key_id"
	CodeSignerKey        = "sign.bad_key"
	CodeSign             = "sign.sign_failed"
	CodeSignatureInvalid = "sign.signature_invalid"
	CodeUnknownKey       = "sign.unknown_key"
	CodeContentType      = "sign.bad_content_type"
	CodeCheckpointDecode = "sign.checkpoint_decode"
)

Signing failure codes, matching the published Provetrail conformance registry.

View Source
const (
	CodeSnapshotDecode    = "snap.decode"
	CodeSnapshotBinding   = "snap.binding_mismatch"
	CodeSnapshotStateHash = "snap.state_hash_mismatch"
	CodeSnapshotLogShort  = "snap.log_short"
	CodeSnapshotNoSigner  = "snap.no_signer"
)

Snapshot failure codes, matching the published Provetrail conformance registry.

View Source
const (
	CodeEmptyStream     = "chain.empty"
	CodeStreamMismatch  = "chain.stream_mismatch"
	CodeNonMonotonicSeq = "chain.non_monotonic_seq"
)

Structural failure codes, matching the published Provetrail conformance registry.

View Source
const CodeNoGroundTruth = "shallow.no_ground_truth"

CodeNoGroundTruth is the ground-truth failure code, matching the published Provetrail registry.

Variables

This section is empty.

Functions

func CanonicalBytes

func CanonicalBytes(e spine.Event) ([]byte, error)

CanonicalBytes returns the deterministic CBOR encoding of e. Two events with the same logical content always produce identical bytes, regardless of Go map iteration order, because the encoder sorts map keys. These are the bytes a hash chain commits to and a proof carries.

Payload integers must be integer-typed (int, int64). A value that is a float64 holding an integral number, which is what a payload round-tripped through a JSON decoder becomes, encodes as a float and changes the bytes. Producers must preserve integer types; the property tests guard this.

func CheckpointKeyID

func CheckpointKeyID(coseBytes []byte) (string, error)

CheckpointKeyID returns the key id a standalone signed checkpoint was signed under, decoded from the signature's protected header without verifying the signature. A verifier of a durably checkpointed stream uses it to derive the self-certifying public key before checking the checkpoint with VerifyCheckpoint.

func DecodeCanonical

func DecodeCanonical(b []byte) (spine.Event, error)

DecodeCanonical strictly decodes canonical bytes back to an Event. It rejects duplicate map keys, indefinite-length items, and trailing bytes, so a byte string that decodes here is well formed CBOR with no structural ambiguity. It does NOT by itself prove the bytes are in canonical form; use VerifyCanonical for that.

func LeafHash

func LeafHash(canonical []byte) ([]byte, error)

LeafHash returns the RFC 6962 leaf hash of an event's canonical bytes. The bytes are first wrapped by LeafInput, so the hash commits to the domain-separated, length-framed preimage rather than the raw canonical bytes.

func LeafInput

func LeafInput(canonical []byte) ([]byte, error)

LeafInput returns the domain-separated preimage the hash chain commits to for one event: the domain tag, the length of the canonical bytes as a big-endian uint64, then the canonical bytes. The length prefix makes the encoding unambiguous so two different events can never share a preimage. The tamper-evident spine hashes this.

func RecordKeyID

func RecordKeyID(record []byte) (string, error)

RecordKeyID returns the key id a record's checkpoint was signed under, decoded from the signature's protected header without verifying the signature. A verifier uses it to look up (or, for a self-certifying key id, derive) the public key before checking the record with VerifyRun.

func RetentionManifest

func RetentionManifest(contentIDs []string) string

RetentionManifest is the digest committing to the exact set of content ids a retention action moved: the SHA-256 over the ids sorted and newline-delimited. Producer and verifier both compute the manifest through this one function, so the digest a record carries cannot silently drift from the bodies it claims to cover, and it is independent of the order the mover happened to visit the bodies. An empty set yields the digest of the empty string, which never appears in a stored event because a no-op archival records nothing.

func VerifyCanonical

func VerifyCanonical(b []byte) error

VerifyCanonical reports whether b is the exact canonical encoding of the event it decodes to. It decodes b strictly, re-encodes the recovered event through the canonical encoder, and compares: any difference means b carries non-canonical or extra content and is rejected. This is the re-derivation check that lets a verifier confirm carried bytes agree with their logical content.

func VerifyConsistency

func VerifyConsistency(size1, size2 uint64, root1, root2 []byte, pf [][]byte) error

VerifyConsistency reports whether pf proves that a tree of size1 with head root1 is a prefix of a tree of size2 with head root2.

func VerifyEventProof

func VerifyEventProof(proof []byte, ring *RootKeyring) (spine.Event, error)

VerifyEventProof verifies a marshalled single-event proof against the keyring and returns the event. It checks every layer and fails closed: the checkpoint signature is valid and authorized, the proof's size matches the signed size, the event is in canonical form, and the event's leaf is included under the signed root at the claimed index. It needs only the proof and the keyring, not the run.

func VerifyGovernance

func VerifyGovernance(events []spine.Event) error

VerifyGovernance checks the admission invariants over a run's events, the governance (L4) tier above the cryptographic record. It assumes the events are already authentic and in order (verify the run record first), and checks what the signed bytes then mean:

  • No action completed without a preceding admission. Every completion (end) names a call that was admitted (a start) earlier in the stream. A record carrying a completion with no admission is rejected: it claims an action ran that was never authorized.
  • No denied action also completed. No call appears as both refused (rejected) and completed (end). A record carrying both for one call is rejected: it claims an action that was denied admission nonetheless executed.

Passing it means the recorded actions are consistent with having been admitted through the governance gate: the record is not just untampered, it is governed.

func VerifyGroundTruth

func VerifyGroundTruth(events []spine.Event) error

VerifyGroundTruth checks that a run's claimed successes are grounded in real checks, the difference between a record that is signed and one that is proven. It assumes the events are already authentic and in order (verify the run record first). For every outcome that claims success, there must be a check, recorded in the same run, that the outcome names and whose verdict passed. A success with no bound check, or bound to a check that did not pass, is rejected: the record asserts an outcome with nothing standing behind it.

Outcomes that do not claim success need no backing check, so a recorded failure or a partial result is not penalized for lacking one.

func VerifyInclusion

func VerifyInclusion(index, size uint64, leafHash, root []byte, pf [][]byte) error

VerifyInclusion reports whether pf proves that leafHash is the leaf at index in a tree of the given size whose head is root. It is the third-party check: a verifier needs only the leaf hash, the signed root, and the proof, not the whole log.

func VerifyRun

func VerifyRun(record []byte, ring *RootKeyring) ([]spine.Event, error)

VerifyRun verifies a marshalled sealed run against the keyring and returns its events in order. It checks every layer and fails closed on the first fault:

  • the checkpoint signature is valid and from an authorized key,
  • the event count equals the signed size,
  • every event is in canonical form and the stream is strictly ordered,
  • the events rebuild exactly the signed Merkle root.

Passing it means an independent party, trusting only the signing key, can rely on the whole event sequence being authentic and untampered.

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder accumulates a run's events as canonical encodings and a Merkle log over them, so the run can be sealed into a signed, independently verifiable record. It composes the canonical encoding, the structural ordering, and the tamper-evident log into one producer. A Builder is not safe for concurrent use.

func NewBuilder

func NewBuilder(origin string) *Builder

NewBuilder starts a record builder for a run identified by origin.

func (*Builder) Add

func (b *Builder) Add(e spine.Event) error

Add canonicalizes e, appends it to the log as the next leaf, and retains the canonical bytes for the sealed record.

func (*Builder) Seal

func (b *Builder) Seal(signer RootSigner) (*SealedRun, error)

Seal signs the current Merkle head and returns the sealed run: the signed checkpoint plus every event's canonical bytes, which together are sufficient for full verification and for extracting a single-event proof. An empty run is refused so a record always attests at least one event.

func (*Builder) SealAndReset

func (b *Builder) SealAndReset(signer RootSigner) (*SealedRun, error)

SealAndReset seals the current run and resets the builder to an empty run under the same origin. This is the rotation point for a long-lived stream: everything a run accumulates (each event's canonical bytes plus the full Merkle node set) moves into the returned immutable record and the builder starts fresh, so retention is bounded per segment instead of growing for the stream's life. Each sealed segment verifies on its own; Seq continuity across segments ties them together.

type Checkpoint

type Checkpoint struct {
	Origin   string
	Size     uint64
	RootHash []byte
}

Checkpoint is a commitment to the log at a point in time: the head root over a given number of leaves, scoped to an origin that names the log. It is the value a signer signs to make the root attributable and non-repudiable. The signing layer is added separately; on its own a Checkpoint is an unauthenticated root.

func VerifyCheckpoint

func VerifyCheckpoint(coseBytes []byte, ring *RootKeyring) (Checkpoint, error)

VerifyCheckpoint verifies a COSE_Sign1 signed checkpoint against the keyring and returns the checkpoint it attests. It fails closed: an unknown key, a wrong content type, a bad signature, an algorithm other than the expected one, or a payload that does not decode are all rejected. The returned checkpoint comes from the signed payload, never from any unsigned carried copy.

func VerifyConsistencyProof

func VerifyConsistencyProof(proof []byte, ring *RootKeyring) (before, after Checkpoint, err error)

VerifyConsistencyProof verifies a marshalled consistency proof against the keyring and returns the two checkpoints it connects, earlier first. It checks every layer and fails closed: the proof is in canonical form, both checkpoints are validly signed by an authorized key, and the consistency path proves the earlier signed root is a prefix of the later signed root. Passing it means an independent party, trusting only the signing key, can rely on the log having only appended between the two checkpoints.

type CheckpointStore

type CheckpointStore interface {
	SaveCheckpoint(ctx context.Context, stream string, size uint64, cose []byte) error
	LatestCheckpoint(ctx context.Context, stream string) (size uint64, cose []byte, ok bool, err error)
}

CheckpointStore persists and recovers a stream's signed tree heads. A durable log records its authenticated length as it grows and recovers it after a restart.

type ConsistencyProof

type ConsistencyProof struct {
	Before []byte   `cbor:"before"`
	After  []byte   `cbor:"after"`
	Proof  [][]byte `cbor:"proof"`
}

ConsistencyProof is a standalone proof that an append-only log only ever grew between two signed checkpoints: the earlier checkpoint's tree is a prefix of the later one's, with no event rewritten or removed in between. It carries both signed checkpoints and the RFC 6962 consistency path that connects their roots. It is the portable artifact a third party checks to confirm a log was not forked or rewritten across two points in time, without needing the events themselves.

func (*ConsistencyProof) Marshal

func (p *ConsistencyProof) Marshal() ([]byte, error)

Marshal encodes the proof as a portable, deterministic CBOR artifact.

type DurableRecorder

type DurableRecorder struct {
	spine.Log
	// contains filtered or unexported fields
}

DurableRecorder wraps a spine.Log and maintains a per-stream RFC 6962 Merkle log whose proof nodes are paged to durable tiles, signing a checkpoint every so often. Unlike a RecordingLog, which accumulates a whole run in memory and seals it once, a DurableRecorder is built for a long-lived stream: its resident state is the append frontier plus the tiles not yet flushed, and its history is recovered after a restart from the latest checkpoint plus the events after it. It is itself a spine.Log, so it drops in wherever a Log is expected.

Recording never fails an Append: the wrapped append is the source of truth, so a recording or checkpoint error is routed to an error handler and the event is still returned, exactly as the RecordingLog keeps producing events when its integrity layer stumbles. A stream is caught up from the log after a transient failure, and an explicit Checkpoint (used to seal a stream's tip) does surface its error.

func NewDurableRecorder

func NewDurableRecorder(inner spine.Log, nodes func(stream string) FlushNodeStore, ckpts CheckpointStore, signer RootSigner, originFor OriginFunc, every int) *DurableRecorder

NewDurableRecorder wraps inner. nodes provides the durable node store for a stream, ckpts persists its checkpoints, signer signs them, and originFor maps a stream to its checkpoint origin (nil uses the stream id). A checkpoint is written every `every` recorded events; a non-positive `every` checkpoints only on an explicit Checkpoint call.

func (*DurableRecorder) Append

Append delegates to the wrapped log, then records the assigned event's leaf into its stream's durable Merkle log, checkpointing when the cadence is reached. Recording is best effort: a failure never fails the append, it is routed to the error handler.

func (*DurableRecorder) Checkpoint

func (r *DurableRecorder) Checkpoint(ctx context.Context, stream string) error

Checkpoint forces a signed checkpoint for a stream at its current recorded length, flushing the tiles first. It is how a caller seals the tail of a stream (at the end of a run, or before shutdown) so the log's full length is durably attested. A stream not yet touched in this process is loaded and caught up to its tip first.

func (*DurableRecorder) CheckpointAll

func (r *DurableRecorder) CheckpointAll(ctx context.Context) error

CheckpointAll seals the tip of every stream recorded in this process, so a graceful shutdown persists the full length of each live stream. It returns the first error and keeps going, so one stream's failure does not skip the rest. It snapshots the resident handles under the global lock, then checkpoints each under its own lock, so the sweep holds neither lock across another stream's I/O.

func (*DurableRecorder) OnError

func (r *DurableRecorder) OnError(fn func(error)) *DurableRecorder

OnError sets the handler called when best-effort recording or an automatic checkpoint fails during Append. It returns the recorder for chaining. With no handler set, such an error is dropped (the stream still catches up from the log later).

func (*DurableRecorder) WithMaxResident

func (r *DurableRecorder) WithMaxResident(n int) *DurableRecorder

WithMaxResident overrides how many streams stay resident before the recorder evicts the excess. A non-positive n disables eviction (every touched stream stays resident). It returns the recorder for chaining.

type Ed25519RootSigner

type Ed25519RootSigner struct {
	// contains filtered or unexported fields
}

Ed25519RootSigner signs checkpoints with an Ed25519 private key as COSE_Sign1. It is the default signer: the standard-library key type, no external signing service.

func NewEd25519RootSigner

func NewEd25519RootSigner(keyID string, priv ed25519.PrivateKey) (*Ed25519RootSigner, error)

NewEd25519RootSigner builds a signer over an existing Ed25519 private key. It refuses an empty key id or a malformed key so a signer is never silently unable to sign or unattributable.

func (*Ed25519RootSigner) KeyID

func (s *Ed25519RootSigner) KeyID() string

KeyID identifies the signing key.

func (*Ed25519RootSigner) SignCheckpoint

func (s *Ed25519RootSigner) SignCheckpoint(c Checkpoint) (SignedCheckpoint, error)

SignCheckpoint signs c. The algorithm, content type, and key id go in the protected header, so all three are covered by the signature and cannot be altered without detection. Ed25519 signing is deterministic, so no randomness is needed.

func (*Ed25519RootSigner) SignSnapshotClaim

func (s *Ed25519RootSigner) SignSnapshotClaim(c SnapshotClaim) ([]byte, error)

SignSnapshotClaim signs a snapshot claim as COSE_Sign1 under the snapshot content type, so the signature binds the claim's meaning as well as its bytes.

type EventProof

type EventProof struct {
	Canonical  []byte   `cbor:"canonical"`
	Index      uint64   `cbor:"index"`
	Size       uint64   `cbor:"size"`
	Inclusion  [][]byte `cbor:"inclusion"`
	Checkpoint []byte   `cbor:"checkpoint"`
}

EventProof is a standalone proof that one event is included in a signed run: the event's canonical bytes, its position, the inclusion path, and the signed checkpoint whose root the path reconstructs. It is the portable artifact a third party checks to confirm a single action happened under a signed log, without the rest of the run.

func (*EventProof) Marshal

func (p *EventProof) Marshal() ([]byte, error)

Marshal encodes the proof as a portable, deterministic CBOR artifact.

type FlushNodeStore

type FlushNodeStore interface {
	NodeStore
	Flush() error
}

FlushNodeStore is a NodeStore that can force its buffered nodes to durable storage. A durable log flushes before it signs a checkpoint, so the checkpoint's size is always fully backed by persisted proof nodes.

type NodeStore

type NodeStore interface {
	// Node returns the hash at (level, index) and whether it is present.
	Node(level uint, index uint64) ([]byte, bool, error)
	// PutNode records the hash at (level, index).
	PutNode(level uint, index uint64, hash []byte) error
}

NodeStore holds the Merkle node hashes keyed by (level, index): Append writes each completed node and proof assembly reads them back. Separating node storage behind this interface is what lets one ever-growing log keep only the O(log n) append frontier in memory (the compact range) while proofs are assembled from nodes that may live packed in a tile, on disk, or in a colder tier. A durable store implements it to persist a long-lived stream's proof material without holding it all in memory.

A node is written once and never changes, so a stored hash is stable and a Node result may be retained without copying.

type OriginFunc

type OriginFunc func(stream string) string

OriginFunc maps a stream id to the origin a sealed record is scoped to. A nil func uses the stream id itself.

type RecordingLog

type RecordingLog struct {
	spine.Log
	// contains filtered or unexported fields
}

RecordingLog wraps a spine.Log and records every appended event into a per-stream verifiable chain, so a stream can be sealed into a signed run record without changing how events are produced. It is itself a spine.Log, so it drops in wherever a Log is expected.

Recording never fails an Append: a recording error is retained and surfaced at Seal. A run is therefore never broken by the integrity layer, but a stream whose recording failed (a non-canonical event) cannot be sealed, so a sealed record always covers the stream's complete, canonical event sequence.

func NewRecordingLog

func NewRecordingLog(inner spine.Log, originFor OriginFunc) *RecordingLog

NewRecordingLog wraps inner. originFor maps a stream to its record origin; if nil, the stream id is used.

func (*RecordingLog) Append

func (r *RecordingLog) Append(ctx context.Context, in spine.AppendInput) (spine.Event, error)

Append delegates to the wrapped log, then records the assigned event into its stream's chain. The append result is returned unchanged; a recording failure is kept for Seal rather than propagated, so producing events never depends on the integrity layer succeeding.

func (*RecordingLog) Seal

func (r *RecordingLog) Seal(stream string, signer RootSigner) (*SealedRun, error)

Seal signs and returns the sealed record for a stream as recorded so far. It fails if the stream was never recorded or if recording any of its events failed, so a sealed record always covers the stream's full, canonical event sequence. The returned record is an immutable snapshot: further appends to the stream do not change it.

func (*RecordingLog) SealAndReset

func (r *RecordingLog) SealAndReset(stream string, signer RootSigner) (*SealedRun, error)

SealAndReset seals the stream's record and resets its recorder, so a long-lived stream can rotate into bounded segments: the returned record owns everything recorded so far, and later appends accumulate into a fresh run under the same origin. It refuses under the same conditions as Seal.

type RetentionRecord

type RetentionRecord struct {
	Action    string
	Tier      string
	Moved     int64
	HotBytes  int64
	WarmBytes int64
	Manifest  string
}

RetentionRecord is the decoded content of a RetentionArchived event: what a reader learns about one tiering action without re-parsing raw payload keys. Moved is the number of bodies relocated; HotBytes is the original (uncompressed) hot bytes reclaimed; WarmBytes is the compressed bytes the warm tier gained; Manifest is the RetentionManifest digest of the moved set.

func DecodeRetention

func DecodeRetention(e spine.Event) (RetentionRecord, bool)

DecodeRetention reads a RetentionArchived event's payload into a RetentionRecord, returning ok=false for any other event type. Integer fields are read tolerantly across the integer and float shapes a CBOR or JSON round trip can produce (see intField), so a record decodes whether its events came straight from the canonical bytes or back through a store that serialized them as JSON.

type RootKeyring

type RootKeyring struct {
	// contains filtered or unexported fields
}

RootKeyring is the set of public keys authorized to sign checkpoints, keyed by key id. Only a signature from a key in the ring counts, so revoking a signer is removing its key. It is read-only after construction in normal use.

func NewRootKeyring

func NewRootKeyring() *RootKeyring

NewRootKeyring builds an empty keyring.

func (*RootKeyring) Add

func (k *RootKeyring) Add(keyID string, pub ed25519.PublicKey) error

Add registers an authorized public key under keyID. A later Add for the same id replaces the key, which is a key rotation. It refuses an empty id or a malformed key so the ring never holds one that can never verify.

type RootSigner

type RootSigner interface {
	// SignCheckpoint signs c and returns it with a detached COSE signature.
	SignCheckpoint(c Checkpoint) (SignedCheckpoint, error)
	// KeyID identifies the signing key in a verifier's keyring.
	KeyID() string
}

RootSigner produces a signed checkpoint over the Merkle head. The method (a local Ed25519 key, a KMS, a hardware token) is an implementation detail behind this port, so a verifier checks a signature without depending on how it was produced.

type SealedRun

type SealedRun struct {
	// contains filtered or unexported fields
}

SealedRun is an immutable, signed record of a run: the COSE-signed checkpoint over the Merkle head plus the canonical bytes of every event. It is a snapshot taken at seal time, so further appends to the builder it came from do not affect it. It can be marshalled to a portable artifact, verified in full, or used to produce a standalone proof for one event.

func (*SealedRun) EventProof

func (s *SealedRun) EventProof(index uint64) (*EventProof, error)

EventProof extracts a standalone proof that the event at index is in the sealed run, verifiable on its own with VerifyEventProof and without the other events.

func (*SealedRun) Marshal

func (s *SealedRun) Marshal() ([]byte, error)

Marshal encodes the sealed run as a portable, deterministic CBOR artifact a third party can verify with VerifyRun.

type SignedCheckpoint

type SignedCheckpoint struct {
	Checkpoint Checkpoint
	KeyID      string
	COSE       []byte
}

SignedCheckpoint is a checkpoint plus a COSE_Sign1 signature over its canonical encoding, identified by the signing key. COSE holds the tagged COSE_Sign1 message, which is the authoritative, self-describing artifact a verifier checks; the parsed Checkpoint is carried for convenience and is never trusted over the signed payload.

type SnapshotClaim

type SnapshotClaim struct {
	Stream     string
	Seq        int64
	StateHash  []byte
	Checkpoint Checkpoint
}

SnapshotClaim binds a projected state to the exact log prefix it derives from: the SHA-256 of the snapshot payload, the stream and sequence the projection is current as of, and the stream's Merkle checkpoint over that prefix. Signed, it makes a snapshot exactly as trustworthy as the log it summarizes - a verifier checks the signature and state hash to restore fast, and can always re-fold the prefix and compare against the checkpoint to audit in depth.

func VerifySnapshotClaim

func VerifySnapshotClaim(coseBytes []byte, ring *RootKeyring) (SnapshotClaim, error)

VerifySnapshotClaim verifies a COSE_Sign1 signed snapshot claim against the keyring and returns the claim it attests. It fails closed exactly like VerifyCheckpoint, and additionally rejects a checkpoint signature presented as a snapshot (the content types differ).

type SnapshotSealer

type SnapshotSealer struct {
	// contains filtered or unexported fields
}

SnapshotSealer seals projection snapshots into verified, checkpoint-bound artifacts and opens (verifies) them on read. It implements spine.SnapshotCodec, so a store that snapshots through it never persists or trusts an unsigned state blob: Seal binds the state to the stream's Merkle checkpoint under the instance key, and Open rejects anything the keyring does not vouch for, so a rebuild falls back to a full fold rather than restoring unverified state.

func NewSnapshotSealer

func NewSnapshotSealer(signer SnapshotSigner, ring *RootKeyring, originFor OriginFunc) (*SnapshotSealer, error)

NewSnapshotSealer builds a sealer verifying against ring and signing with signer. A nil signer makes a verify-only sealer (Open works, Seal refuses), for a process that reads snapshots but holds no key. originFor maps a stream to its checkpoint origin; if nil, the stream id is used.

func (*SnapshotSealer) Open

Open verifies a sealed snapshot and returns it with the inner projection payload. It fails closed: a payload that is not a sealed envelope, a signature the keyring does not vouch for, a claim bound to a different stream or seq, or a state whose hash does not match the signed claim are all rejected - the caller falls back to a full fold, which is only slower, never wrong.

func (*SnapshotSealer) Seal

Seal wraps a projection snapshot in a signed claim binding it to the stream's Merkle checkpoint at the snapshot's Seq. It folds the stream's canonical events up to Seq into the tree, which is linear in the prefix - a write-side cost paid once per snapshot, amortized by the cadence, and bounded by segment rotation; the read side is what stops growing with history.

type SnapshotSigner

type SnapshotSigner interface {
	// SignSnapshotClaim signs c and returns the tagged COSE_Sign1 message.
	SignSnapshotClaim(c SnapshotClaim) ([]byte, error)
	// KeyID identifies the signing key in a verifier's keyring.
	KeyID() string
}

SnapshotSigner produces a signed snapshot claim. Like RootSigner it is a port: the signing method is an implementation detail, so a verifier checks a snapshot without depending on how it was signed. Ed25519RootSigner implements both.

type Tree

type Tree struct {
	// contains filtered or unexported fields
}

Tree is an append-only RFC 6962 Merkle log over event leaf hashes. It computes the signed-tree-head root and produces inclusion and consistency proofs. The hashing and proof verification are delegated to the transparency-dev/merkle library; the perfect-subtree node hashes a proof needs are kept in a NodeStore, so the same tree can hold them in memory or page them into tiles. A Tree is not safe for concurrent mutation.

func LoadTree

func LoadTree(store NodeStore, size uint64) (*Tree, error)

LoadTree reopens a Merkle log of the given size from a store that already holds its nodes. It rebuilds the append frontier (the roots of the perfect subtrees covering the leaves) from the stored nodes, so a durable log resumes appending and proving after a restart without folding the whole history back into memory. The size is the authenticated leaf count, taken from the log's latest signed checkpoint.

func NewTree

func NewTree() *Tree

NewTree returns an empty Merkle log whose nodes are held in memory.

func NewTreeWithStore

func NewTreeWithStore(store NodeStore) *Tree

NewTreeWithStore returns an empty Merkle log backed by store. It is the shared constructor: NewTree pairs it with the in-memory map, and a durable log pairs it with a tiled, storage-backed store so proof nodes need not all be resident.

func (*Tree) Append

func (t *Tree) Append(canonical []byte) error

Append adds an event's canonical bytes as the next leaf. It records the leaf and every internal node the append completes into the store, so later proofs can be assembled without recomputing the tree.

func (*Tree) ConsistencyProof

func (t *Tree) ConsistencyProof(size uint64) ([][]byte, error)

ConsistencyProof returns the proof that the tree at the earlier size is a prefix of the tree at its current size, which is what makes the log verifiably append-only.

func (*Tree) InclusionProof

func (t *Tree) InclusionProof(index uint64) ([][]byte, error)

InclusionProof returns the audit path proving the leaf at index is included in the tree at its current size.

func (*Tree) Root

func (t *Tree) Root() ([]byte, error)

Root returns the current Merkle tree head. An empty tree returns the hasher's empty root.

func (*Tree) Size

func (t *Tree) Size() uint64

Size is the number of leaves appended so far.

type Verifier

type Verifier struct{}

Verifier performs structural verification of a canonical event stream: each event is well formed and in canonical form, all events belong to one stream, and Seq strictly increases. It deliberately does NOT prove tamper-evidence: that is composed at the record layer, where VerifyRun and VerifyEventProof pair this structural check with checkpoint-signature and Merkle-root verification. A Verifier is stateless and safe for concurrent use.

func NewVerifier

func NewVerifier() *Verifier

NewVerifier returns a structural verifier.

func (*Verifier) VerifyStream

func (v *Verifier) VerifyStream(canonicalEvents [][]byte) ([]spine.Event, error)

VerifyStream checks a contiguous slice of canonical event byte blobs in Seq order and returns the decoded events. It fails closed: the first structural fault stops verification and returns a coded error, so a partially valid stream is never reported as valid. An empty stream is an error rather than a vacuous pass.

Directories

Path Synopsis
Package conformance defines the Provetrail conformance vector suite: a fixed set of artifacts a conforming verifier must accept, and malformed ones it must reject with a specific failure code.
Package conformance defines the Provetrail conformance vector suite: a fixed set of artifacts a conforming verifier must accept, and malformed ones it must reject with a specific failure code.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL