Go SDK
The Go SDK is the reference implementation of the Ratify Protocol. Every code path you’ll see here is the same path that emits the conformance fixtures every other SDK (TypeScript, Python, Rust) is validated against.
Stability: every primitive on this page is stable in v1.0.0-alpha.7. There are no
preview / experimental APIs in the snippets below.
Install
Section titled “Install”Module: github.com/identities-ai/ratify-protocol. Go 1.22+.
Three minutes, end to end
Section titled “Three minutes, end to end”This is the entire protocol in one file: Alice creates her root identity, signs a delegation to her agent, the agent builds a proof bundle, and a verifier runs the verifier algorithm. No HTTP, no servers — just function calls.
package main
import ( "fmt" "time"
"github.com/identities-ai/ratify-protocol")
func main() { // 1. Alice generates her hybrid (Ed25519 + ML-DSA-65) root identity. alice, alicePriv, err := ratify.GenerateHumanRootKeypair() if err != nil { panic(err) }
// 2. Her AI agent generates its own hybrid keypair. agent, agentPriv, err := ratify.GenerateAgentKeypair("Alice's Scheduler", "custom") if err != nil { panic(err) }
// 3. Alice signs a delegation cert for the agent. now := time.Now().Unix() cert := &ratify.DelegationCert{ CertID: "cert-001", Version: ratify.ProtocolVersion, IssuerID: alice.ID, IssuerPubKey: alice.PublicKey, SubjectID: agent.ID, SubjectPubKey: agent.PublicKey, Scope: []string{ratify.ScopeMeetingAttend, ratify.ScopeMeetingSpeak}, IssuedAt: now, ExpiresAt: now + 7*24*3600, } if err := ratify.IssueDelegation(cert, alicePriv); err != nil { panic(err) }
// 4. Verifier issues a challenge. Agent signs it. challenge, err := ratify.GenerateChallenge() if err != nil { panic(err) } challengeAt := time.Now().Unix() challengeSig, err := ratify.SignChallenge(challenge, challengeAt, agentPriv) if err != nil { panic(err) }
// 5. Agent assembles a proof bundle. Note: Delegations is a slice of // values, not pointers — the SDK takes []DelegationCert. bundle := &ratify.ProofBundle{ AgentID: agent.ID, AgentPubKey: agent.PublicKey, Delegations: []ratify.DelegationCert{*cert}, Challenge: challenge, ChallengeAt: challengeAt, ChallengeSig: challengeSig, }
// 6. Verifier runs the verifier in a single call. result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopeMeetingAttend, })
if result.Valid { fmt.Printf("✓ Authorized\n") fmt.Printf(" human_id: %s\n", result.HumanID) fmt.Printf(" agent_id: %s\n", result.AgentID) fmt.Printf(" granted_scope: %v\n", result.GrantedScope) fmt.Printf(" identity_status: %s\n", result.IdentityStatus) } else { fmt.Printf("✗ Rejected: %s — %s\n", result.IdentityStatus, result.ErrorReason) }}Run it with go run main.go. Output (human_id and agent_id are derived from the freshly
generated keys, so they’ll differ on every run):
✓ Authorized human_id: 2254340cfd7bd2058c2813bb381a98e7 agent_id: 6fc06ca1155498bff675a6048da797af granted_scope: [meeting:attend meeting:speak] identity_status: authorized_agentThe full sequence, illustrated
Section titled “The full sequence, illustrated” ┌─────────────┐ ┌─────────────┐ │ Alice │ 1. GenerateHumanRootKeypair │ Verifier │ │ │ 2. IssueDelegation(cert) │ │ │ private │ │ │ │ key on │ │ │ │ her dev │ │ │ └──────┬──────┘ └─────▲───────┘ │ │ │ cert (signed) │ 6. Verify(bundle) ▼ │ → result.Valid ┌─────────────┐ ┌─────┴───────┐ │ AI Agent │ 3. GenerateAgentKeypair │ │ │ │ 4. SignChallenge(nonce) │ │ │ cert + │ 5. ProofBundle{...} │ │ │ own keys │ ────────────────────────────────▶│ │ └─────────────┘ └─────────────┘
Alice's private key never leaves her device. Agent never sees Alice's private key. Verifier never sees any private key — only public keys + signatures.Verifier-side: branching on IdentityStatus
Section titled “Verifier-side: branching on IdentityStatus”Verify always returns a VerifyResult. Inspect result.Valid first; if it’s false, the
specific failure mode is in result.IdentityStatus. The constants live in the ratify
package as IdentityStatus*:
result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopeMeetingAttend,})
switch result.IdentityStatus {case ratify.IdentityStatusAuthorizedAgent: // ✓ All checks passed. result.GrantedScope is the intersection of every // cert's scope (effective scope across the chain).
case ratify.IdentityStatusExpired: // ✗ At least one cert in the chain is past its expires_at, OR not-yet-valid // (now < issued_at).
case ratify.IdentityStatusRevoked: // ✗ A cert ID in the chain matched a revoked entry returned by your // Revocation provider (or legacy IsRevoked closure).
case ratify.IdentityStatusScopeDenied: // ✗ RequiredScope was not in the chain's effective (intersected) scope. // Also returned when an advanced PolicyProvider rejects the bundle.
case ratify.IdentityStatusConstraintDenied: // ✗ A first-class constraint (geo/time/amount/rate) on some cert // evaluated to "outside the allowed range".
case ratify.IdentityStatusConstraintUnverifiable: // ✗ A constraint required input the caller didn't provide // (e.g. cert has geo_circle but VerifierContext.HasLocation == false).
case ratify.IdentityStatusConstraintUnknown: // ✗ A cert declared a constraint Type this SDK build doesn't recognize. // Fail-closed by design — future-cert defense.
case ratify.IdentityStatusDelegationNotAuthorized: // ✗ An intermediate cert in the chain sub-delegated, but its parent // never granted identity:delegate. The sub-delegation gate.
case ratify.IdentityStatusInvalid: // ✗ Catch-all for structural / cryptographic failures: bad signature, // broken chain linkage, malformed key, wrong version, etc. // result.ErrorReason carries the specific code (e.g. "bad_signature", // "broken_chain", "stale_challenge", "key_mismatch").}The full list of constants lives in
verify.go — and the
IdentityStatusInvalid failure modes have stable, machine-readable reason codes inside
ErrorReason (e.g. stale_challenge: challenge is 31 seconds old (max 300)), so your
audit pipeline can route on those without parsing prose.
The verifier is fail-closed by default. Any error path returns Valid: false — you cannot
accidentally accept a malformed bundle.
Constraints (geo / time / amount / rate / speed)
Section titled “Constraints (geo / time / amount / rate / speed)”If a delegation has constraints, supply the runtime context in VerifyOptions.Context:
result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopeDroneDeliver, Context: ratify.VerifierContext{ HasLocation: true, CurrentLat: 37.7749, CurrentLon: -122.4194, },})The Has* flags on VerifierContext are authoritative: when false, the corresponding
numeric fields are zeroed in the canonical context hash (this is what keeps PolicyVerdict
caching portable across SDKs). Setting CurrentLat = 37 without HasLocation = true is
equivalent to “no location supplied.”
Real output for the three branches of a geo_circle constraint:
Inside circle: valid=true status=authorized_agent
50 km away: valid=false status=constraint_denied reason=constraint_denied: cert 0: constraint[0] (geo_circle): outside allowed radius: 67574.0m > 5000.0m
No location supplied: valid=false status=constraint_unverifiable reason=constraint_unverifiable: cert 0: constraint[0] (geo_circle): constraint_unverifiable: no current location in contextThe full constraint vocabulary (geo_circle, geo_polygon, geo_bbox, time_window,
max_speed_mps, max_amount, max_rate) and their VerifierContext field requirements
are documented in Constraints.
Sub-delegation
Section titled “Sub-delegation”Agent-to-agent delegation uses the same primitive. Alice grants Agent A meeting:attend plus
the identity:delegate privilege; A then issues a sub-delegation to Agent B. The
sub-delegation gate is intentional — without identity:delegate on A’s grant from Alice, A
cannot mint chains that B can use.
// Alice → A: meeting:attend + identity:delegatealiceToA := &ratify.DelegationCert{ CertID: "cert-alice-to-a", Version: ratify.ProtocolVersion, IssuerID: alice.ID, IssuerPubKey: alice.PublicKey, SubjectID: agentA.ID, SubjectPubKey: agentA.PublicKey, Scope: []string{ratify.ScopeMeetingAttend, ratify.ScopeIdentityDelegate}, IssuedAt: now, ExpiresAt: now + 86400,}_ = ratify.IssueDelegation(aliceToA, alicePriv)
// A → B: must be a subset of A's grantaToB := &ratify.DelegationCert{ CertID: "cert-a-to-b", Version: ratify.ProtocolVersion, IssuerID: agentA.ID, IssuerPubKey: agentA.PublicKey, SubjectID: agentB.ID, SubjectPubKey: agentB.PublicKey, Scope: []string{ratify.ScopeMeetingAttend}, IssuedAt: now, ExpiresAt: now + 3600,}_ = ratify.IssueDelegation(aToB, agentAPriv)
// Bundle: chain order is [leaf, ..., root]. A→B is the leaf, Alice→A is the root.bundle := &ratify.ProofBundle{ AgentID: agentB.ID, AgentPubKey: agentB.PublicKey, Delegations: []ratify.DelegationCert{*aToB, *aliceToA}, Challenge: challenge, ChallengeAt: challengeAt, ChallengeSig: agentBSig,}
result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopeMeetingAttend,})Real output:
Valid: true status: authorized_agent human_id: b11b8196e2cf7bc0dd6853065eddd1f8 (Alice) agent_id: c814242c6ad075698554ce93e0e391e3 (Agent B) granted_scope: [meeting:attend]Notice the effective scope is [meeting:attend] only — identity:delegate doesn’t propagate
because A didn’t grant it onward. The effective scope of any chain is the lex-sorted
intersection across every cert.
Provider hooks (SPEC §17)
Section titled “Provider hooks (SPEC §17)”Beyond the deterministic verifier core, VerifyOptions exposes pluggable hooks for
revocation, policy, audit, anchor resolution, and extension constraint evaluators. They are
all stable in alpha.7; full reference at
Provider architecture.
result := ratify.Verify(bundle, ratify.VerifyOptions{ RequiredScope: ratify.ScopeMeetingAttend, Revocation: myRevocationProvider, // §17.1 Policy: myPolicyProvider, // §17.2 Audit: myAuditProvider, // §17.3 ConstraintEvaluators: myExtensionRegistry, // §17.7 AnchorResolver: myAnchorResolver, // §17.8 Context: ctx,})Each hook has fail-closed semantics — a RevocationProvider that returns an error fails the
bundle with IdentityStatusInvalid + error_reason: revocation_error: ..., not a silent
“unknown means allow.” Audit hook errors are deliberately swallowed (auditing must not alter
the verdict).
Running the conformance suite
Section titled “Running the conformance suite”git clone https://github.com/identities-ai/ratify-protocolcd ratify-protocolgo test ./...If you see ok github.com/identities-ai/ratify-protocol followed by the per-fixture pass
log, all 59 canonical wire-format fixtures plus the 10 alpha.7 cross-SDK byte-equivalence
vectors passed. Your local build is byte-for-byte interoperable with every other Ratify SDK
at the same version.
The repo ships a ratify CLI for human use — create identities, sign delegations, verify
bundles, list the canonical scopes.
go build -o /tmp/ratify ./cmd/ratify/tmp/ratify --helpCommon workflows:
# Create a HumanRoot identity (writes to ~/.ratify/)ratify init
# Sign a delegation cert. The agent pubkey JSON must be exported by the# `ratify agent-init` testing command or generated programmatically.ratify delegate \ --agent-pubkey-file agent-pubkey.json \ --scope "meeting:attend,meeting:speak" \ --days 7 \ --out delegation.json
# Verify a bundle (note: --bundle is required; no positional argument)ratify verify --bundle bundle.json
# Print the canonical 53-scope vocabularyratify scopes
# Show this device's identity stateratify statusratify agent-init and ratify challenge are agent-side helpers for end-to-end testing
without writing Go code; see ratify --help for the full surface.
Where to next
Section titled “Where to next”- Protocol concepts: Delegate → Present → Verify — every primitive in depth
- Constraints — full constraint vocabulary +
VerifierContextreference - Revocation — signed revocation lists, push revocation, and the §17.1
RevocationProviderhook - Provider architecture — the §17 hooks plus the alpha.7 levers (
VerificationReceipt,PolicyVerdict) - Integration guides — surface-specific patterns (Zoom / Teams / Twilio / API Gateway)