anchor

package
v0.0.0-...-278c5aa Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package anchor provides transparency log anchoring for HELM's ProofGraph.

Anchoring periodically submits Merkle roots from the ProofGraph to external transparency logs (Sigstore Rekor, RFC 3161 TSA), creating externally verifiable trust anchors that prove the integrity of the proof chain without trusting HELM.

This implements the P4 ProofGraph anchoring strategy from HELM UCS v1.2.

quantum_posture: eIDAS anchoring consumes QTSP-issued classical timestamp tokens (X.509 chains, SHA-256 imprints); this file adds no cryptographic control of its own, and the receipt-encoding change keeps the DER token base64-encoded in Signature without altering any primitive.

Index

Constants

View Source
const (
	// DefaultRekorURL is the public Sigstore Rekor v2 instance.
	DefaultRekorURL = "https://rekor.sigstore.dev"
)
View Source
const (
	// EIDASBackendName identifies the eIDAS/QTSP-labelled anchor backend. The
	// identifier alone is not proof of cryptographic or legal qualification.
	EIDASBackendName = "eidas-qtsp"
)

Variables

View Source
var (
	// ErrEIDASChainNotTrusted means none of the certificates inside the
	// timestamp token matched a thumbprint on the EU Trusted List.
	ErrEIDASChainNotTrusted = errors.New("eidas: timestamp chain does not terminate at an EU Trusted List root")

	// ErrEIDASLOTLStale means the EU Trusted List cache has not been
	// refreshed inside the configured maxLOTLAge window.
	ErrEIDASLOTLStale = errors.New("eidas: EU Trusted List cache is stale; refresh required before verification")

	// ErrEIDASMissingLOTL means the anchor was constructed without a
	// trust.EUTrustedList instance.
	ErrEIDASMissingLOTL = errors.New("eidas: no EU Trusted List configured")

	// ErrEIDASMalformedToken means the embedded RFC 3161 token could not
	// be parsed (corrupt response, wrong content type, etc.).
	ErrEIDASMalformedToken = errors.New("eidas: malformed RFC 3161 timestamp token")
)

Errors surfaced by the eIDAS anchor's verification path.

Functions

This section is empty.

Types

type AnchorBackend

type AnchorBackend interface {
	// Name returns the human-readable name of this backend (e.g., "rekor-v2", "rfc3161").
	Name() string

	// Anchor submits a Merkle root to the transparency log and returns an AnchorReceipt.
	// The receipt contains all information necessary to independently verify the anchoring.
	Anchor(ctx context.Context, req AnchorRequest) (*AnchorReceipt, error)

	// Verify checks that an AnchorReceipt is valid against the transparency log.
	Verify(ctx context.Context, receipt *AnchorReceipt) error
}

AnchorBackend defines the interface for transparency log backends. Implementations include Sigstore Rekor v2 and RFC 3161 timestamping authorities.

type AnchorReceipt

type AnchorReceipt struct {
	// Backend identifies which transparency log produced this receipt.
	Backend string `json:"backend"`

	// Request is the original anchor request.
	Request AnchorRequest `json:"request"`

	// LogID is the transparency log's unique identifier (e.g., Rekor log ID).
	LogID string `json:"log_id"`

	// LogIndex is the entry's position in the transparency log.
	LogIndex int64 `json:"log_index"`

	// IntegratedTime is the timestamp assigned by the transparency log.
	IntegratedTime time.Time `json:"integrated_time"`

	// Signature is the transparency log's signature over the entry.
	Signature string `json:"signature"`

	// InclusionProof contains the Merkle inclusion proof from the log's tree.
	InclusionProof *LogInclusionProof `json:"inclusion_proof,omitempty"`

	// RawResponse is the full response from the transparency log for archival.
	RawResponse json.RawMessage `json:"raw_response,omitempty"`

	// ReceiptHash is the SHA-256 hash of this receipt for indexing.
	ReceiptHash string `json:"receipt_hash"`
}

AnchorReceipt is the proof returned by a transparency log after anchoring.

func (*AnchorReceipt) ComputeReceiptHash

func (r *AnchorReceipt) ComputeReceiptHash() string

ComputeReceiptHash computes a deterministic hash of the receipt for indexing.

type AnchorRequest

type AnchorRequest struct {
	// MerkleRoot is the hex-encoded SHA-256 root of the ProofGraph subtree being anchored.
	MerkleRoot string `json:"merkle_root"`

	// FromLamport is the start of the Lamport clock range covered by this anchor.
	FromLamport uint64 `json:"from_lamport"`

	// ToLamport is the end of the Lamport clock range covered by this anchor.
	ToLamport uint64 `json:"to_lamport"`

	// NodeCount is the number of ProofGraph nodes covered by this anchor.
	NodeCount int `json:"node_count"`

	// HeadNodeIDs are the current DAG head node hashes at anchor time.
	HeadNodeIDs []string `json:"head_node_ids"`

	// Timestamp is the time the anchor request was created.
	Timestamp time.Time `json:"timestamp"`
}

AnchorRequest contains the data to be anchored.

func (*AnchorRequest) ComputeDigest

func (r *AnchorRequest) ComputeDigest() ([]byte, error)

ComputeDigest returns a deterministic SHA-256 digest of the anchor request for signing/submission. Uses sorted JSON keys for determinism.

type EIDASAnchor

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

EIDASAnchor submits ProofGraph Merkle-root imprints to a configured RFC 3161 endpoint and performs a limited token-certificate inventory check against a caller-supplied EUTrustedList.

EIDASAnchor implements the same AnchorBackend interface as RFC3161Backend, adding two stages on top:

  1. Submit an RFC 3161 TimeStampReq and preserve the returned token.
  2. Extract certificates from the token and confirm that at least one certificate's SHA-256 thumbprint is present in the supplied EUTrustedList.

Verify does not validate the CMS signature, bind the token's message imprint to the receipt, build an X.509 certification path, or verify the LOTL XML signature. Those missing checks mean this type does not by itself establish cryptographic or legal eIDAS qualification.

If the LOTL cache is empty (never refreshed) or stale beyond the configured threshold, anchoring still succeeds (we have a token) but verification fails with ErrEIDASLOTLStale so callers that invoke Verify can refuse to trust the anchor. The CLI `verify --require-eidas` does not invoke this method; it performs a separate metadata-only check.

func NewEIDASAnchor

func NewEIDASAnchor(qtspURL string, lotl *trust.EUTrustedList, opts ...EIDASOption) *EIDASAnchor

NewEIDASAnchor creates a new eIDAS/QTSP-labelled RFC 3161 anchor backend.

qtspURL must point to an RFC 3161 endpoint. Callers are responsible for selecting and independently validating the provider and its legal status.

lotl is the EU Trusted List validator used to gate verification; it must not be nil. Callers are responsible for invoking lotl.Refresh on a schedule (see trust.DefaultEULOTLRefreshInterval).

func (*EIDASAnchor) Anchor

func (a *EIDASAnchor) Anchor(ctx context.Context, req AnchorRequest) (*AnchorReceipt, error)

Anchor submits the Merkle root to the QTSP and returns an AnchorReceipt whose Signature field is the base64-encoded TSA response. Verification against the EU Trusted List happens inside Verify, which lets callers archive an anchor that was qualified at submission time even if the LOTL later rotates.

func (*EIDASAnchor) Name

func (a *EIDASAnchor) Name() string

Name returns "eidas-qtsp" — the canonical backend identifier.

func (*EIDASAnchor) QTSPURL

func (a *EIDASAnchor) QTSPURL() string

QTSPURL exposes the configured QTSP endpoint (used by the trust CLI).

func (*EIDASAnchor) Verify

func (a *EIDASAnchor) Verify(_ context.Context, receipt *AnchorReceipt) error

Verify performs a limited RFC 3161 certificate-inventory check and requires at least one embedded certificate thumbprint to be present in the supplied EUTrustedList. It does not validate the token signature, message imprint, certificate path, LOTL signature, or legal qualification.

Returns ErrEIDASLOTLStale if the LOTL cache is older than maxLOTLAge. Returns ErrEIDASChainNotTrusted if no embedded certificate thumbprint is present in the supplied EUTrustedList.

type EIDASOption

type EIDASOption func(*EIDASAnchor)

EIDASOption configures the eIDAS anchor.

func WithEIDASAllowEmptyChain

func WithEIDASAllowEmptyChain(allow bool) EIDASOption

WithEIDASAllowEmptyChain is a test-only knob that lets Verify accept a token whose embedded certificate chain is empty (e.g. when the QTSP returned a token without certReq=true). Production code should never enable this.

func WithEIDASHTTPClient

func WithEIDASHTTPClient(client *http.Client) EIDASOption

WithEIDASHTTPClient supplies a custom HTTP client (mTLS, timeouts, etc.).

func WithEIDASMaxLOTLAge

func WithEIDASMaxLOTLAge(d time.Duration) EIDASOption

WithEIDASMaxLOTLAge sets the maximum allowed age of the LOTL cache before Verify rejects anchors with ErrEIDASLOTLStale. Defaults to trust.DefaultEULOTLRefreshInterval.

type InMemoryReceiptStore

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

InMemoryReceiptStore is a simple in-memory receipt store for testing.

func NewInMemoryReceiptStore

func NewInMemoryReceiptStore() *InMemoryReceiptStore

NewInMemoryReceiptStore creates a new in-memory receipt store.

func (*InMemoryReceiptStore) GetLatestReceipt

func (s *InMemoryReceiptStore) GetLatestReceipt(_ context.Context) (*AnchorReceipt, error)

func (*InMemoryReceiptStore) GetReceiptByLamportRange

func (s *InMemoryReceiptStore) GetReceiptByLamportRange(_ context.Context, from, to uint64) ([]*AnchorReceipt, error)

func (*InMemoryReceiptStore) StoreReceipt

func (s *InMemoryReceiptStore) StoreReceipt(_ context.Context, receipt *AnchorReceipt) error

type LogInclusionProof

type LogInclusionProof struct {
	TreeSize   int64    `json:"tree_size"`
	RootHash   string   `json:"root_hash"`
	LogIndex   int64    `json:"log_index"`
	Hashes     []string `json:"hashes"`
	Checkpoint string   `json:"checkpoint,omitempty"`
}

LogInclusionProof proves that the anchored entry exists in the transparency log's Merkle tree.

type RFC3161Backend

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

RFC3161Backend anchors ProofGraph Merkle roots via RFC 3161 timestamping authorities. This provides a standards-based fallback when Sigstore Rekor is unavailable.

func NewRFC3161Backend

func NewRFC3161Backend(tsaURL string, opts ...RFC3161Option) *RFC3161Backend

NewRFC3161Backend creates a new RFC 3161 timestamping backend.

func (*RFC3161Backend) Anchor

Anchor submits the Merkle root hash to an RFC 3161 TSA.

func (*RFC3161Backend) Name

func (r *RFC3161Backend) Name() string

Name returns "rfc3161".

func (*RFC3161Backend) Verify

func (r *RFC3161Backend) Verify(_ context.Context, receipt *AnchorReceipt) error

Verify checks the RFC 3161 timestamp token. Full verification requires parsing the ASN.1 TimeStampResp and validating the TSA's certificate chain. This is a structural check.

type RFC3161Option

type RFC3161Option func(*RFC3161Backend)

RFC3161Option configures the RFC 3161 backend.

func WithRFC3161HTTPClient

func WithRFC3161HTTPClient(client *http.Client) RFC3161Option

WithRFC3161HTTPClient sets a custom HTTP client.

func WithTSAURL

func WithTSAURL(url string) RFC3161Option

WithTSAURL sets the TSA endpoint URL.

type ReceiptStore

type ReceiptStore interface {
	StoreReceipt(ctx context.Context, receipt *AnchorReceipt) error
	GetLatestReceipt(ctx context.Context) (*AnchorReceipt, error)
	GetReceiptByLamportRange(ctx context.Context, from, to uint64) ([]*AnchorReceipt, error)
}

ReceiptStore persists anchor receipts.

type RekorBackend

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

RekorBackend anchors ProofGraph Merkle roots to Sigstore Rekor v2. Rekor v2 (GA Oct 2025) uses a tile-backed transparency log with sigstore-go v1.0.

func NewRekorBackend

func NewRekorBackend(opts ...RekorOption) *RekorBackend

NewRekorBackend creates a new Rekor v2 anchoring backend.

func (*RekorBackend) Anchor

func (r *RekorBackend) Anchor(ctx context.Context, req AnchorRequest) (*AnchorReceipt, error)

Anchor submits the ProofGraph Merkle root to Rekor as a hashedrekord entry.

func (*RekorBackend) Name

func (r *RekorBackend) Name() string

Name returns "rekor-v2".

func (*RekorBackend) Verify

func (r *RekorBackend) Verify(ctx context.Context, receipt *AnchorReceipt) error

Verify checks the Rekor receipt against the transparency log.

type RekorOption

type RekorOption func(*RekorBackend)

RekorOption configures the Rekor backend.

func WithHTTPClient

func WithHTTPClient(client *http.Client) RekorOption

WithHTTPClient sets a custom HTTP client (for mTLS, timeouts, etc.)

func WithRekorURL

func WithRekorURL(url string) RekorOption

WithRekorURL sets a custom Rekor URL (for staging/private instances).

type Service

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

Service orchestrates periodic anchoring of the ProofGraph to transparency logs. It maintains anchor state and supports multiple backends with failover.

func NewService

func NewService(cfg ServiceConfig) (*Service, error)

NewService creates a new anchoring service.

func (*Service) AnchorNow

func (s *Service) AnchorNow(ctx context.Context, req AnchorRequest) (*AnchorReceipt, error)

AnchorNow triggers an immediate anchoring cycle. It tries each backend in order until one succeeds (failover).

func (*Service) LastAnchoredLamport

func (s *Service) LastAnchoredLamport() uint64

LastAnchoredLamport returns the Lamport clock value of the last successful anchor.

func (*Service) VerifyReceipt

func (s *Service) VerifyReceipt(ctx context.Context, receipt *AnchorReceipt) error

VerifyReceipt verifies an anchor receipt against its transparency log.

type ServiceConfig

type ServiceConfig struct {
	// Backends are the transparency log backends, tried in priority order.
	Backends []AnchorBackend

	// Store persists anchor receipts.
	Store ReceiptStore

	// Interval between automatic anchoring cycles. Default: 5 minutes.
	Interval time.Duration

	// Logger for structured logging.
	Logger *slog.Logger
}

ServiceConfig configures the anchoring service.

Jump to

Keyboard shortcuts

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