ratify

package module
v1.0.0-alpha.7 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

Ratify Protocol

Ratify Protocol™

A cryptographic trust protocol for human-to-agent and agent-to-agent interactions.

CI Release Patent Pending

Go Reference PyPI Crates.io npm

When a human authorizes an AI agent — or when one agent transacts with another agent — Ratify produces a signed, verifiable proof that says exactly who authorized what, within which bounds, and for how long. Any party in the conversation can check that proof in under a millisecond, across voice, video, API, and Physical AI.

Quantum-safe by design. Every signature is hybrid: Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify. Bundles signed today remain unforgeable even when cryptographically-relevant quantum computers exist.

JSON wire format. No blockchain. No tokens. No central issuer. Open spec under CC-BY-4.0.

Status: v1.0.0-alpha.7 · reference implementation complete · 59 canonical test vectors · cross-language interop proven (Go + TypeScript + Python + Rust) · Patent Pending.

Maintained by Identities AI, Inc. Ratify Protocol™ and identities.ai™ are trademarks of Identities AI, Inc.


Table of contents


The mental model

The protocol replaces "the AI says so" with "prove it."

Today, when an AI agent shows up — joining a meeting, calling your support line, sending an email, executing a trade on your behalf — the receiving party has no cryptographic way to verify three things:

  1. Who authorized this agent to act?
  2. What is this agent allowed to do?
  3. For how long is that authority valid?

Ratify answers those three questions with a single primitive — a signed delegation certificate paired with a fresh challenge signature — that any verifier can check offline, in under a millisecond, with no live call to a central authority.

Same primitive works for humans authorizing agents and agents sub-authorizing other agents. Same verifier algorithm in every direction. That symmetry is what makes Ratify a protocol rather than a product.


The three verbs

   DELEGATE                  PRESENT                   VERIFY
   ────────                  ───────                   ──────
   Principal signs a         Presenter (agent)         Any third party
   DelegationCert            carries the cert          runs the verifier.
   naming the subject,       and signs a fresh         Both Ed25519 AND
   the scopes, and the       challenge on every        ML-DSA-65 must
   expiration.               interaction.              verify. Yes/no
                                                       in <1ms. No trust
   Human → Agent OR          Proves "this key is       relationship with
   Agent → Agent.            live right now."          presenter required.

Symmetric in both directions. A human delegating to an AI agent and one AI agent sub-delegating to another use the exact same primitive, the same verifier algorithm, and the same cryptographic guarantees.


How the bytes flow

Concrete picture of one full interaction:

                  ┌───────────────────────────────────┐
                  │   Alice (Principal)               │
                  │                                   │
                  │   Holds: Hybrid private key       │
                  │   ▸ Ed25519 + ML-DSA-65           │
                  │   Key never leaves Alice's        │
                  │   device.                         │
                  └───────────────┬───────────────────┘
                                  │
                                  │ 1. signs DelegationCert {
                                  │      issuer:    Alice
                                  │      subject:   Agent-A
                                  │      scope:     ["meeting:attend"]
                                  │      expires:   +7 days
                                  │      hybrid signature
                                  │    }
                                  ▼
                  ┌───────────────────────────────────┐
                  │   Agent-A (Subject)               │
                  │                                   │
                  │   Holds: Cert + own hybrid key    │
                  └───────────────┬───────────────────┘
                                  │
                                  │ 3. presents ProofBundle {
                                  │      delegations: [cert]
                                  │      challenge:   nonce-from-verifier
                                  │      challenge_sig: hybrid signature
                                  │                     over (nonce, time)
                                  │    }
                                  ▼
                  ┌───────────────────────────────────┐
   2. issues      │   Verifier (Zoom / your API /     │
   challenge ───▶ │   any third party)                │
                  │                                   │
                  │   ✓ both signatures verify        │
                  │   ✓ cert not expired              │
                  │   ✓ challenge fresh (<5 min)      │
                  │   ✓ scope covers requested action │
                  │   ✓ cert not revoked              │
                  │                                   │
                  │   → YES / NO in <1ms              │
                  └───────────────────────────────────┘

What's not in the picture (and why that matters): no central authority is consulted at verify time. The verifier needs only Alice's public key to check everything above. No OAuth introspection endpoint, no token registry call, no network hop. That is what makes Ratify deployable in offline environments (drones, vehicles, edge inference) and at internet scale (every Zoom call, every API call, every voice interaction).

For sub-delegation (Agent-A authorizing Agent-B), the bundle just carries two certs, and the verifier checks each link in the chain plus the scope intersection. Same verifier algorithm.


60-second install + verify

Pick your language. Each one runs the full 59-fixture conformance suite — the same fixtures every other SDK passes byte-for-byte. If you see "all passing," you've proven cross-language interop on your own machine.

Go

git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol
go test ./...
# → ok  github.com/identities-ai/ratify-protocol  0.5s

Or install as a module in your own project:

go get github.com/identities-ai/ratify-protocol@v1.0.0-alpha.7

TypeScript

git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol/sdks/typescript
npm install
npm run test:conformance
# → 59/59 fixtures pass

(npm package @identities-ai/ratify-protocol ships once the npm org is approved. Until then, install from source as above.)

Python

pip install ratify-protocol==1.0.0a7

Or to run the conformance suite yourself:

git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol/sdks/python
pip install -e '.[dev]'
pytest
# → 59 passed

Rust

cargo add ratify-protocol@1.0.0-alpha.7

Or to run the conformance suite yourself:

git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol/sdks/rust
cargo test
# → test result: ok. 1 passed (loads all 59 fixtures)

End-to-end demo — see the full protocol run

The conformance suite proves the bytes are correct. The demos prove the protocol does what it claims. Each runs the same nine-scenario narrative — five positive (authorized → verified), four negative (tampered / out-of-scope / expired / revoked) — and prints what happened and why.

Language Run from repo root
Go go run ./demos/go
Python cd sdks/python && pip install -e . && cd ../.. && python demos/python/demo.py
TypeScript cd sdks/typescript && npm install && npm run build && cd ../../demos/typescript && npm install && npm run demo
Rust cargo run --manifest-path demos/rust/Cargo.toml

What you'll see (abbreviated):

═══ Scenario 1: Authorized agent joins a meeting ═══
Alice creates her hybrid root identity ✓
Agent-A generates its hybrid keypair  ✓
Alice signs delegation:
  scope: [meeting:attend, meeting:speak]
  expires: 2026-05-17 18:00:00 UTC
Agent-A builds proof bundle with fresh challenge ✓
Verifier runs verify_bundle() → ✅ VALID
  effective scope: [meeting:attend, meeting:speak]

═══ Scenario 2: Attacker tampers cert scope after signing ═══
Attacker modifies cert.scope: [meeting:attend] → [meeting:record]
Verifier runs verify_bundle() → ❌ REJECTED
  identity_status: bad_signature
  reason: Ed25519 signature does not cover modified bytes

═══ Scenario 3: Agent presents wrong scope ═══
Agent-A holds cert for meeting:attend, requests meeting:record
Verifier runs verify_bundle(required_scope: meeting:record) → ❌ REJECTED
  identity_status: scope_denied
  reason: requested scope not in effective chain scope

═══ Scenario 4: Cert expired ═══
... (and so on)

This is what an alpha tester runs to convince themselves the protocol works the way the spec says. Read the spec second; run the demo first.

Full demo source for every language is in demos/. The accompanying demos/README.md explains each scenario in prose.


What's actually happening (read this once)

If you got this far and want a real understanding of why the bytes are the bytes:

The signing function

Every Ratify signature is two signatures concatenated — one Ed25519, one ML-DSA-65 — over the same canonical bytes of the object being signed.

HybridSignature = Ed25519.Sign(canonicalBytes, priv.ed) ∥ MLDSA65.Sign(canonicalBytes, priv.ml)

Verify(σ) := Ed25519.Verify(canonicalBytes, σ.ed, pub.ed)
           ∧ MLDSA65.Verify(canonicalBytes, σ.ml, pub.ml)

Both must hold. This means:

  • If a quantum computer breaks Ed25519 tomorrow, ML-DSA-65 still holds the line.
  • If a flaw is found in the (newer) ML-DSA-65 algorithm, Ed25519 still holds the line.
  • Harvest-now-decrypt-later adversaries cannot forge today's bundles in a post-quantum future.

This is the hybrid-PQC posture recommended by CNSA 2.0 and BSI for the transition period.

Canonical JSON

Both signers must produce identical bytes from the same logical input. JSON is unordered by default, which would break this. Ratify defines a small canonical serialization:

  • Object keys sorted lexicographically
  • No insignificant whitespace
  • UTF-8 with \u escapes only where mandatory
  • Numbers as integers when integer-valued, no trailing zeros otherwise

The canonicalizer is hand-written in every SDK and produces byte-identical output across Go, TypeScript, Python, and Rust. The 59 fixtures verify this on every CI run.

Spec: SPEC.md §6 (canonical JSON) and §7 (delegationSignBytes / challengeSignBytes).

The challenge-response

Without freshness, an attacker who steals a valid bundle once could replay it forever. The challenge-response defeats this:

  1. Verifier generates 32 random bytes (challenge) and notes the current timestamp.
  2. Verifier sends those to the presenter.
  3. Presenter signs (challenge, timestamp) with the agent's hybrid private key.
  4. Verifier rejects if the challenge timestamp is older than ~5 minutes.

So even if Eve recorded last Tuesday's interaction, she can't replay it today: today's challenge bytes are different.

Effective scope of a chain

If Alice delegates [meeting:*] to Agent-A, and Agent-A sub-delegates [meeting:attend, meeting:record] to Agent-B, the effective scope of Agent-B's chain is the intersection:

effective(chain) = ⋂  cert.scope.expand()   for each cert in chain
                  i

  Alice → Agent-A:   meeting:*  expands to  {attend, speak, video, record, chat, share_screen}
  Agent-A → Agent-B: {attend, record}
                  ∩ = {attend, record}

An agent cannot grant more rights than it itself was given. Spec: SPEC.md §9.


Cross-language interop

The 59 fixtures in testvectors/v1/ are the canonical conformance set. Any implementation in any language that passes all 59 is byte-for-byte interoperable with the reference. This is the contract.

Implementation Language Status Install
github.com/identities-ai/ratify-protocol Go ✅ 59/59 go get github.com/identities-ai/ratify-protocol@v1.0.0-alpha.7
@identities-ai/ratify-protocol TypeScript ✅ 59/59 npm install @identities-ai/ratify-protocol (after npm org approval; install from source for now)
ratify-protocol Python ✅ 59/59 pip install ratify-protocol==1.0.0a7
ratify-protocol Rust ✅ 59/59 cargo add ratify-protocol@1.0.0-alpha.7
C / C++ via stable C ABI planned embedded systems / appliances
Swift planned mobile wallet
Java / Kotlin planned Android / JVM

If you're implementing a new language port, start from the fixtures, not the spec. Match the bytes; the rest follows. See docs/SDKS.md for the conformance contract.


Where to go next

You want to… Go to
Run the demo and see the protocol work demos/README.md
Understand the threat model docs/EXPLAINED.md
Read the normative spec SPEC.md
Use the Verify managed service (revocation, audit, policy enforcement at scale) docs.identities.ai
Integrate with a specific surface (meetings, voice, API gateway, physical AI) docs.identities.ai/guides
Add a new language SDK docs/SDKS.md + the new-SDK issue template
Report a security issue SECURITY.md — do not open a public issue
Cite Ratify in academic work CITATION.cff — GitHub auto-renders BibTeX/APA/Chicago

This README is the entry point. docs.identities.ai covers per-language quickstarts in depth, integration guides for each surface, the managed Ratify Verify product, and the commercial API reference.


Repository layout

ratify-protocol/
├── SPEC.md                Normative protocol specification (CC-BY-4.0)
├── README.md              You are here
├── LICENSE                Apache-2.0 (source code)
├── docs/LICENSES.md       Per-asset license breakdown
├── SECURITY.md            Vulnerability disclosure policy
├── CONTRIBUTING.md        How to contribute (DCO, conformance contract)
├── CODE_OF_CONDUCT.md
├── CITATION.cff           Citation metadata
│
├── types.go               Data structures (DelegationCert, ProofBundle, …)
├── crypto.go              Hybrid Ed25519 + ML-DSA-65 primitives + canonical JSON
├── scope.go               Canonical 52-scope vocabulary + intersect/expand
├── constraints.go         Geo, time, version constraints
├── verify.go              The verifier algorithm
├── ratify_test.go         Unit tests + conformance-suite loader
├── fuzz_test.go           Fuzz harness
├── go.mod
│
├── cmd/
│   ├── ratify/                  ratify-cli (init, delegate, agent-init,
│   │                            agent-bundle, challenge, verify, scopes)
│   ├── ratify-testvectors/      Deterministic fixture generator
│   └── ratify-verifier/         Minimal HTTP reference verifier
│
├── testvectors/v1/        59 canonical fixtures (JSON)
│
├── sdks/
│   ├── typescript/        @identities-ai/ratify-protocol (npm — coming soon)
│   ├── python/            ratify-protocol (PyPI)
│   └── rust/              ratify-protocol (crates.io)
│
├── demos/                 End-to-end narrative demos: go/ python/ typescript/ rust/
│
└── docs/
    ├── EXPLAINED.md           Architecture + threat model + real-time patterns
    ├── AGENT_TO_AGENT.md      A2A patterns (mutual auth, sub-delegation, receipts)
    ├── RELEASES.md            Release process + cross-SDK sync
    ├── REGISTRY_SETUP.md      How the SDK orgs are set up on PyPI/crates.io/npm
    ├── ROADMAP.md             v1.1 / v2 planned work
    ├── SDKS.md                SDK roadmap + conformance contract for new languages
    ├── TESTING.md             Internal testing guide — four levels
    ├── TEST_PLAN.md           Testing methodology
    └── TRANSACTION_RECEIPTS.md  v1.1 receipt envelope design

Security

  • Quantum-safe in v1. Every signature is hybrid Ed25519 (RFC 8032) + ML-DSA-65 (NIST FIPS 204). Both must verify.
  • No central authority at verify time — verifiers need only the principal's public key. No live token-introspection call.
  • Fail-closed verifier. Unknown fields, invalid signatures, expired certs, out-of-scope requests all return a deterministic NO.
  • External audit planned before v1.0.0 stable.
  • Responsible-disclosure policy: see SECURITY.md. Do not open public issues for security reports.
  • Threat model: docs/EXPLAINED.md §5.

License + trademarks + patent

  • Source code: Apache-2.0 — see LICENSE.
  • Specification text: CC-BY-4.0 — see docs/LICENSES.md.
  • Trademarks: Ratify Protocol™ and identities.ai™ are trademarks of Identities AI, Inc. The trademark and patent rights are not licensed under the open-source licenses governing the code or specification.
  • Patent: U.S. patent application pending.

Maintained by Identities AI, Inc. See CONTRIBUTING.md for participation, the governance plan, and the DCO sign-off requirement.


Built by ex-Nokia engineers from the Symbian OS team. The same principle that made mobile identity work at carrier scale — proof by math, not by the network trusting the endpoint — is what makes Ratify work for AI agents.

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:

  1. Each party generates a HybridPrivateKey containing one Ed25519 and one ML-DSA-65 keypair. Seeds are independent.
  2. Humans (or tenant admins, or already-delegated agents) sign Delegation Certs granting another party specific scopes.
  3. Agents sign a fresh challenge with both algorithms to prove liveness.
  4. 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

View Source
const (
	// --- Meeting scopes ---
	ScopeMeetingAttend      = "meeting:attend"
	ScopeMeetingSpeak       = "meeting:speak"
	ScopeMeetingVideo       = "meeting:video"
	ScopeMeetingChat        = "meeting:chat"
	ScopeMeetingShareScreen = "meeting:share_screen"
	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: share data with an authorized third party
	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)

View Source
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
)
View Source
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.

View Source
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.

View Source
const ProtocolVersion = 1

ProtocolVersion is the current wire format version. v1 mandates hybrid Ed25519 + ML-DSA-65 signing on every signed object.

Variables

View Source
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

func CanonicalJSON(v any) ([]byte, error)

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

func ChallengeSignBytes(challenge []byte, ts int64) []byte

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

func ChallengeSignBytesWithSessionContext(challenge []byte, ts int64, sessionContext []byte) []byte

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

func ExpandScopes(scopes []string) []string

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

func GenerateChallenge() ([]byte, error)

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

func HasScope(granted []string, required string) bool

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

func IntersectScopes(lists ...[]string) []string

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

func IsSensitive(scope string) bool

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

func ValidateScopes(scopes []string) error

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

type AnchorResolver interface {
	ResolveAnchor(humanID string) (*Anchor, error)
}

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

type RevocationProvider interface {
	IsRevoked(certID string) (bool, error)
}

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:

  1. Checks the SessionToken's HMAC against sessionSecret.
  2. Checks the token is within [IssuedAt, ValidUntil] at `now`.
  3. Verifies the challenge is fresh (within ChallengeWindowSeconds).
  4. 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.

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).

Jump to

Keyboard shortcuts

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