Documentation
¶
Overview ¶
Package evidence models tamper-evident, hash-chained records of what an engagement produced (scans, findings, reports). Each item's Hash covers its content AND the previous item's Hash, so any later edit, insertion, or removal breaks the chain – and the report path must refuse to proceed on a mismatch . This is the domain groundwork; persistence + wiring land with the evidence vault.
Index ¶
Constants ¶
const ( AttestationContextEvidence = "synapse-evidence-head:v1" AttestationContextAudit = "synapse-audit-head:v1" )
Attestation context tags provide DOMAIN SEPARATION for the signed message: one ed25519 key signs both evidence and audit chain heads, so the signed bytes are prefixed with a per-chain tag. This makes an attestation self-describing about WHICH custody chain it covers and means an evidence-head signature can never be presented as an audit-head signature (and vice versa) even though both heads are 64-char hex sha256 strings. The `:v1` suffix lets the format evolve.
Variables ¶
var ErrBadAttestation = errors.New("evidence attestation invalid")
ErrBadAttestation is returned when a chain-head signature fails verification.
var ErrChainBroken = errors.New("evidence chain broken")
ErrChainBroken is returned when an evidence chain fails verification.
Functions ¶
func AttestationMessage ¶
AttestationMessage builds the exact bytes that are signed/verified for a chain head under a context. An empty context reproduces the legacy bare-head message so attestations minted before domain separation still verify; a non-empty context binds the signature to that chain kind. Sign and VerifyAttestation MUST agree by going through this one function.
func ComputeHash ¶
ComputeHash returns the chain hash binding an evidence link to its predecessor. It binds not just previous_hash + content but ALSO the attribution + metadata (kind, finding_id, storage_ref, created_by, created_at) – so a rewrite of WHO produced the evidence or WHEN is detected by VerifyChain, not just a content edit. The timestamp is truncated to µs (matching Postgres timestamptz) so the hash is stable across a DB round-trip. Including previous_hash links the chain: any earlier change cascades.
func KeyFingerprint ¶
KeyFingerprint returns a short, stable id for an ed25519 public key (first 16 hex chars of its sha256) – used as the attestation KeyID and to pin a signer.
func VerifyAttestation ¶
func VerifyAttestation(att Attestation) error
VerifyAttestation checks that att is a well-formed ed25519 signature over its context-tagged head by att.PublicKey, and that the embedded KeyID matches that key. It does NOT decide whether the key is trusted, nor whether the Context is the one the caller expected – those are the verifier's policy (pin a known key; assert the context for your chain). Returns ErrBadAttestation on any failure.
func VerifyChain ¶
VerifyChain checks that items form an unbroken hash chain in order: each item's PreviousHash equals the prior item's Hash, and each Hash recomputes from its own content + previous hash. It returns ErrChainBroken at the first break.
Types ¶
type Attestation ¶
type Attestation struct {
Algorithm string `json:"algorithm"` // "ed25519"
KeyID string `json:"key_id"` // short fingerprint of the public key (sha256 prefix)
PublicKey string `json:"public_key"` // base64-std of the 32-byte ed25519 public key
Context string `json:"context,omitempty"` // domain-separation tag (e.g. AttestationContextAudit); empty = legacy bare-head
Head string `json:"head"` // the chain head hash that was signed
Signature string `json:"signature"` // base64-std of the 64-byte signature
}
Attestation is a detached signature over a chain head: it proves WHICH key attested to that head (origin / non-repudiation) and, via Context, WHICH chain it covers, on top of the chain's integrity. ed25519 signatures are deterministic (RFC 8032), and only the (context-tagged) head is signed – so re-signing the same chain yields identical bytes, keeping the report byte-reproducible. Verification needs only the public key, so it is a pure function here; only signing needs the private key (a port).
type Evidence ¶
type Evidence struct {
ID shared.ID
EngagementID shared.ID
FindingID shared.ID // optional link to the finding this evidence supports
Kind string // e.g. "scan", "finding", "report"
Content []byte // the sealed payload (or a digest of it)
StorageRef string // optional content-addressed blob key (sha256) for an out-of-line artifact; the same hash is also inside Content, so the blob is chain-protected
Hash string // hex sha256 binding previous_hash + attribution + metadata + content
PreviousHash string // the prior item's Hash; empty for the first link
CreatedBy string // human/agent id that produced this evidence (attribution)
CreatedAt time.Time
}
Evidence is one link in an engagement's append-only hash chain.