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
- func CanonicalBytes(e spine.Event) ([]byte, error)
- func CheckpointKeyID(coseBytes []byte) (string, error)
- func DecodeCanonical(b []byte) (spine.Event, error)
- func LeafHash(canonical []byte) ([]byte, error)
- func LeafInput(canonical []byte) ([]byte, error)
- func RecordKeyID(record []byte) (string, error)
- func RetentionManifest(contentIDs []string) string
- func VerifyCanonical(b []byte) error
- func VerifyConsistency(size1, size2 uint64, root1, root2 []byte, pf [][]byte) error
- func VerifyEventProof(proof []byte, ring *RootKeyring) (spine.Event, error)
- func VerifyGovernance(events []spine.Event) error
- func VerifyGroundTruth(events []spine.Event) error
- func VerifyInclusion(index, size uint64, leafHash, root []byte, pf [][]byte) error
- func VerifyRun(record []byte, ring *RootKeyring) ([]spine.Event, error)
- type Builder
- type Checkpoint
- type CheckpointStore
- type ConsistencyProof
- type DurableRecorder
- func (r *DurableRecorder) Append(ctx context.Context, in spine.AppendInput) (spine.Event, error)
- func (r *DurableRecorder) Checkpoint(ctx context.Context, stream string) error
- func (r *DurableRecorder) CheckpointAll(ctx context.Context) error
- func (r *DurableRecorder) OnError(fn func(error)) *DurableRecorder
- func (r *DurableRecorder) WithMaxResident(n int) *DurableRecorder
- type Ed25519RootSigner
- type EventProof
- type FlushNodeStore
- type NodeStore
- type OriginFunc
- type RecordingLog
- type RetentionRecord
- type RootKeyring
- type RootSigner
- type SealedRun
- type SignedCheckpoint
- type SnapshotClaim
- type SnapshotSealer
- type SnapshotSigner
- type Tree
- type Verifier
Constants ¶
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.
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.
const ( CodeUnadmittedAction = "gov.unadmitted_action" CodeDeniedButExecuted = "gov.admission_denied_but_executed" )
Governance failure codes, matching the published Provetrail registry.
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.
const ( CodeMissingNode = "merkle.missing_node" CodeInclusionInvalid = "merkle.inclusion_invalid" CodeConsistencyInvalid = "merkle.consistency_invalid" )
Merkle failure codes, matching the published Provetrail conformance registry.
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.
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.
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.
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.
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.
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.
const ( CodeEmptyStream = "chain.empty" CodeStreamMismatch = "chain.stream_mismatch" CodeNonMonotonicSeq = "chain.non_monotonic_seq" )
Structural failure codes, matching the published Provetrail conformance registry.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
NewBuilder starts a record builder for a run identified by origin.
func (*Builder) Add ¶
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 ¶
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 ¶
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 ¶
func (r *DurableRecorder) Append(ctx context.Context, in spine.AppendInput) (spine.Event, error)
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 ¶
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 ¶
OriginFunc maps a stream id to the origin a sealed record is scoped to. A nil func uses the stream id itself.
type RecordingLog ¶
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 (*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.
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 ¶
func (ss *SnapshotSealer) Seal(ctx context.Context, log spine.Log, s spine.Snapshot) (spine.Snapshot, error)
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
InclusionProof returns the audit path proving the leaf at index is included in the tree at its current size.
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 (*Verifier) VerifyStream ¶
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.
Source Files
¶
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. |