Documentation
¶
Overview ¶
Package sign is the cross-cutting signing primitive: per-file ephemeral Ed25519 keypair generation, canonical JSON encoding (RFC 8785-style), and envelope signing/verification. The private key is discarded the instant the signature is computed; the public key + signature live inside the envelope, so every file is independently verifiable offline.
See ARCHITECTURE.md §Core principles #4 and docs/architecture/02-layers.md §Per-file signing.
Index ¶
- Constants
- func Algorithms() []string
- func Encode(v any) ([]byte, error)
- func EncodeEnvelope(e *core.Envelope) ([]byte, error)
- func EncodeManifest(m *core.Manifest) ([]byte, error)
- func Envelope(e *core.Envelope) error
- func Manifest(m *core.Manifest) error
- func Register(name string, signer Signer, verify VerifyFunc)
- func SetDefaultAlgorithm(name string) error
- func Verify(payload []byte, sig Signature) error
- func VerifyEnvelope(e *core.Envelope) error
- func VerifyManifest(m *core.Manifest) error
- type Signature
- type Signer
- type VerifyFunc
Constants ¶
const AlgorithmEd25519 = "ed25519"
AlgorithmEd25519 is the built-in default signature algorithm. Its name travels in Signature.Algorithm (and thus in every envelope and manifest), so a verifier selects the right implementation from the signed artifact alone — adding a new algorithm never changes the wire format.
Variables ¶
This section is empty.
Functions ¶
func Algorithms ¶ added in v0.27.2
func Algorithms() []string
Algorithms returns the sorted list of registered algorithm names.
func Encode ¶
Encode returns the RFC 8785-style canonical JSON encoding of v. Output is byte-identical for any Go input that represents the same logical JSON document — which is what makes Ed25519 signatures reproducible across processes, languages, and time.
Canonicalization rules implemented:
- UTF-8 output (delegated to encoding/json).
- No insignificant whitespace.
- Object keys sorted lexicographically by Unicode code-point.
- String escaping follows encoding/json with SetEscapeHTML(false); '<', '>', '&' are emitted verbatim, not as \u escapes.
- Numbers are preserved as their exact JSON source text via a UseNumber re-parse, so arbitrary-precision integers (64-bit IDs, epoch-nanosecond timestamps) survive canonicalization with no precision loss. Floats are emitted verbatim from their source text; signed payloads in this CLI are not expected to contain floats, but if one appears it round-trips unchanged.
- Array order is preserved.
The strategy is to leverage encoding/json for marshaling and string escaping (the parts most likely to go subtly wrong if hand-rolled), then re-emit the resulting generic tree with sorted keys and no whitespace.
func EncodeEnvelope ¶
EncodeEnvelope returns the canonical JSON bytes of e (signature included). This is the form written to the vault: the on-disk bytes are byte-identical across writers, so an auditor can verify by either re-canonicalizing on read or checking the file's SHA-256 against a recorded hash.
Returns an error if e has not been signed.
func EncodeManifest ¶
EncodeManifest returns the canonical JSON bytes of m (signature included). Returns an error if m has not been signed.
func Envelope ¶
Envelope signs e in place. After return, e.Signature carries the ephemeral keypair's public key and the signature value. The signature covers canonical JSON of {format_version, produced_at, records} — three fields, no signature sentinel.
func Manifest ¶
Manifest signs m in place using a fresh ephemeral Ed25519 keypair. The signature covers canonical JSON of every Manifest field except Signature itself — including FileHashes, which is the single-level Merkle root binding every file in the run folder.
func Register ¶ added in v0.27.2
func Register(name string, signer Signer, verify VerifyFunc)
Register adds or replaces the Signer + VerifyFunc for an algorithm name. Intended for eager package-level registration by built-in algorithms and for project-local or HSM/KMS-backed signers compiled in via `sigcomply build`. Panics on an empty name or nil signer/verify — a misconfigured signer must fail loudly at startup, never silently produce unverifiable evidence.
The registry is the sole coupling point between "how we sign" and "what we sign": Envelope/Manifest call Sign/Verify and never name an algorithm, mirroring how the source/vault/framework registries decouple policies from plugins.
func SetDefaultAlgorithm ¶ added in v0.27.2
SetDefaultAlgorithm changes the algorithm Sign uses. Returns an error if the algorithm is not registered. Used by a FIPS build profile or a project policy to make, e.g., ECDSA-P256 the default signer without changing any calling code.
func Verify ¶
Verify checks signature against payload by dispatching on sig.Algorithm to the registered VerifyFunc. Returns a non-nil error on any failure mode (unregistered algorithm, malformed key/value, or cryptographic mismatch).
Verification answers "this payload was signed by whoever holds this key", not "this key is authorized". The caller is responsible for any trust-establishment beyond that.
func VerifyEnvelope ¶
VerifyEnvelope checks the embedded signature against the envelope's content fields. Returns nil if the signature is valid.
func VerifyManifest ¶
VerifyManifest checks the embedded signature against m's content fields. Returns nil if valid.
Types ¶
type Signature ¶
Signature is the on-the-wire shape of a signature: an algorithm identifier, the public verification key, and the signature value. Bytes are raw; envelopes/manifests encode them as base64 via JSON marshaling. The shape is algorithm-agnostic — Ed25519, ECDSA, RSA, an HSM/KMS-backed signer, or a post-quantum scheme all populate the same three fields.
type Signer ¶ added in v0.27.2
type Signer interface {
// Algorithm is the identifier stamped into Signature.Algorithm and
// used by Verify to dispatch to the matching VerifyFunc.
Algorithm() string
// Sign returns a Signature over payload.
Sign(payload []byte) (Signature, error)
}
Signer produces a Signature over a payload using one algorithm.
The per-file ephemeral-keypair discipline (CLAUDE.md Invariant #3) is a property of the built-in ed25519Signer, NOT a requirement of this interface: an HSM/KMS-backed signer legitimately holds a long-lived key whose private half never leaves the device. What every Signer must guarantee is that the returned Signature carries enough to verify offline — for asymmetric schemes that means the public key in Signature.PublicKey; a future KMS signer that verifies by key reference would register a matching VerifyFunc.
type VerifyFunc ¶ added in v0.27.2
VerifyFunc checks a signature's raw public key + value against a payload for one algorithm. Registered alongside a Signer so Verify can dispatch on Signature.Algorithm.