verify

package
v0.12.1 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package verify runs the end-to-end cryptographic verification pipeline against a parsed proof bundle: signing-key lookup against the public keyring, Ed25519 signature check, Merkle-inclusion proof, block hash chain, and optional public-blockchain commitments (Stellar, Bitcoin). Callers receive a Report summarizing every check.

Index

Constants

View Source
const (
	CatDataIntegrity = "data_integrity"
	CatCryptographic = "cryptographic"
	CatStructural    = "structural"
	CatTiming        = "timing"
	CatBlockchain    = "blockchain"
)

Category constants for user-friendly failure grouping.

Variables

CategoryOrder defines the display order for categories. This is Appendix E.22's category report order, not a local preference — every surface that groups steps by category (the terminal Issues section and the --json `steps` array) must present them in it.

Functions

func HexToBase64

func HexToBase64(h string) string

HexToBase64 converts a lowercase-hex string to standard base64 encoding, returning the input unchanged when it is not one.

It decodes through tscrypto.HexToBytes rather than hex.DecodeString so that E.4's lowercase rule holds on the display surfaces too. Go's decoder is case-insensitive, so an uppercase cx[].memo used to be re-encoded into a base64 string indistinguishable from the one a conforming bundle produces — the presenter and the --json `committed_hash_base64` field laundered the very defect the verification steps now fail on. Echoing the offending value back instead keeps the two surfaces telling the same story.

func Present

func Present(r *Report)

Present renders a Report to stdout with lipgloss styling.

Types

type BlockchainCommitment

type BlockchainCommitment struct {
	Network             string `json:"network"`
	Ledger              int    `json:"ledger,omitempty"`
	BlockHeight         int    `json:"block_height,omitempty"`
	Timestamp           string `json:"timestamp,omitempty"`
	TxHash              string `json:"tx_hash"`
	CommittedHashHex    string `json:"committed_hash_hex"`
	CommittedHashBase64 string `json:"committed_hash_base64"`
	ExternalCheck       string `json:"external_check"` // "confirmed" | "skipped" | "failed"
	ExternallyVerified  bool   `json:"externally_verified"`
}

BlockchainCommitment holds data for a Stellar or Bitcoin commitment.

ExternalCheck is the machine-readable form of ExternalStatus's three states and ExternallyVerified is its `== "confirmed"` projection. The boolean alone collapsed "the chain answered and disagreed" and "no lookup was attempted" into one `false`, which is the collapse the tri-state was introduced to remove — but only the terminal presenter had been taught the difference, so a JSON consumer still got the boolean back.

type Claims

type Claims struct {
	Hash            string          `json:"hash"`
	HashType        string          `json:"hash_type"`
	Name            string          `json:"name"`
	Description     string          `json:"description,omitempty"`
	Timestamp       string          `json:"timestamp,omitempty"`
	URL             string          `json:"url,omitempty"`
	Location        *LatLong        `json:"location,omitempty"`
	HasMetadata     bool            `json:"-"`
	RawMetadata     json.RawMessage `json:"-"`
	TimestampStatus TimestampStatus `json:"-"`
	TimestampNote   string          `json:"-"`
}

Claims holds the parsed user claims for display in the report.

type CommitmentInfo

type CommitmentInfo struct {
	Method        string         `json:"method"`               // "stellar" or "bitcoin"
	Network       string         `json:"network"`              // "testnet", "public", "mainnet", "regtest"
	Ledger        int            `json:"ledger"`               // Stellar ledger (0 if bitcoin)
	Height        int            `json:"height"`               // Bitcoin block height (0 if stellar)
	TxHash        string         `json:"tx_hash"`              // Transaction hash (full, for explorer lookup)
	CommittedHash string         `json:"committed_hash"`       // The value committed on-chain (memo_hash or op_return)
	BlockHash     string         `json:"block_hash,omitempty"` // Bitcoin block header hash (for explorer lookup)
	Timestamp     string         `json:"timestamp"`            // Public blockchain timestamp (ISO 8601)
	ExternalCheck ExternalStatus `json:"external_check"`       // outcome of the public-chain lookup
}

CommitmentInfo holds summary data about a commitment for display.

type EntropySubject

type EntropySubject struct {
	RawSource  string // lowercase identifier emitted by ptype.Name (e.g. "entropy_nist")
	Source     string // humanized: "NIST Beacon"
	CapturedAt string // from entropy data timestamp (source-specific)

	// NIST Beacon fields
	PulseIndex  int    // pulse index number
	ChainIndex  int    // chain index
	Version     string // beacon version
	OutputValue string // pulse output value (hex)

	// Bitcoin Block fields
	BlockHash   string // block hash
	BlockHeight int    // block height
	BlockTime   int64  // block time (unix epoch)

	// Stellar Ledger fields
	LedgerHash     string // ledger hash
	LedgerSequence int    // ledger sequence number
	LedgerClosedAt string // ledger close timestamp
}

EntropySubject holds parsed entropy data for display (internal use by presenter). Fields are populated based on the entropy source type.

type ExternalCommit

type ExternalCommit = proof.ExternalCommit

ExternalCommit is a type alias for convenience

type ExternalStatus added in v0.12.0

type ExternalStatus int

ExternalStatus records what happened when a commitment's public-chain lookup was attempted. It replaces a bare "skipped" boolean because two different facts were collapsing into one: a lookup that never ran (no public API for the network, or --skip-external) and a lookup that ran and disagreed. Reporting both as "not skipped" is what let a 404 from Horizon be published as externally_verified: true.

const (
	// ExternalSkipped means no external lookup was attempted. It is the
	// zero value on purpose: a CommitmentInfo built without setting this
	// field has had nothing looked up, and the safe reading of "nothing
	// looked up" is "not confirmed". ExternalConfirmed held the zero
	// value before, which meant a literal that forgot the field
	// published externally_verified: true — the inverse of the defect
	// the tri-state was introduced to fix.
	ExternalSkipped ExternalStatus = iota
	// ExternalConfirmed means an external source was consulted and
	// agreed with the bundle. Only a lookup that ran and agreed may
	// carry it, and every call site assigns it explicitly.
	ExternalConfirmed
	// ExternalFailed means an external lookup ran and did not confirm
	// the commitment.
	ExternalFailed
)

func (ExternalStatus) String added in v0.12.0

func (e ExternalStatus) String() string

String renders an ExternalStatus as the token published in the --json surface. The three states are named rather than collapsed to a boolean because "the chain disagreed" and "we never asked" are different facts and a consumer must be able to tell them apart.

type HashComparison

type HashComparison struct {
	Supplied bool   `json:"supplied"`
	Matched  bool   `json:"matched"`
	Provided string `json:"provided,omitempty"` // the caller's expected hash
	Found    string `json:"found,omitempty"`    // the hash carried in the proof
}

HashComparison reports Appendix E.7's expected-hash check. `supplied` and `matched` are distinct facts and both are always emitted: E.7 requires a consumer to be able to tell "no expected hash was given" from "one was given and did not match". Inferring the first from the object's absence does not work, because the step can also be reported as a skip for a subject that commits to no file hash at all.

type JSONCommitments

type JSONCommitments struct {
	Truestamp *TruestampCommitment  `json:"truestamp,omitempty"`
	Stellar   *BlockchainCommitment `json:"stellar,omitempty"`
	Bitcoin   *BlockchainCommitment `json:"bitcoin,omitempty"`
}

JSONCommitments holds structured commitment data keyed by blockchain.

type JSONIssue

type JSONIssue struct {
	Severity string `json:"severity"` // "error", "warning", "skipped"
	Category string `json:"category"`
	Message  string `json:"message"`
	Detail   string `json:"detail,omitempty"`
}

JSONIssue represents a non-passing verification check. It carries the same rows the terminal's "Issues" section renders — failures, warnings AND skips — because the two surfaces are two renderings of one Report and a consumer comparing them must not find the same row in different buckets. Three of Appendix D.4's fourteen rows are skips; dropping them here left `issues` absent from the --json document of a run whose terminal output printed an Issues heading with three entries.

type JSONNote added in v0.8.0

type JSONNote struct {
	Severity string `json:"severity"` // "warning" | "info"
	Message  string `json:"message"`
}

JSONNote is a workflow-level observation about the verify session that is NOT a proof defect. Examples: "you can pass --hash to confirm a local file" (severity warning), or "this is a claims-only item, no external file to compare" (severity info). Notes are emitted separately from issues so consumers can render them as optional follow-ups rather than failed checks.

type JSONOutput

type JSONOutput struct {
	Result      string `json:"result"`
	SubjectType string `json:"subject_type"`
	SubjectID   string `json:"subject_id"`

	// SignaturesChecked is the machine-readable form of the verdict
	// line's --skip-signatures disclosure. E.25 does not list E.16 among
	// the steps a verifier MAY skip and still call a run verified, so
	// `result` alone is not enough: it reads "verified" whether the
	// Ed25519 signature was confirmed or never looked at.
	SignaturesChecked bool `json:"signatures_checked"`

	Subject        any              `json:"subject"`
	HashComparison HashComparison   `json:"hash_comparison"`
	Timeline       *JSONTimeline    `json:"timeline,omitempty"`
	Commitments    *JSONCommitments `json:"commitments,omitempty"`

	// Steps is the complete step record, in Appendix E.22's category
	// order. It precedes the two filtered views below because those
	// drop rows: `issues` keeps only failures and warnings, and
	// `verification_notes` only one group, so every skip and info row —
	// including three of Appendix D.4's fourteen (Key Binding, Stellar
	// Commitment, Bitcoin Commitment) — used to survive --json as
	// nothing but a count. Appendix E.25 requires that no step D.4
	// reports be absent from a verifier's output.
	Steps []Step `json:"steps"`

	VerificationNotes []JSONNote  `json:"verification_notes,omitempty"`
	Issues            []JSONIssue `json:"issues,omitempty"`
	Summary           JSONSummary `json:"summary"`
}

JSONOutput is the structured output for --json mode. It mirrors the visual terminal output sections.

func BuildJSONOutput

func BuildJSONOutput(r *Report) *JSONOutput

BuildJSONOutput creates a presentation DTO from the internal Report.

type JSONSummary

type JSONSummary struct {
	Passed   int `json:"passed"`
	Failed   int `json:"failed"`
	Warnings int `json:"warnings"`
	Skipped  int `json:"skipped"`
	Info     int `json:"info"`
	Total    int `json:"total"`
}

JSONSummary holds step counts, one field per Appendix E.22 status plus a total that includes all five. `info` used to be both absent and excluded from `total`, so summing the summary gave a different number than iterating `steps` with nothing explaining the gap.

type JSONTimeline

type JSONTimeline struct {
	ClaimedAt   string `json:"claimed_at,omitempty"`
	SubmittedAt string `json:"submitted_at,omitempty"`
	CapturedAt  string `json:"inserted_at,omitempty"`
	CommittedAt string `json:"committed_at,omitempty"`
}

JSONTimeline holds the verified temporal bracket. CapturedAt carries the observation's record-into-Truestamp time and is emitted as `inserted_at` to match the server's renamed wire key; the Go field keeps its CapturedAt name, mirroring TemporalSummary. (Distinct from the entropy subject's own source-capture time in buildSubject, which stays `captured_at`.)

type LatLong

type LatLong struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

LatLong holds geographic coordinates.

type Options

type Options struct {
	KeyringURL     string
	APIURL         string // optional; populates Report.APIURL so the presenter can emit subject-detail + verify web links
	SkipExternal   bool
	SkipSignatures bool
	ExpectedHash   string // hex hash to compare against claims.hash

	// ExpectedSubjectType, when non-empty, asserts that the parsed
	// bundle's subject type name (ptype.Name) matches. A mismatch
	// surfaces as a StatusFail step in the "Subject Type" group —
	// crypto steps still run, the report still renders, and the
	// mismatch appears in the Issues section. Mirrors --type in the
	// download command so users can guard against verifying the wrong
	// file. Must be one of: item, entropy_nist, entropy_stellar,
	// entropy_bitcoin, block, beacon.
	ExpectedSubjectType string
}

Options holds CLI flags for the verifier.

type RemoteOptions

type RemoteOptions struct {
	APIURL       string
	Team         string // team ID, sent as tenant header
	ExpectedHash string // hex hash to compare against claims.hash

	// ExpectedSubjectType, when non-empty, is sent to the server as
	// `data.type` on /proof/verify. The server then asserts the posted
	// bundle's `t` matches; a mismatch returns a structured 422 with
	// meta.code == "subject_type_mismatch" (see truestamp-v2
	// kb/verification/proof-bundle-format.md). Mirrors
	// Options.ExpectedSubjectType for the local path.
	ExpectedSubjectType string
}

RemoteOptions holds configuration for remote verification. The credential is applied by the process-wide auth.Authorizer; only the tenant scoping is carried here.

type Report

type Report struct {
	Filename        string
	FileSize        int64
	ProofVersion    int
	SubjectID       string
	SubjectType     string // ptype.Name(bundle.T): "item" | "entropy_nist" | "entropy_stellar" | "entropy_bitcoin" | "block" | "beacon"
	APIURL          string // the resolved API base URL, used by the presenter to emit subject-detail + verify web links
	GeneratedAt     string
	Source          string // raw entropy source identifier
	Temporal        TemporalSummary
	Claims          Claims
	Steps           []Step
	Remote          bool
	HashProvided    string // non-empty if --hash was used
	SkippedExternal bool
	ChainLength     int

	// SigningKeyID names the key that actually signed this bundle: the
	// key id DERIVED from `pk` (E.9), which is what fills the kid slot
	// of E.16's signature payload. E.9 blesses key rotation, under which
	// it differs from the stored `b.kid`.
	SigningKeyID string

	// BlockSigningKeyID is the block's own `b.kid` field, verbatim. It
	// is an input to E.14's 0x32 preimage, not a statement about who
	// signed, so it is reported wherever a surface is describing the
	// block rather than the signature. Empty when the caller did not
	// populate it, in which case display sites fall back to
	// SigningKeyID — the two agree on every bundle whose key has not
	// rotated.
	BlockSigningKeyID string

	CommitmentInfos []CommitmentInfo
	EntropySubject  EntropySubject
}

Report holds the complete verification results (internal use only). For JSON output, use BuildJSONOutput() to create a presentation DTO.

func Run

func Run(filename string, opts Options) (*Report, error)

Run executes the full verification pipeline on a proof file.

func RunFromBytes

func RunFromBytes(data []byte, displayName string, opts Options) (*Report, error)

RunFromBytes executes the full verification pipeline on raw proof bytes.

func RunRemote

func RunRemote(filename string, opts RemoteOptions) (*Report, error)

RunRemote calls RunRemoteCtx with context.Background.

func RunRemoteCtx added in v0.9.0

func RunRemoteCtx(ctx context.Context, filename string, opts RemoteOptions) (*Report, error)

RunRemoteCtx sends the proof to the Truestamp API for server-side verification and returns a Report compatible with the local verification output. ctx cancels the in-flight request and bounds any token refresh.

func (*Report) BlockKeyID added in v0.12.0

func (r *Report) BlockKeyID() string

BlockKeyID returns the key id to display when describing the block itself, preferring the block's own b.kid over the derived signer.

func (*Report) Counts

func (r *Report) Counts() StepCounts

Counts computes all step status counts in a single pass.

func (*Report) FailedCount

func (r *Report) FailedCount() int

FailedCount returns the number of failed steps.

func (*Report) HashMatched

func (r *Report) HashMatched() bool

HashMatched returns true if a hash was provided and the Hash Comparison group establishes that it matched.

A pass alone is not enough: in remote mode the group can carry both the server's row and the CLI's own locally computed E.7 row, and a server that reports a match the CLI's own comparison refutes must not be able to publish hash_comparison.matched: true. A fail anywhere in the group is decisive against a match.

func (*Report) InfoCount

func (r *Report) InfoCount() int

InfoCount returns the number of info steps.

func (*Report) PassCount

func (r *Report) PassCount() int

PassCount returns the number of passed steps.

func (*Report) Passed

func (r *Report) Passed() bool

Passed returns true if no steps have StatusFail. This is Appendix E.22's verdict rule ("a proof passes when no step is fail") and the predicate behind the process exit code. To render or serialize the outcome use Report.Verdict, which never disagrees with it.

func (*Report) ProofFailedCount

func (r *Report) ProofFailedCount() int

ProofFailedCount returns the number of failures excluding hash comparison.

func (*Report) ProofPassed

func (r *Report) ProofPassed() bool

ProofPassed returns true if all non-hash-comparison steps passed.

It exists so a caller whose local file differs from the timestamped one is told the hash mismatched rather than that the proof is broken — in that case the proof is sound. It is neither the verdict nor the exit-code predicate: use Report.Passed for E.22's verdict rule and Report.Verdict for what to display.

func (*Report) SignaturesSkipped added in v0.12.0

func (r *Report) SignaturesSkipped() bool

SignaturesSkipped reports whether this run left Appendix E.16's Ed25519 check unperformed (the --skip-signatures path emits the group's only skip).

E.25 lists the steps a verifier MAY skip and still call a run verified — the external ones and E.17's keyring cross-check. E.16 is not among them, so a surface that states an outcome has to disclose that the signature was never checked; without it the same bundle prints "VERIFIED - proof is valid" whether its signature is genuine or forged.

func (*Report) SkipCount

func (r *Report) SkipCount() int

SkipCount returns the number of skipped steps.

func (*Report) Verdict added in v0.12.0

func (r *Report) Verdict() Verdict

Verdict classifies the report. The two passing verdicts are returned if and only if Report.Passed is true, so a rendered verdict can never contradict the exit code derived from Passed.

func (*Report) WarnCount

func (r *Report) WarnCount() int

WarnCount returns the number of warning steps.

type Status

type Status int

Status represents the outcome of a verification step.

const (
	StatusPass Status = iota
	StatusFail
	StatusSkip
	StatusWarn
	StatusInfo
)

func StatusFromString

func StatusFromString(s string) (Status, error)

StatusFromString parses a status string. Returns an error for unknown values, and StatusFail alongside it so a caller that ignores the error still fails closed.

func (Status) MarshalJSON

func (s Status) MarshalJSON() ([]byte, error)

MarshalJSON encodes a Status as a JSON string.

func (*Status) UnmarshalJSON

func (s *Status) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a Status. A string outside Appendix E.22's five-value vocabulary is an error, not a value.

It used to map anything unrecognized onto StatusInfo "for forward compatibility". Info is verdict-neutral, so one word of drift in a server's status vocabulary silently turned a reported failure into a note, Report.Passed returned true, and the run printed VERIFIED at exit 0. E.22's verdict rule ("a proof passes when no step is fail") only holds when every status is known; a status this verifier cannot read is handled by Step.UnmarshalJSON, which grades it fail and discloses it, rather than by guessing here.

type Step

type Step struct {
	Group    string `json:"group"`
	Category string `json:"category"`
	Status   Status `json:"status"`
	Message  string `json:"message"`
}

Step is a single verification result.

func (*Step) UnmarshalJSON added in v0.12.0

func (s *Step) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a step supplied by a remote verifier, failing closed on any status this verifier cannot read as one of Appendix E.22's five.

Two shapes used to be scored as passing. A step object carrying no `status` key never reached Status.UnmarshalJSON at all — Go leaves an absent field at its zero value and StatusPass is the zero value — so a row reading "Proof signature invalid (Ed25519)" counted as a pass. A status string outside the vocabulary was mapped to StatusInfo, which is verdict-neutral. In both cases Report.Passed returned true and the run printed VERIFIED at exit 0 over a server-reported failure.

E.22's verdict rule is "a proof passes when no step is fail", which is only sound when every status is known. An unreadable status is graded fail and says so in its own message: the server reported something, and this verifier will not publish a verdict that depends on guessing what.

type StepCounts

type StepCounts struct {
	Passed  int
	Failed  int
	Warned  int
	Info    int
	Skipped int
	Total   int
}

StepCounts holds all step status counts computed in a single pass.

type TemporalSummary

type TemporalSummary struct {
	ClaimedAt   string `json:"claimed_at,omitempty"`
	SubmittedAt string `json:"submitted_at,omitempty"`
	CapturedAt  string `json:"inserted_at,omitempty"`
	CommittedAt string `json:"committed_at,omitempty"`
}

TemporalSummary holds the verified temporal bracket timestamps. Its JSON tags decode the server's /proof/verify `temporal` object (they are never marshaled outbound — the --json surface uses JSONTimeline). The entropy observation timestamp arrives on the wire as `inserted_at`; the CLI keeps naming it CapturedAt and surfacing it under a "Captured" label.

type TimestampStatus

type TimestampStatus int

TimestampStatus indicates the validation state of the claims timestamp.

const (
	TimestampOK      TimestampStatus = iota // within expected range
	TimestampFuture                         // not before submission time
	TimestampStale                          // >7 days before submission
	TimestampMissing                        // no timestamp in claims
)

type TruestampCommitment

type TruestampCommitment struct {
	ChainLength  int    `json:"chain_length"`
	SigningKeyID string `json:"signing_key_id"`
}

TruestampCommitment holds the internal chain summary.

type Verdict added in v0.12.0

type Verdict int

Verdict is the report's single overall outcome. Every surface that states an outcome — the terminal verdict line, the --json `result` field, and (through Report.Passed) the process exit code — derives it from here. Before that was true a Hash Comparison failure raised for a reason other than a mismatch printed "VERIFIED" while the process exited 1.

const (
	// VerdictVerified: no step failed and no expected hash was matched
	// against the proof.
	VerdictVerified Verdict = iota
	// VerdictFullyVerified: no step failed and the caller's expected
	// hash matched the hash carried in the proof.
	VerdictFullyVerified
	// VerdictHashMismatch: every check on the proof itself passed, but
	// the caller's expected hash disagrees with it. Appendix E.7 makes
	// this a statement about the caller's data, not a defective proof —
	// hence its own verdict rather than a bare failure. It is still a
	// failure for exit-code purposes.
	VerdictHashMismatch
	// VerdictFailed: at least one verification step failed.
	VerdictFailed
)

Jump to

Keyboard shortcuts

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