Documentation
¶
Overview ¶
Package ratify implements the Ratify Protocol — a cryptographic trust protocol for human-agent and agent-agent interactions as agents start to transact.
v1 uses hybrid cryptography: every signature is a pair (Ed25519, ML-DSA-65). Both must verify for a signature to be considered valid. This provides defense in depth against classical cryptanalytic advances on either algorithm and resistance to quantum attacks via ML-DSA-65 (NIST FIPS 204).
Core flow:
- Each party generates a HybridPrivateKey containing one Ed25519 and one ML-DSA-65 keypair. Seeds are independent.
- Humans (or tenant admins, or already-delegated agents) sign Delegation Certs granting another party specific scopes.
- Agents sign a fresh challenge with both algorithms to prove liveness.
- Verifiers check: chain signatures (both algs) + not expired + not revoked + scope intersection valid + challenge fresh.
Package ratify implements the Ratify Protocol — a cryptographic trust protocol for human-agent and agent-agent interactions as agents start to transact.
Index ¶
- Constants
- Variables
- func BundleHash(bundle *ProofBundle) ([]byte, error)
- func CanonicalJSON(v any) ([]byte, error)
- func ChainHash(chain []DelegationCert) ([]byte, error)
- func ChallengeSignBytes(challenge []byte, ts int64) []byte
- func ChallengeSignBytesWithSessionContext(challenge []byte, ts int64, sessionContext []byte) []byte
- func ChallengeSignBytesWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64) []byte
- func DelegationSignBytes(cert *DelegationCert) ([]byte, error)
- func DeriveID(pub HybridPublicKey) string
- func ExpandScopes(scopes []string) []string
- func GenerateAgentKeypair(name, agentType string) (*AgentIdentity, HybridPrivateKey, error)
- func GenerateChallenge() ([]byte, error)
- func GenerateHumanRootKeypair() (*HumanRoot, HybridPrivateKey, error)
- func GenerateHybridKeypair() (HybridPublicKey, HybridPrivateKey, error)
- func HasScope(granted []string, required string) bool
- func HybridKeypairFromSeeds(edSeed, mlSeed [32]byte) (HybridPublicKey, HybridPrivateKey, error)
- func IntersectScopes(lists ...[]string) []string
- func IsSensitive(scope string) bool
- func IssueDelegation(cert *DelegationCert, issuerPriv HybridPrivateKey) error
- func IssueKeyRotationStatement(stmt *KeyRotationStatement, oldPriv, newPriv HybridPrivateKey) error
- func IssueRevocationList(list *RevocationList, issuerPriv HybridPrivateKey) error
- func IssueRevocationPush(push *RevocationPush, issuerPriv HybridPrivateKey) error
- func IssueWitnessEntry(entry *WitnessEntry, witnessPriv HybridPrivateKey) error
- func KeyRotationSignBytes(stmt *KeyRotationStatement) ([]byte, error)
- func PolicyVerdictSignBytes(v *PolicyVerdict) ([]byte, error)
- func ReceiptHash(r *VerificationReceipt) ([]byte, error)
- func RevocationPushSignBytes(push *RevocationPush) ([]byte, error)
- func RevocationSignBytes(list *RevocationList) ([]byte, error)
- func SessionTokenSignBytes(token *SessionToken) ([]byte, error)
- func TransactionReceiptSignBytes(receipt *TransactionReceipt) ([]byte, error)
- func ValidateScopes(scopes []string) error
- func VerificationReceiptSignBytes(r *VerificationReceipt) ([]byte, error)
- func VerifierContextHash(ctx VerifierContext) ([]byte, error)
- func VerifyChallengeSignature(challenge []byte, ts int64, sig HybridSignature, agentPub HybridPublicKey) error
- func VerifyChallengeSignatureWithSessionContext(challenge []byte, ts int64, sessionContext []byte, sig HybridSignature, ...) error
- func VerifyChallengeSignatureWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64, ...) error
- func VerifyDelegationSignature(cert *DelegationCert) error
- func VerifyKeyRotationStatement(stmt *KeyRotationStatement) error
- func VerifyPolicyVerdict(v *PolicyVerdict, policySecret []byte, expectedAgentID, expectedScope string, ...) error
- func VerifyRevocationList(list *RevocationList, issuerPub HybridPublicKey) error
- func VerifyRevocationPush(push *RevocationPush, issuerPub HybridPublicKey) error
- func VerifySessionToken(token *SessionToken, sessionSecret []byte, now time.Time) error
- func VerifyVerificationReceipt(r *VerificationReceipt) error
- func VerifyWitnessEntry(entry *WitnessEntry, witnessPub HybridPublicKey) error
- func WitnessEntrySignBytes(entry *WitnessEntry) ([]byte, error)
- type AgentIdentity
- type Anchor
- type AnchorResolver
- type AuditProvider
- type Constraint
- type ConstraintEvaluator
- type ConstraintType
- type DelegationCert
- type HumanRoot
- type HybridPrivateKey
- type HybridPublicKey
- type HybridSignature
- func SignChallenge(challenge []byte, ts int64, agentPriv HybridPrivateKey) (HybridSignature, error)
- func SignChallengeWithSessionContext(challenge []byte, ts int64, sessionContext []byte, agentPriv HybridPrivateKey) (HybridSignature, error)
- func SignChallengeWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64, ...) (HybridSignature, error)
- type KeyRotationStatement
- type PolicyProvider
- type PolicyVerdict
- type ProofBundle
- type ReceiptParty
- type ReceiptPartySignature
- type RevocationList
- type RevocationProvider
- type RevocationPush
- type SessionToken
- type StreamContext
- type TransactionReceipt
- type TransactionReceiptResult
- type VerificationReceipt
- type VerifierContext
- type VerifyOptions
- type VerifyReceiptOptions
- type VerifyResult
- type WitnessEntry
Constants ¶
const ( // --- Meeting scopes --- ScopeMeetingAttend = "meeting:attend" ScopeMeetingSpeak = "meeting:speak" ScopeMeetingVideo = "meeting:video" ScopeMeetingChat = "meeting:chat" ScopeMeetingRecord = "meeting:record" // sensitive // --- Communication scopes --- ScopeCommsMessageRead = "comms:message:read" ScopeCommsMessageSend = "comms:message:send" ScopeCommsMessageDelete = "comms:message:delete" // sensitive ScopeCommsEmailRead = "comms:email:read" ScopeCommsEmailSend = "comms:email:send" ScopeCommsEmailDelete = "comms:email:delete" // sensitive ScopeCommsCalendarRead = "comms:calendar:read" ScopeCommsCalendarWrite = "comms:calendar:write" // --- File scopes --- ScopeFilesRead = "files:read" ScopeFilesWrite = "files:write" // sensitive // --- Identity scopes --- ScopeIdentityProve = "identity:prove" ScopeIdentityDelegate = "identity:delegate" // sensitive // --- Transaction scopes (v1, core to the "transaction horizon" thesis) --- // ScopeTransactPurchase: buy goods/services on behalf of the principal ScopeTransactPurchase = "transact:purchase" // ScopeTransactSell: sell goods/services on behalf of the principal ScopeTransactSell = "transact:sell" // ScopePaymentsSend: initiate an outbound payment ScopePaymentsSend = "payments:send" // ScopePaymentsReceive: receive / collect a payment on behalf ScopePaymentsReceive = "payments:receive" // ScopePaymentsAuthorize: (sensitive) authorize movement of funds from an // account beyond standard purchase limits. Requires explicit grant. ScopePaymentsAuthorize = "payments:authorize" // --- Contract scopes --- ScopeContractRead = "contract:read" // ScopeContractSign: (sensitive) enter into a binding agreement ScopeContractSign = "contract:sign" // --- Data scopes (for structured application data, distinct from files) --- ScopeDataRead = "data:read" // ScopeDataWrite: (sensitive) create or modify data records ScopeDataWrite = "data:write" // ScopeDataDelete: (sensitive) delete data records ScopeDataDelete = "data:delete" // ScopeDataExport: (sensitive) bulk export — data exfiltration concern ScopeDataExport = "data:export" ScopeDataShare = "data:share" // --- Execute scopes --- // ScopeExecuteTool: invoke an external tool / API on the principal's behalf // (covers MCP-style tool calls). ScopeExecuteTool = "execute:tool" // ScopeExecuteCode: (sensitive) execute arbitrary code on the principal's // compute resources. Requires explicit grant. ScopeExecuteCode = "execute:code" // --- Generate scopes (AI content generation on someone's behalf) --- // ScopeGenerateContent: generate text / image / audio / video content. ScopeGenerateContent = "generate:content" // ScopeGenerateDeepfake: (sensitive) generate content specifically intended // to imitate a specific real person. Sensitive by policy so that any // such generation creates an auditable explicit authorization trail. ScopeGenerateDeepfake = "generate:deepfake" // ScopePhysicalEnter: enter a physical zone (non-sensitive — the real // gate is typically the geo_polygon/geo_circle constraint on the cert). ScopePhysicalEnter = "physical:enter" // ScopePhysicalExit: exit a physical zone. ScopePhysicalExit = "physical:exit" // ScopePhysicalActuate: (sensitive) activate a physical actuator — valve, // lock, door, latch. Anything that moves matter in the world. ScopePhysicalActuate = "physical:actuate" // ScopePhysicalManipulate: (sensitive) manipulate physical objects // (pick-and-place, lift, rotate). Distinct from actuate: manipulation // targets objects, actuation targets fixtures. ScopePhysicalManipulate = "physical:manipulate" // ScopeRobotOperate: operate a robotic platform (power on, hold, idle // motion). The umbrella permission for embodied robots; per-action // restrictions use scope chain intersection + constraints. ScopeRobotOperate = "robot:operate" // ScopeRobotMove: autonomous locomotion — the robot may move in space. // Pair with a geo_polygon/geo_circle constraint to bound where. ScopeRobotMove = "robot:move" // ScopeRobotInteract: the robot may interact with humans or objects in // its environment (touch, grasp, gesture). Non-sensitive at the scope // level; applications requiring higher assurance combine this with // explicit physical:manipulate for stronger semantics. ScopeRobotInteract = "robot:interact" // ScopeDroneFly: (sensitive) operate a drone under active flight. // Nearly always paired with geo + altitude + time_window constraints. ScopeDroneFly = "drone:fly" // ScopeDroneDeliver: conduct a delivery mission. ScopeDroneDeliver = "drone:deliver" // ScopeDroneCapture: capture imagery / telemetry data during a flight. ScopeDroneCapture = "drone:capture" // ScopeVehicleOperate: (sensitive) operate a vehicle — cars, trucks, // watercraft, aircraft other than drones. Max-speed and geo constraints // are strongly recommended. ScopeVehicleOperate = "vehicle:operate" // ScopeVehicleTransport: transport a named passenger or payload. ScopeVehicleTransport = "vehicle:transport" // ScopeVehicleCharge: access charging infrastructure / refueling. ScopeVehicleCharge = "vehicle:charge" // ScopeInfrastructureMonitor: read sensor values and system state from // a piece of infrastructure (HVAC, power, access logs). Read-only. ScopeInfrastructureMonitor = "infrastructure:monitor" // ScopeInfrastructureControl: (sensitive) modify infrastructure state — // HVAC setpoints, breaker state, door policy, etc. ScopeInfrastructureControl = "infrastructure:control" // ScopeInfrastructureAccess: (sensitive) unlock / grant entry to a // restricted facility. Pair with geo + time_window constraints. ScopeInfrastructureAccess = "infrastructure:access" // ScopeActuateValve: (sensitive) generic valve operation. ScopeActuateValve = "actuate:valve" // ScopeActuateMotor: (sensitive) generic motor / actuator operation. ScopeActuateMotor = "actuate:motor" // ScopeActuateSwitch: (sensitive) generic switch / relay operation. ScopeActuateSwitch = "actuate:switch" )
Canonical scope constants. Use these instead of bare strings. Format: domain:resource:action (colon-separated hierarchy)
const ( // ChallengeWindowSeconds is the maximum age of a signed challenge. // Challenges older than this are rejected to prevent replay attacks. ChallengeWindowSeconds = 300 // 5 minutes // MaxDelegationChainDepth is the maximum number of certs in a delegation chain. MaxDelegationChainDepth = 3 )
const ( IdentityStatusAuthorizedAgent = "authorized_agent" IdentityStatusVerifiedHuman = "verified_human" IdentityStatusExpired = "expired" IdentityStatusRevoked = "revoked" IdentityStatusScopeDenied = "scope_denied" IdentityStatusConstraintDenied = "constraint_denied" IdentityStatusConstraintUnverifiable = "constraint_unverifiable" IdentityStatusDelegationNotAuthorized = "delegation_not_authorized" // IdentityStatusConstraintUnknown is returned when a cert carries a // Constraint with a `type` the verifier does not recognize. Fail-closed // — rather than silently ignoring unknown types (which would let a // future cert smuggle unenforced constraints past an older verifier) // the protocol rejects the cert so each version's verifier sees a // consistent supported set. IdentityStatusConstraintUnknown = "constraint_unknown" // IdentityStatusInvalid is the catch-all for structural / cryptographic // failures (bad signature, malformed chain, wrong key, etc). IdentityStatusInvalid = "invalid" )
Identity status values (SPEC §5.9). Success surfaces; granular failure statuses surface so audit/policy layers can route on the status enum without parsing ErrorReason text.
const CustomScopePrefix = "custom:"
CustomScopePrefix is the extension-pattern prefix for application-specific scopes that are not in the canonical vocabulary. Any scope string starting with this prefix is accepted by ValidateScopes, passed through ExpandScopes unchanged, and treated as non-sensitive unless the application opts in via out-of-band policy.
Example: "custom:acme:inventory:read" — Acme Corp's custom scope for their inventory system.
const ProtocolVersion = 1
ProtocolVersion is the current wire format version. v1 mandates hybrid Ed25519 + ML-DSA-65 signing on every signed object.
Variables ¶
var ErrConstraintUnverifiable = errors.New("constraint_unverifiable")
ErrConstraintUnverifiable, when wrapped or returned from a ConstraintEvaluator, routes the failure to identity_status `constraint_unverifiable` rather than `constraint_denied`. Use this for "I don't have the inputs to decide" — never for "the cert says no."
Functions ¶
func BundleHash ¶
func BundleHash(bundle *ProofBundle) ([]byte, error)
BundleHash returns the canonical SHA-256 digest of a ProofBundle. The stable identifier of "what was verified" inside a VerificationReceipt.
Every reference SDK (Go, TypeScript, Python, Rust) MUST produce the same 32-byte digest for the same logical bundle. The canonical form is a fixed alphabetical-key shape with no omitempty so the byte output is deterministic regardless of which optional v1.1 fields are set.
Verified against fixtures in `testvectors/v1/cross_sdk_vectors.json`. Drift in any SDK is caught by that SDK's cross-SDK test suite.
func CanonicalJSON ¶
CanonicalJSON marshals v into canonical bytes per the rules above. The single chokepoint for canonical serialization; all three signable-bytes helpers route through it.
func ChainHash ¶
func ChainHash(chain []DelegationCert) ([]byte, error)
ChainHash returns the canonical 32-byte hash of a delegation chain, defined as SHA-256 of the concatenated delegationSignBytes of each cert in order. Used as a stable identity for a verified chain inside SessionToken so a cert rotation invalidates any token issued against the old chain.
func ChallengeSignBytes ¶
ChallengeSignBytes returns the canonical byte sequence that is signed to produce ProofBundle.ChallengeSig (for both algorithm components). Format: challenge || big-endian uint64(ts).
func ChallengeSignBytesWithSessionContext ¶
ChallengeSignBytesWithSessionContext returns the v1.1 session-bound challenge signable bytes: challenge || big-endian uint64(ts) || session_context.
func ChallengeSignBytesWithStream ¶
func ChallengeSignBytesWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64) []byte
ChallengeSignBytesWithStream returns the v1.1 stream-bound challenge signable bytes. sessionContext may be nil or 32 bytes; streamID is 32 bytes; streamSeq is appended as big-endian int64. Layout:
challenge || big-endian uint64(ts) || [session_context] || stream_id || big-endian int64(stream_seq)
func DelegationSignBytes ¶
func DelegationSignBytes(cert *DelegationCert) ([]byte, error)
DelegationSignBytes returns the canonical byte sequence that is signed to produce DelegationCert.Signature (for both algorithm components). Other- language implementations MUST produce identical bytes for any given cert, or signatures will not verify across implementations.
func DeriveID ¶
func DeriveID(pub HybridPublicKey) string
DeriveID computes the canonical ID for a hybrid public key as hex(SHA-256(ed25519_pub || ml_dsa_65_pub)[:16]).
128-bit collision space is sufficient for agent/human identifiers at the expected scale; birthday bound is 2^64.
func ExpandScopes ¶
ExpandScopes replaces any wildcard scopes with their constituent scopes. Deduplicates the result and returns scopes in lexicographic order so that callers (and downstream serializers) see deterministic output.
Custom scopes (prefix "custom:") pass through unchanged — they are never expanded by wildcards.
func GenerateAgentKeypair ¶
func GenerateAgentKeypair(name, agentType string) (*AgentIdentity, HybridPrivateKey, error)
GenerateAgentKeypair creates a fresh AgentIdentity with a hybrid keypair.
func GenerateChallenge ¶
GenerateChallenge returns 32 cryptographically random bytes from the OS RNG.
func GenerateHumanRootKeypair ¶
func GenerateHumanRootKeypair() (*HumanRoot, HybridPrivateKey, error)
GenerateHumanRootKeypair creates a fresh HumanRoot identity from secure randomness, returning the public HumanRoot and the hybrid private key (which must stay on-device).
func GenerateHybridKeypair ¶
func GenerateHybridKeypair() (HybridPublicKey, HybridPrivateKey, error)
GenerateHybridKeypair produces a fresh hybrid keypair from secure randomness. The two component keys are drawn from independent entropy streams; knowledge of one keypair's private material reveals nothing about the other's.
func HasScope ¶
HasScope checks if the granted scope list covers the required scope, including wildcard expansion.
func HybridKeypairFromSeeds ¶
func HybridKeypairFromSeeds(edSeed, mlSeed [32]byte) (HybridPublicKey, HybridPrivateKey, error)
HybridKeypairFromSeeds derives a hybrid keypair deterministically from two 32-byte seeds. For test vector generation. Production code SHOULD use GenerateHybridKeypair (which reads from the OS RNG).
func IntersectScopes ¶
IntersectScopes returns the set of scopes present in every input list, after wildcard expansion. This is the effective grant of a delegation chain: an agent can only exercise scopes that every cert in the chain conveyed.
Sensitive scopes are never introduced by wildcard expansion, so a sensitive scope not present explicitly in every list is filtered out. Custom scopes pass through the intersection normally.
Returns nil for an empty input. For a single list, returns its expansion.
func IsSensitive ¶
IsSensitive returns true if the scope requires explicit human confirmation. Custom scopes (prefix "custom:") are NOT sensitive by default; applications that want sensitive custom scopes should enforce that at the application policy layer.
func IssueDelegation ¶
func IssueDelegation(cert *DelegationCert, issuerPriv HybridPrivateKey) error
IssueDelegation signs a DelegationCert with the issuer's hybrid private key. The cert must have all fields set except Signature before calling.
Normalizes Constraints from nil to []Constraint{} so the outer cert JSON serializes as `"constraints":[]` rather than `"constraints":null`, matching the canonical wire format and satisfying strict deserializers in TypeScript/Python/Rust SDKs.
func IssueKeyRotationStatement ¶
func IssueKeyRotationStatement(stmt *KeyRotationStatement, oldPriv, newPriv HybridPrivateKey) error
IssueKeyRotationStatement signs a root-key rotation statement with both the old and new private keys. The old signature endorses the new key; the new signature proves possession. Both signatures cover identical canonical bytes.
func IssueRevocationList ¶
func IssueRevocationList(list *RevocationList, issuerPriv HybridPrivateKey) error
IssueRevocationList signs a RevocationList with the issuer's hybrid private key. Both component signatures are produced.
func IssueRevocationPush ¶
func IssueRevocationPush(push *RevocationPush, issuerPriv HybridPrivateKey) error
IssueRevocationPush signs a RevocationPush with the issuer's hybrid private key.
func IssueWitnessEntry ¶
func IssueWitnessEntry(entry *WitnessEntry, witnessPriv HybridPrivateKey) error
IssueWitnessEntry signs a WitnessEntry with the witness operator's hybrid private key.
func KeyRotationSignBytes ¶
func KeyRotationSignBytes(stmt *KeyRotationStatement) ([]byte, error)
KeyRotationSignBytes returns the canonical bytes signed by both old and new keys in a KeyRotationStatement.
func PolicyVerdictSignBytes ¶
func PolicyVerdictSignBytes(v *PolicyVerdict) ([]byte, error)
PolicyVerdictSignBytes returns the canonical byte sequence over which a PolicyVerdict's MAC is computed.
func ReceiptHash ¶
func ReceiptHash(r *VerificationReceipt) ([]byte, error)
ReceiptHash returns the SHA-256 of a receipt's canonical signable bytes. Use this as the `prev_hash` for the NEXT receipt in the chain.
func RevocationPushSignBytes ¶
func RevocationPushSignBytes(push *RevocationPush) ([]byte, error)
RevocationPushSignBytes returns the canonical byte sequence that is signed to produce RevocationPush.Signature.
func RevocationSignBytes ¶
func RevocationSignBytes(list *RevocationList) ([]byte, error)
RevocationSignBytes returns the canonical byte sequence that is signed to produce RevocationList.Signature.
func SessionTokenSignBytes ¶
func SessionTokenSignBytes(token *SessionToken) ([]byte, error)
SessionTokenSignBytes returns the canonical byte sequence that the verifier HMACs with session_secret to produce SessionToken.MAC. The signable excludes MAC itself — signatures (or MACs) cannot cover themselves.
func TransactionReceiptSignBytes ¶
func TransactionReceiptSignBytes(receipt *TransactionReceipt) ([]byte, error)
TransactionReceiptSignBytes returns the canonical byte sequence that every listed party signs to bind the receipt. Parties are sorted lex by PartyID; duplicates are an error (the caller must ensure uniqueness — Verify will reject non-unique receipts).
func ValidateScopes ¶
ValidateScopes returns an error if any scope is not in the canonical vocabulary, and is not a wildcard, and does not start with CustomScopePrefix.
Custom scopes are allowed for application-specific extensions. See the protocol spec §9.4 for the extension rules.
func VerificationReceiptSignBytes ¶
func VerificationReceiptSignBytes(r *VerificationReceipt) ([]byte, error)
VerificationReceiptSignBytes returns the canonical byte sequence that is signed to produce VerificationReceipt.Signature.
func VerifierContextHash ¶
func VerifierContextHash(ctx VerifierContext) ([]byte, error)
VerifierContextHash returns the SHA-256 of the canonical-byte representation of a VerifierContext (SPEC §17.6). Used as the `ContextHash` on a PolicyVerdict so a verdict cached for one context never accidentally applies to another (different country, different amount tier, etc).
func VerifyChallengeSignature ¶
func VerifyChallengeSignature(challenge []byte, ts int64, sig HybridSignature, agentPub HybridPublicKey) error
VerifyChallengeSignature checks the hybrid challenge signature.
func VerifyChallengeSignatureWithSessionContext ¶
func VerifyChallengeSignatureWithSessionContext(challenge []byte, ts int64, sessionContext []byte, sig HybridSignature, agentPub HybridPublicKey) error
VerifyChallengeSignatureWithSessionContext checks a v1.1 session-bound challenge signature.
func VerifyChallengeSignatureWithStream ¶
func VerifyChallengeSignatureWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64, sig HybridSignature, agentPub HybridPublicKey) error
VerifyChallengeSignatureWithStream checks a v1.1 stream-bound challenge signature. sessionContext may be nil or 32 bytes; streamID MUST be 32 bytes; streamSeq MUST be ≥1.
func VerifyDelegationSignature ¶
func VerifyDelegationSignature(cert *DelegationCert) error
VerifyDelegationSignature verifies both component signatures on a DelegationCert against the declared IssuerPubKey. Returns nil iff both verify.
func VerifyKeyRotationStatement ¶
func VerifyKeyRotationStatement(stmt *KeyRotationStatement) error
VerifyKeyRotationStatement verifies key continuity, key possession, and structural ID/pubkey consistency for a KeyRotationStatement.
func VerifyPolicyVerdict ¶
func VerifyPolicyVerdict( v *PolicyVerdict, policySecret []byte, expectedAgentID, expectedScope string, expectedContextHash []byte, now time.Time, ) error
VerifyPolicyVerdict checks a PolicyVerdict's HMAC against `policySecret`, confirms the validity window contains `now`, and confirms the verdict's (agent_id, scope, context_hash) tuple matches the caller's expectation. Returns nil iff everything matches AND `verdict.Allow == true`. A verdict whose MAC is fresh but whose `Allow == false` returns a descriptive "policy_verdict_denied" error — explicit cached deny.
func VerifyRevocationList ¶
func VerifyRevocationList(list *RevocationList, issuerPub HybridPublicKey) error
VerifyRevocationList verifies both component signatures on a RevocationList against the issuer's hybrid public key.
func VerifyRevocationPush ¶
func VerifyRevocationPush(push *RevocationPush, issuerPub HybridPublicKey) error
VerifyRevocationPush verifies the hybrid signature on a RevocationPush against the issuer's public key. Returns nil iff the signature is valid.
func VerifySessionToken ¶
func VerifySessionToken(token *SessionToken, sessionSecret []byte, now time.Time) error
VerifySessionToken checks a SessionToken's HMAC against sessionSecret and its validity window against now. Returns nil iff the MAC matches and the token is within [IssuedAt, ValidUntil]. This does NOT verify a challenge signature; callers who need to verify a streamed turn use VerifyStreamedTurn.
func VerifyVerificationReceipt ¶
func VerifyVerificationReceipt(r *VerificationReceipt) error
VerifyVerificationReceipt verifies the hybrid signature on a VerificationReceipt against the asserted verifier public key. Returns nil iff both component signatures verify. Note: this only verifies the receipt's *authenticity* — that the named verifier did sign it. Callers who need to verify the receipt chain (prev_hash linkage) MUST hash each prior receipt's signable bytes and check that the chain is contiguous.
func VerifyWitnessEntry ¶
func VerifyWitnessEntry(entry *WitnessEntry, witnessPub HybridPublicKey) error
VerifyWitnessEntry verifies the hybrid signature on a WitnessEntry against the witness operator's public key. Returns nil iff the signature is valid.
func WitnessEntrySignBytes ¶
func WitnessEntrySignBytes(entry *WitnessEntry) ([]byte, error)
WitnessEntrySignBytes returns the canonical byte sequence that is signed to produce WitnessEntry.Signature.
Types ¶
type AgentIdentity ¶
type AgentIdentity struct {
// ID is hex(SHA-256(ed25519_pub || ml_dsa_65_pub)[:16]) — lowercase hex.
ID string `json:"id"`
PublicKey HybridPublicKey `json:"public_key"`
Name string `json:"name"`
AgentType string `json:"agent_type"` // "zoom_bot", "voice_agent", "mcp_server", "custom"
CreatedAt int64 `json:"created_at"`
}
AgentIdentity is the keypair for an AI agent. Agents generate their own keypairs and request delegation from a principal (human or another agent).
type Anchor ¶
type Anchor struct {
Type string `json:"type"` // "enterprise_sso" | "email" | "government_id"
Provider string `json:"provider"` // "okta" | "google" | "azure_ad"
Reference string `json:"reference"` // Opaque (privacy-preserving), not PII
VerifiedAt int64 `json:"verified_at"`
}
Anchor optionally binds a HumanRoot to an external identity system (SSO, email, government ID) for higher assurance at registration time. Opaque references only — no PII on the wire.
type AnchorResolver ¶
AnchorResolver resolves a verified `human_id` to its external-identity binding — the `Anchor` originally registered when the HumanRoot was minted (SSO assertion, government ID attestation, email-verified, etc). Implementations typically read from a verifier-local identity directory. (SPEC §17.8)
Errors are non-fatal: the verifier MUST NOT fail the bundle because the resolver errored. The verifier silently leaves `VerifyResult.Anchor` nil and continues. A nil resolver disables the lookup entirely.
type AuditProvider ¶
type AuditProvider interface {
LogVerification(result VerifyResult, bundle *ProofBundle) error
}
AuditProvider handles the persistence of verification receipts for compliance and forensic analysis. (SPEC §17.3)
type Constraint ¶
type Constraint struct {
Count int `json:"count,omitempty"`
Currency string `json:"currency,omitempty"` // ISO 4217
End string `json:"end,omitempty"` // "HH:MM"
Lat float64 `json:"lat,omitempty"`
Lon float64 `json:"lon,omitempty"`
MaxAltM float64 `json:"max_alt_m,omitempty"`
MaxAmount float64 `json:"max_amount,omitempty"`
MaxLat float64 `json:"max_lat,omitempty"`
MaxLon float64 `json:"max_lon,omitempty"`
MaxMps float64 `json:"max_mps,omitempty"` // m/s SI
MinAltM float64 `json:"min_alt_m,omitempty"`
MinLat float64 `json:"min_lat,omitempty"`
MinLon float64 `json:"min_lon,omitempty"`
Points [][2]float64 `json:"points,omitempty"` // [[lat,lon], ...]
RadiusM float64 `json:"radius_m,omitempty"`
Start string `json:"start,omitempty"` // "HH:MM"
Type ConstraintType `json:"type"`
TZ string `json:"tz,omitempty"` // IANA zone
WindowS int64 `json:"window_s,omitempty"`
}
Constraint is an optional, first-class bound on when / where / how much an agent may exercise its scopes. Constraints are evaluated at verify time against a VerifierContext supplied by the application. Verifiers fail closed: if the context lacks the inputs a constraint requires, the cert is rejected with `constraint_unverifiable`.
The wire format is a tagged JSON object. Constraint.Type identifies the kind; the remaining fields are the kind-specific parameters. Unknown Type values MUST be rejected by conformant verifiers (fail-closed is the v1 semantics).
Constraint is a tagged union on wire. The struct carries every possible kind-specific field, but canonical serialization (MarshalJSON below) emits ONLY the fields meaningful for the specific Type plus `type` itself. That eliminates the v1 "zero-as-absence" ambiguity: a geo_circle at lat=0, lon=0 (equator / prime meridian) serializes with lat:0 and lon:0 explicitly, not by omission, so the canonical bytes are unambiguous.
Unknown Type serializes as `{"type": "<unknown>"}` with no other fields; the verifier catches unknown tags via isConstraintUnknown and fails closed.
func (Constraint) MarshalJSON ¶
func (c Constraint) MarshalJSON() ([]byte, error)
MarshalJSON emits the canonical per-kind shape. Keys are alphabetical — Go's encoding/json sorts map keys automatically. Zero values of kind-relevant fields ARE emitted (lat:0, lon:0 for a geo_circle at the equator/prime-meridian intersection); fields irrelevant to this kind are not emitted, so downstream parsers never see a stray unused zero.
CROSS-SDK: TypeScript / Python / Rust MUST produce byte-identical output for the same input. See sdks/*/src/constraints.* for each SDK's canonicalConstraintDict helper.
type ConstraintEvaluator ¶
type ConstraintEvaluator interface {
Evaluate(c Constraint, certID string, ctx VerifierContext, now time.Time) error
}
ConstraintEvaluator is a pluggable evaluator for extension constraint types (SPEC §17.7). Built-in types (geo_circle, geo_polygon, geo_bbox, time_window, max_speed_mps, max_amount, max_rate — §5.7.2) are evaluated by the SDK directly; an evaluator is consulted ONLY for types the SDK does not natively understand. An evaluator returning `nil` allows the constraint; any other error rejects. To explicitly mark a constraint as requiring context the caller hasn't supplied, return a wrapped `ErrConstraintUnverifiable`. Registering a built-in type name (e.g. "geo_circle") is a no-op — the SDK's hard-coded evaluator wins.
type ConstraintType ¶
type ConstraintType string
ConstraintType is the discriminator for Constraint. Canonical v1 kinds:
const ( // ConstraintGeoCircle — valid only when the agent is within RadiusM of // (Lat, Lon). Haversine distance on WGS-84. ConstraintGeoCircle ConstraintType = "geo_circle" // ConstraintGeoPolygon — valid only when the agent is inside the polygon // defined by Points (at least 3 points, winding order irrelevant). ConstraintGeoPolygon ConstraintType = "geo_polygon" // ConstraintGeoBBox — valid only when the agent is inside the rectangular // bounding box [MinLat, MinLon] × [MaxLat, MaxLon], optionally with an // altitude floor/ceiling [MinAltM, MaxAltM]. Altitude bounds are ignored // if both are zero. ConstraintGeoBBox ConstraintType = "geo_bbox" // ConstraintTimeWindow — valid only when the current local time in TZ // falls within [Start, End]. Inclusive at both ends. End < Start means // the window wraps midnight. ConstraintTimeWindow ConstraintType = "time_window" // ConstraintMaxSpeedMps — the agent's current velocity must not exceed // MaxMps meters per second. Verifier requires current-speed in context. ConstraintMaxSpeedMps ConstraintType = "max_speed_mps" // ConstraintMaxAmount — the requested transaction amount must not exceed // MaxAmount in Currency. Verifier requires (amount, currency) in context. ConstraintMaxAmount ConstraintType = "max_amount" // ConstraintMaxRate — across a rolling WindowS seconds, at most Count // exercises of this cert are allowed. Verifier requires a rate-counter // callback in context. ConstraintMaxRate ConstraintType = "max_rate" )
type DelegationCert ¶
type DelegationCert struct {
CertID string `json:"cert_id"`
Version int `json:"version"` // = ProtocolVersion
IssuerID string `json:"issuer_id"`
IssuerPubKey HybridPublicKey `json:"issuer_pub_key"`
SubjectID string `json:"subject_id"`
SubjectPubKey HybridPublicKey `json:"subject_pub_key"`
Scope []string `json:"scope"`
// Constraints may be empty but is always serialized — canonical form
// requires `"constraints":[]` when none are declared, so signatures are
// deterministic across issuers.
Constraints []Constraint `json:"constraints"`
IssuedAt int64 `json:"issued_at"`
ExpiresAt int64 `json:"expires_at"`
Signature HybridSignature `json:"signature"`
}
DelegationCert is a signed certificate granting an agent permission to act on behalf of a principal within defined scopes, a time window, and optionally an enumerated set of first-class Constraints (geo, time-of-day, speed, amount, rate). The scope answers "*what* may the agent do"; the constraints answer "*where/when/how much*."
The Signature is a hybrid pair; both component signatures must verify independently against the IssuerPubKey for the cert to be accepted.
type HumanRoot ¶
type HumanRoot struct {
// ID is hex(SHA-256(ed25519_pub || ml_dsa_65_pub)[:16]) — lowercase hex.
ID string `json:"id"`
PublicKey HybridPublicKey `json:"public_key"`
CreatedAt int64 `json:"created_at"`
Anchors []Anchor `json:"anchors,omitempty"`
}
HumanRoot is the master identity for a human (or tenant admin, or any party that can delegate to agents). Only the public component travels on the wire. Private keys SHOULD be stored on the owner's device where possible (self-custody mode). Custodial deployments where a registry operator holds envelope-encrypted keys are a valid deployment mode with different trust assumptions — see SPEC.md §15.2.
type HybridPrivateKey ¶
type HybridPrivateKey struct {
Ed25519 ed25519.PrivateKey // 64 bytes (seed || pub)
MLDSA65 *mldsa65.PrivateKey // internal representation
}
HybridPrivateKey holds both component private keys. Both are required to sign. The public component is derived by Public() and matches what goes into a HybridPublicKey.
func (HybridPrivateKey) Public ¶
func (k HybridPrivateKey) Public() HybridPublicKey
Public returns the HybridPublicKey component of this private keypair.
type HybridPublicKey ¶
type HybridPublicKey struct {
Ed25519 []byte `json:"ed25519"` // 32 bytes
MLDSA65 []byte `json:"ml_dsa_65"` // 1952 bytes (FIPS 204 ML-DSA-65)
}
HybridPublicKey pairs an Ed25519 public key with an ML-DSA-65 public key. Both are used for verification; a signature is accepted only if both component signatures verify against their respective component public keys.
Canonical JSON form (keys in lex order):
{"ed25519":"<base64-32-bytes>","ml_dsa_65":"<base64-1952-bytes>"}
type HybridSignature ¶
type HybridSignature struct {
Ed25519 []byte `json:"ed25519"` // 64 bytes
MLDSA65 []byte `json:"ml_dsa_65"` // 3309 bytes (FIPS 204 ML-DSA-65)
}
HybridSignature is an Ed25519 signature paired with an ML-DSA-65 signature over the same canonical bytes. Both MUST verify for the signature to be considered valid. Resists both classical cryptanalytic advances on either algorithm and quantum attacks (via ML-DSA-65's lattice-based security).
func SignChallenge ¶
func SignChallenge(challenge []byte, ts int64, agentPriv HybridPrivateKey) (HybridSignature, error)
SignChallenge signs a challenge to prove agent liveness. sign_data = challenge_bytes || big-endian uint64(unix_timestamp). Both component signatures are produced. Use SignChallengeWithSessionContext for v1.1 verifier/session-bound challenges.
func SignChallengeWithSessionContext ¶
func SignChallengeWithSessionContext(challenge []byte, ts int64, sessionContext []byte, agentPriv HybridPrivateKey) (HybridSignature, error)
SignChallengeWithSessionContext signs a v1.1 session-bound challenge. sessionContext MUST be exactly 32 bytes and is appended to the v1 challenge signable bytes: challenge || big-endian uint64(ts) || session_context.
func SignChallengeWithStream ¶
func SignChallengeWithStream(challenge []byte, ts int64, sessionContext, streamID []byte, streamSeq int64, agentPriv HybridPrivateKey) (HybridSignature, error)
SignChallengeWithStream signs a v1.1 stream-bound challenge. sessionContext may be nil (stream-only binding) or exactly 32 bytes (both session- and stream-bound). streamID MUST be exactly 32 bytes; streamSeq MUST be ≥1. The signable bytes append streamID and big-endian int64(streamSeq) after the session_context position.
type KeyRotationStatement ¶
type KeyRotationStatement struct {
Version int `json:"version"` // = ProtocolVersion
OldID string `json:"old_id"`
OldPubKey HybridPublicKey `json:"old_pub_key"`
NewID string `json:"new_id"`
NewPubKey HybridPublicKey `json:"new_pub_key"`
RotatedAt int64 `json:"rotated_at"`
Reason string `json:"reason"`
SignatureOld HybridSignature `json:"signature_old"`
SignatureNew HybridSignature `json:"signature_new"`
}
KeyRotationStatement links an old root key to a new root key. Both keys sign the same canonical bytes: the old key authorizes continuity, and the new key proves possession. This is backward-compatible with v1 certs; it is consumed by registries/auditors that need continuity across root rotations.
type PolicyProvider ¶
type PolicyProvider interface {
EvaluatePolicy(bundle *ProofBundle, context VerifierContext) (bool, error)
}
PolicyProvider evaluates application-level policy that exceeds the deterministic constraint logic defined in SPEC §5.7.2.
type PolicyVerdict ¶
type PolicyVerdict struct {
Version int `json:"version"` // = ProtocolVersion
VerdictID string `json:"verdict_id"`
AgentID string `json:"agent_id"`
Scope string `json:"scope"` // the specific scope this verdict allows
Allow bool `json:"allow"` // false = explicit cached deny
ContextHash []byte `json:"context_hash"` // 32 bytes — SHA-256 of canonical VerifierContext
IssuedAt int64 `json:"issued_at"`
ValidUntil int64 `json:"valid_until"`
MAC []byte `json:"mac"` // 32 bytes — HMAC-SHA256 over canonical signable bytes
}
PolicyVerdict is a v1.1 verifier-cached policy decision: a short-lived HMAC-bound attestation that a given (agent_id, scope, context_hash) tuple passed advanced policy evaluation at a specific moment (SPEC §17.6). It is the policy equivalent of `SessionToken`: instead of caching the result of a full *chain* verification, it caches the result of a `PolicyProvider` evaluation — letting subsequent calls within ValidUntil accept the cached allow/deny without re-calling the policy backend.
MAC semantics are identical to `SessionToken`:
mac = HMAC-SHA256(policy_secret, PolicyVerdictSignBytes(verdict))
`policy_secret` is private to whoever issued the verdict — a commercial backend typically holds it, gives only public verdict objects to the verifier, and rotates the secret to invalidate stale verdicts globally.
`ContextHash` is the canonical SHA-256 of the VerifierContext used during the original evaluation. If a verifier later runs with a different context, the verdict no longer applies — preventing a verdict cached for one context from leaking into another (e.g. different country, different amount tier).
func IssuePolicyVerdict ¶
func IssuePolicyVerdict( verdictID, agentID, scope string, allow bool, contextHash []byte, issuedAt, validUntil int64, policySecret []byte, ) (*PolicyVerdict, error)
IssuePolicyVerdict constructs and HMAC-binds a PolicyVerdict. Typically called by a commercial policy backend: it makes the allow/deny decision, stamps the verdict, and hands it to the verifier — which can then accept the verdict locally for the rest of `validUntil` without re-calling the backend. `policySecret` MUST be cryptographically random (≥32 bytes) and private to the issuing service.
type ProofBundle ¶
type ProofBundle struct {
AgentID string `json:"agent_id"`
AgentPubKey HybridPublicKey `json:"agent_pub_key"`
Delegations []DelegationCert `json:"delegations"` // [leaf, ..., root], depth 1..MaxDelegationChainDepth
Challenge []byte `json:"challenge"`
ChallengeAt int64 `json:"challenge_at"`
ChallengeSig HybridSignature `json:"challenge_sig"`
SessionContext []byte `json:"session_context,omitempty"` // optional 32-byte verifier/session binding
StreamID []byte `json:"stream_id,omitempty"` // optional 32-byte opaque stream identifier
StreamSeq int64 `json:"stream_seq,omitempty"` // optional monotonic sequence number (≥1 when StreamID is set)
}
ProofBundle is what an agent presents to a verifier. The challenge signature proves the agent's private key is live right now; the delegation chain proves the agent was authorized by a principal. Used symmetrically in human-agent and agent-agent interactions.
v1.1 optional stream binding: when StreamID and StreamSeq are populated, the bundle is "stream-bound" — it belongs to an ordered sequence of interactions sharing a StreamID. Both fields are signed into the challenge bytes (§6.4.2) so a proxy cannot replay, reorder, or omit bundles within the stream without invalidating the signature.
type ReceiptParty ¶
type ReceiptParty struct {
PartyID string `json:"party_id"`
Role string `json:"role"`
AgentID string `json:"agent_id"`
AgentPubKey HybridPublicKey `json:"agent_pub_key"`
ProofBundle ProofBundle `json:"proof_bundle"`
}
ReceiptParty is one party to a TransactionReceipt. Each party presents a ProofBundle that is verified independently by the generic verifier; only the identifying fields (`party_id`, `role`, `agent_id`, `agent_pub_key`) enter the signable. The proof bundle is therefore ambient evidence, not part of the cryptographic binding.
type ReceiptPartySignature ¶
type ReceiptPartySignature struct {
PartyID string `json:"party_id"`
Signature HybridSignature `json:"signature"`
}
ReceiptPartySignature carries the hybrid signature by party `party_id`'s agent key over the canonical signable. The `party_id` acts as the index back into the `Parties` array — verifiers MUST reject duplicate party_id signatures and signatures for party_ids absent from `Parties`.
func SignTransactionReceiptParty ¶
func SignTransactionReceiptParty(receipt *TransactionReceipt, partyID string, agentPriv HybridPrivateKey) (ReceiptPartySignature, error)
SignTransactionReceiptParty produces a party's hybrid signature over the receipt's canonical signable bytes. Use once per party with that party's agent private key; collect the resulting ReceiptPartySignature into TransactionReceipt.PartySignatures before emitting the receipt.
type RevocationList ¶
type RevocationList struct {
IssuerID string `json:"issuer_id"`
UpdatedAt int64 `json:"updated_at"`
RevokedCerts []string `json:"revoked_certs"`
Signature HybridSignature `json:"signature"`
}
RevocationList is a signed list of revoked CertIDs, served by the issuer (or by a registry acting on the issuer's behalf). Verifiers should cache with a short TTL and fail-closed on sustained unreachability.
type RevocationProvider ¶
RevocationProvider determines if a certificate ID is currently revoked. (SPEC §17.1)
type RevocationPush ¶
type RevocationPush struct {
IssuerID string `json:"issuer_id"`
SeqNo int64 `json:"seq_no"` // monotonically increasing; first push is 1
Entries []string `json:"entries"` // cert IDs being revoked in this push
PushedAt int64 `json:"pushed_at"` // unix seconds
Signature HybridSignature `json:"signature"`
}
RevocationPush is a v1.1 signed notification payload that a revocation-list issuer sends to subscribed verifiers in real time. The payload itself is hybrid-signed by the issuer, so a verifier can trust it without re-fetching the full revocation list. Verifiers that cannot maintain long-lived subscriptions (edge, serverless) continue using the pull model; this is for verifiers that need sub-second revocation propagation.
The Entries field carries the delta — cert IDs newly added to the revocation list since the previous push. A verifier applies the delta to its local revocation cache. The SeqNo field is monotonically increasing per issuer so receivers can detect missed pushes and fall back to a full list fetch.
type SessionToken ¶
type SessionToken struct {
Version int `json:"version"` // = ProtocolVersion
SessionID string `json:"session_id"`
AgentID string `json:"agent_id"`
AgentPubKey HybridPublicKey `json:"agent_pub_key"`
HumanID string `json:"human_id"`
GrantedScope []string `json:"granted_scope"` // sorted lex
IssuedAt int64 `json:"issued_at"`
ValidUntil int64 `json:"valid_until"`
ChainHash []byte `json:"chain_hash"` // 32 bytes — SHA-256 of concatenated delegation sign bytes
MAC []byte `json:"mac"` // 32 bytes — HMAC-SHA256 over canonical signable bytes
}
SessionToken is a v1.1 verifier-issued credential that caches the result of a full chain verification for the lifetime of a session. After a verifier has fully verified a ProofBundle once (hybrid signatures on every cert, freshness, scope, constraints, etc.), it MAY issue a SessionToken that binds the verified chain and agent to the verifier's session. Subsequent turns present the token alongside a fresh ChallengeSig; the verifier recomputes and checks the token's HMAC, confirms validity, and verifies the hybrid challenge signature — avoiding full chain re-verification.
MAC = HMAC-SHA256(session_secret, SessionTokenSignBytes(token)). Token lifetime is bounded by ValidUntil. ChainHash binds the token to the exact chain the verifier accepted, so a cert rotation invalidates every previously issued token.
The session_secret is private to the verifier (never leaves the verifier's trust boundary). Tokens do not travel beyond a single verifier's scope.
func IssueSessionToken ¶
func IssueSessionToken(bundle *ProofBundle, result VerifyResult, sessionID string, issuedAt, validUntil int64, sessionSecret []byte) (*SessionToken, error)
IssueSessionToken constructs a SessionToken from a verified bundle and the verifier's session parameters. The caller MUST only invoke this after Verify(bundle, opts) returned Valid=true — nothing in this function re- checks the chain. sessionSecret MUST be a cryptographically random secret (≥32 bytes recommended) known only to the verifier.
type StreamContext ¶
type StreamContext struct {
StreamID []byte // 32 bytes
LastSeenSeq int64 // ≥ 0; first expected seq is 1
}
StreamContext tracks the state of an ordered interaction stream.
type TransactionReceipt ¶
type TransactionReceipt struct {
Version int `json:"version"` // = ProtocolVersion
TransactionID string `json:"transaction_id"`
CreatedAt int64 `json:"created_at"`
TermsSchemaURI string `json:"terms_schema_uri"`
TermsCanonicalJSON []byte `json:"terms_canonical_json"`
Parties []ReceiptParty `json:"parties"`
PartySignatures []ReceiptPartySignature `json:"party_signatures"`
}
TransactionReceipt is the v1.1 canonical envelope for a multi-party, atomic transaction. Every listed party signs the same signable (version, transaction_id, created_at, terms_schema_uri, terms_canonical_json, sorted party set) so altering or omitting any party invalidates every other party's signature — no partial-valid receipt state exists.
Ratify does not interpret `TermsCanonicalJSON` (the business terms); the application owns that schema. `TermsSchemaURI` identifies which schema a specialized verifier would dispatch on. Ratify guarantees the envelope atomicity and party signatures.
type TransactionReceiptResult ¶
type TransactionReceiptResult struct {
Valid bool
ErrorReason string
PartyResults []VerifyResult
}
TransactionReceiptResult is the generic envelope-verifier outcome. Valid iff every envelope check in SPEC §5.14 passes AND every party's ProofBundle produced Valid=true from Verify. ErrorReason carries the first envelope- or party-level failure encountered; PartyResults captures the per-party VerifyResult in Parties-order for callers that want audit detail.
func VerifyTransactionReceipt ¶
func VerifyTransactionReceipt(receipt *TransactionReceipt, opts VerifyReceiptOptions) TransactionReceiptResult
VerifyTransactionReceipt runs the canonical envelope verification of SPEC §5.14 / TRANSACTION_RECEIPTS.md §5. The envelope is atomic: any single-party failure fails the whole receipt, there is no partial-valid state.
type VerificationReceipt ¶
type VerificationReceipt struct {
Version int `json:"version"` // = ProtocolVersion
VerifierID string `json:"verifier_id"` // derived ID of verifier's signing key
VerifierPub HybridPublicKey `json:"verifier_pub"` // the key used to sign this receipt
BundleHash []byte `json:"bundle_hash"` // SHA-256 of canonical bundle bytes
Decision string `json:"decision"` // identity_status from VerifyResult
HumanID string `json:"human_id,omitempty"`
AgentID string `json:"agent_id,omitempty"`
GrantedScope []string `json:"granted_scope,omitempty"`
ErrorReason string `json:"error_reason,omitempty"`
VerifiedAt int64 `json:"verified_at"` // unix seconds
PrevHash []byte `json:"prev_hash"` // 32 bytes; zeros for genesis
Signature HybridSignature `json:"signature"` // hybrid sig over canonical bytes
}
VerificationReceipt is a verifier-signed attestation that a specific `ProofBundle` was verified at a specific moment with a specific outcome (SPEC §17.5). It is the cryptographic complement of `AuditProvider`: an AuditProvider chooses what to do with verification events; a VerificationReceipt makes the event itself unforgeable, so any auditor — even one that doesn't trust the verifier operator — can independently confirm the verifier saw what it claims it saw.
Receipts chain by `prev_hash` so a missing or backdated receipt is detectable: an immutable verification log is just a sequence of `VerificationReceipt`s where each one's `prev_hash` is the SHA-256 of the previous receipt's canonical signable bytes. Genesis uses 32 zero bytes.
Receipts are OPTIONAL — the protocol does not auto-issue them. SDKs ship `IssueVerificationReceipt` and `VerifyVerificationReceipt` helpers, and downstream `AuditProvider` implementations may choose to wrap each `VerifyResult` in a receipt before persisting it. Wire format is unchanged whether receipts are issued or not.
func IssueVerificationReceipt ¶
func IssueVerificationReceipt( bundle *ProofBundle, result VerifyResult, verifierID string, verifierPub HybridPublicKey, verifierPriv HybridPrivateKey, prevHash []byte, verifiedAt int64, ) (*VerificationReceipt, error)
IssueVerificationReceipt constructs and signs a VerificationReceipt over a (bundle, VerifyResult, prev) triple. The verifier's hybrid private key authenticates "this verifier saw this bundle, and reached this decision, at this time." `prevHash` is the SHA-256 digest of the previous receipt's canonical signable bytes — pass 32 zero bytes for genesis. `verifiedAt` is unix seconds (use the same clock as the verifier).
The receipt is OPTIONAL — the protocol does not auto-issue. AuditProvider implementations that want a tamper-evident chain wrap this around each VerifyResult before persisting; implementations that don't, don't.
type VerifierContext ¶
type VerifierContext struct {
// CurrentLat / CurrentLon / CurrentAltM — agent's current position.
// Required by geo_circle, geo_polygon, geo_bbox.
CurrentLat float64
CurrentLon float64
CurrentAltM float64
HasLocation bool
// CurrentSpeedMps — agent's current velocity in meters per second.
// Required by max_speed_mps.
CurrentSpeedMps float64
HasSpeed bool
// RequestedAmount / RequestedCurrency — the transaction being authorized.
// Required by max_amount.
RequestedAmount float64
RequestedCurrency string
HasAmount bool
// InvocationsInWindow looks up how many times this cert has been
// exercised in the most recent `window` seconds. Required by max_rate.
InvocationsInWindow func(certID string, windowS int64) int
}
VerifierContext carries the application-supplied inputs needed to evaluate first-class constraints at verify time. Fields are optional individually, but a cert bearing a constraint whose required context is absent will be rejected (fail-closed, v1 semantics).
type VerifyOptions ¶
type VerifyOptions struct {
// RequiredScope must be present in the effective scope for the proof to
// be valid. Empty string skips scope checking.
RequiredScope string
// IsRevoked is the legacy v1.0 revocation closure. Return true if the
// cert has been revoked; nil disables the check entirely.
//
// Deprecated: Use Revocation (SPEC §17.1) instead. The closure has no
// way to surface lookup failures — it must collapse "I don't know" to
// `false` (allow) or `true` (deny), neither of which is correct.
// Revocation returns `(bool, error)` and the verifier fails closed on
// error (`revocation_error`). When both are set, Revocation wins. The
// closure remains supported through v1.0.0-* releases and will be
// removed in v1.0.0-beta.1.
IsRevoked func(certID string) bool
// Revocation is the pluggable provider hook for revocation state
// (SPEC §17.1). If set, takes precedence over IsRevoked. A provider
// that returns an error fails the bundle as `revocation_error` —
// fail-closed semantics: a verifier that cannot determine revocation
// state MUST NOT report a cert as valid.
Revocation RevocationProvider
// Policy is an advanced policy evaluator hook (SPEC §17.2). Evaluated
// AFTER all cryptographic checks pass. A nil provider is a no-op.
Policy PolicyProvider
// Audit is a verification audit logging hook (SPEC §17.3). Called on
// every Verify invocation (both Valid=true and Valid=false). Errors
// from the audit provider are ignored — auditing MUST NOT alter the
// verifier's decision.
Audit AuditProvider
// ConstraintEvaluators is the per-Verify registry of extension
// constraint evaluators (SPEC §17.7). Keys are constraint type strings
// that are NOT in the built-in set (geo_circle, geo_polygon, geo_bbox,
// time_window, max_speed_mps, max_amount, max_rate). Built-in types
// are evaluated by the SDK directly; the registry is only consulted
// for unknown types. A type with no registered evaluator still fails
// closed with identity_status="constraint_unknown".
ConstraintEvaluators map[string]ConstraintEvaluator
// PolicyVerdict, when non-nil, is a fast-path cached policy decision
// (SPEC §17.6). When present, Verify skips the Policy provider hook
// IF the verdict is valid (MAC matches PolicySecret, within validity
// window, agent/scope/context-hash matches). If the verdict is
// expired or mismatched, the verifier falls back to Policy provider
// (or, if Policy is nil, treats the bundle as policy-passing).
PolicyVerdict *PolicyVerdict
// PolicySecret is the HMAC secret used to verify the PolicyVerdict's
// MAC. Required when PolicyVerdict is non-nil; otherwise ignored.
PolicySecret []byte
// AnchorResolver, when non-nil, is consulted on successful verifications
// to populate `VerifyResult.Anchor` (SPEC §17.8). It maps the verified
// HumanID to its external-identity binding (Okta SSO, government ID,
// email-verified, etc) so AuditProviders can record an unforgeable
// chain from the verification event to the identity attestation
// behind the human root. Errors from the resolver are non-fatal:
// the bundle still verifies; Anchor is simply left nil. A nil
// resolver disables the lookup entirely.
AnchorResolver AnchorResolver
// ForceRevocationCheck, when true, signals the verifier to bypass its
// local revocation cache and query the issuer (or registry) for the
// freshest revocation state before proceeding. This is the v1.1 "force
// fresh check" path for high-stakes endpoints that cannot tolerate the
// polling interval's staleness window (ROADMAP §2.4). The actual fresh-
// fetch is the caller's responsibility — the verifier protocol does not
// mandate a transport. When ForceRevocationCheck is true and IsRevoked
// is nil, the verifier returns "force_revocation_no_callback".
ForceRevocationCheck bool
// Now overrides the current time for verification (expiry, challenge
// age). Zero value uses time.Now().
Now time.Time
// Context carries the application-supplied inputs needed to evaluate
// first-class constraints (geo, time, etc).
Context VerifierContext
// SessionContext is a verifier-reconstructed 32-byte hash that binds a
// challenge to this specific verifier/session/request. When set, the
// bundle MUST carry a byte-equal session_context. Prevents cross-verifier
// challenge forwarding (SPEC §15.1).
SessionContext []byte
// Stream binds a verifier-tracked sequence context for v1.1 stream-bound
// bundles. Both StreamID and LastSeenSeq must be populated.
Stream *StreamContext
}
VerifyOptions controls what the verifier checks beyond the cryptographic basics.
type VerifyReceiptOptions ¶
type VerifyReceiptOptions struct {
// Now overrides the current time. Zero value uses time.Now().
Now time.Time
// PartyVerifyOptions returns the VerifyOptions a party's ProofBundle is
// checked against, keyed by party role. Callers typically configure
// required scopes per role (e.g. "buyer" requires payments:send, "seller"
// requires transact:sell). If nil or returns an empty VerifyOptions, the
// party's bundle is verified with defaults (no scope requirement) at
// VerifyReceiptOptions.Now. The option's Now field is ignored — the outer
// Now propagates for consistency.
PartyVerifyOptions func(role string) VerifyOptions
}
VerifyReceiptOptions controls the per-party verification work inside VerifyTransactionReceipt. Defaults produce a generic envelope check: each party's ProofBundle must fully verify, every declared party must have exactly one signature, and every party signature must verify against that party's agent_pub_key over the canonical signable. Applications add scope routing by setting PartyVerifyOptions.
type VerifyResult ¶
type VerifyResult struct {
Valid bool `json:"valid"`
HumanID string `json:"human_id,omitempty"`
AgentID string `json:"agent_id,omitempty"`
AgentName string `json:"agent_name,omitempty"`
AgentType string `json:"agent_type,omitempty"`
GrantedScope []string `json:"granted_scope,omitempty"`
// IdentityStatus is the closed-enum outcome of Verify. See the full
// table in SPEC §5.9 and the IdentityStatus* constants in verify.go.
// The current set is:
// authorized_agent, verified_human,
// expired, revoked,
// scope_denied, constraint_denied, constraint_unverifiable,
// constraint_unknown, delegation_not_authorized,
// invalid, unauthorized.
// Adding a new status is a spec change — never invent values locally.
IdentityStatus string `json:"identity_status"`
ErrorReason string `json:"error_reason,omitempty"`
// Anchor is the resolved external-identity binding for the
// HumanID (SPEC §17.8). Populated by Verify only when the caller
// supplies a `VerifyOptions.AnchorResolver` AND the bundle verifies.
// When set, downstream `AuditProvider`s can record "this verification
// was tied to an SSO-bound human at Okta," etc., giving compliance
// auditors a chain from VerificationReceipt → identity attestation.
// Nil when no resolver is configured or no Anchor was found.
Anchor *Anchor `json:"anchor,omitempty"`
}
VerifyResult is the deterministic output of Verify(). Always check Valid first; on success, inspect GrantedScope and AgentID.
func Verify ¶
func Verify(bundle *ProofBundle, opts VerifyOptions) VerifyResult
Verify validates a ProofBundle against the Ratify Protocol and returns a deterministic VerifyResult. Always returns a result; check result.Valid.
A single component failure (e.g. Ed25519 valid but ML-DSA-65 invalid, or vice versa) fails the whole signature — fail-closed is the v1 semantics.
func VerifyStreamedTurn ¶
func VerifyStreamedTurn(token *SessionToken, sessionSecret []byte, challenge []byte, challengeAt int64, challengeSig HybridSignature, sessionContext, streamID []byte, streamSeq int64, now time.Time) VerifyResult
VerifyStreamedTurn is the fast-path verifier for v1.1 session cert cache (ROADMAP 2.3). Given a previously issued SessionToken and a per-turn challenge signature, it:
- Checks the SessionToken's HMAC against sessionSecret.
- Checks the token is within [IssuedAt, ValidUntil] at `now`.
- Verifies the challenge is fresh (within ChallengeWindowSeconds).
- Verifies the hybrid challenge signature against token.AgentPubKey. The signable bytes may be legacy (challenge || ts) or session/stream-bound; callers pass the session_context and stream binding alongside.
On success, VerifyResult.Valid=true, GrantedScope=token.GrantedScope, AgentID=token.AgentID, HumanID=token.HumanID. The chain is NOT re-verified — that's the point of the token. Callers who need fresh revocation semantics should evict the token when the issuer publishes a new revocation list or when token.ValidUntil expires.
type WitnessEntry ¶
type WitnessEntry struct {
PrevHash []byte `json:"prev_hash"` // 32 bytes; zeros for genesis
EntryData []byte `json:"entry_data"` // the receipt/cert/revocation being witnessed
Timestamp int64 `json:"timestamp"`
WitnessID string `json:"witness_id"`
Signature HybridSignature `json:"signature"`
}
WitnessEntry is a v1.1 spec-defined element in a hash-chain append-only log. Any party may operate a Witness: Identities AI, an enterprise's own audit system, a third-party notary, or a blockchain-anchored system. Multiple witnesses MAY independently log the same events (redundancy).
v1.1 defines the shape only. Operating a scalable witness is an implementation concern; the spec does not mandate deployment topology.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
ratify
command
ratify-cli — Ratify Protocol command-line tool.
|
ratify-cli — Ratify Protocol command-line tool. |
|
ratify-testvectors
command
Command ratify-testvectors regenerates the canonical Ratify Protocol v1 test vectors from fixed seeds.
|
Command ratify-testvectors regenerates the canonical Ratify Protocol v1 test vectors from fixed seeds. |
|
ratify-verifier
command
Command ratify-verifier is a minimal HTTP reference verifier for the Ratify Protocol.
|
Command ratify-verifier is a minimal HTTP reference verifier for the Ratify Protocol. |
|
demos
|
|
|
go
command
Ratify Protocol v1 — end-to-end narrative demo (Go).
|
Ratify Protocol v1 — end-to-end narrative demo (Go). |