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
- Variables
- func HexToBase64(h string) string
- func Present(r *Report)
- type BlockchainCommitment
- type Claims
- type CommitmentInfo
- type EntropySubject
- type ExternalCommit
- type ExternalStatus
- type HashComparison
- type JSONCommitments
- type JSONIssue
- type JSONNote
- type JSONOutput
- type JSONSummary
- type JSONTimeline
- type LatLong
- type Options
- type RemoteOptions
- type Report
- func Run(filename string, opts Options) (*Report, error)
- func RunFromBytes(data []byte, displayName string, opts Options) (*Report, error)
- func RunRemote(filename string, opts RemoteOptions) (*Report, error)
- func RunRemoteCtx(ctx context.Context, filename string, opts RemoteOptions) (*Report, error)
- func (r *Report) BlockKeyID() string
- func (r *Report) Counts() StepCounts
- func (r *Report) FailedCount() int
- func (r *Report) HashMatched() bool
- func (r *Report) InfoCount() int
- func (r *Report) PassCount() int
- func (r *Report) Passed() bool
- func (r *Report) ProofFailedCount() int
- func (r *Report) ProofPassed() bool
- func (r *Report) SignaturesSkipped() bool
- func (r *Report) SkipCount() int
- func (r *Report) Verdict() Verdict
- func (r *Report) WarnCount() int
- type Status
- type Step
- type StepCounts
- type TemporalSummary
- type TimestampStatus
- type TruestampCommitment
- type Verdict
Constants ¶
const ( CatDataIntegrity = "data_integrity" CatCryptographic = "cryptographic" CatStructural = "structural" CatTiming = "timing" CatBlockchain = "blockchain" )
Category constants for user-friendly failure grouping.
Variables ¶
var CategoryOrder = map[string]int{ CatDataIntegrity: 0, CatCryptographic: 1, CatStructural: 2, CatTiming: 3, CatBlockchain: 4, }
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 ¶
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.
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 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 PROOF_FORMAT_IMPLEMENTERS_GUIDE
// §12). 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 RunFromBytes ¶
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
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
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 ¶
FailedCount returns the number of failed steps.
func (*Report) HashMatched ¶
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) Passed ¶
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 ¶
ProofFailedCount returns the number of failures excluding hash comparison.
func (*Report) ProofPassed ¶
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
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) Verdict ¶ added in v0.12.0
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.
type Status ¶
type Status int
Status represents the outcome of a verification step.
func StatusFromString ¶
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 ¶
MarshalJSON encodes a Status as a JSON string.
func (*Status) UnmarshalJSON ¶
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
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 ¶
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 )