Authorities bind a key to the kind of effect it may produce.
With authorities, TKeeper checks the requested effect before it starts threshold signing.
Full Verdict syntax lives here:
github.com/exploit-org/verdict
Key Authorities
A key stores a list of authorities.
Concrete authorities use OCI references:
[
{
"id": "payments-small",
"oci": "oci://registry.example/verdict/authorities/payments-small@sha256:..."
},
{
"id": "evm-mainnet-usdc",
"oci": "oci://registry.example/verdict/authorities/evm-mainnet-usdc@sha256:..."
}
]
arbitrary is for raw data signing:
[
{ "id": "arbitrary" }
]
Rules:
- the request field is a JSON array
- every key needs at least one authority
- use
arbitraryfor raw signing - use concrete authorities for policy-checked commands
arbitrarydoes not use an OCI referencearbitrarycannot be mixed with concrete authorities on the same key- non-arbitrary authorities require an OCI reference
- concrete authorities must be digest-pinned with
@sha256:... - tags are for local development, not production trust anchors
- authority ids must be unique on the same key
Authority Document
Concrete authorities are Verdict authority documents.
schemaVersion: verdict.authority/v1
id: evm-mainnet-usdc
type: evm.transaction
version: 1.0.0
metadata:
title: Mainnet USDC policy
config:
chainId: 1
contracts:
- standard: erc20
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
policy:
id: usdc-transfer
fallback: DENY
allow:
- id: allow-small-usdc-transfer
where:
- "effect.one(effects, 'erc20.transfer')"
- "effect.any(effects, 'erc20.transfer', {'token': tokenAddress})"
- "bigint.lte(effect.amount(effects, 'erc20.transfer'), maxAmount)"
deny: []
variables:
tokenAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
maxAmount: "100000000"
Fields:
| Field | Required | Meaning |
|---|---|---|
schemaVersion |
yes | verdict.authority/v1. |
id |
yes | Stable authority id. Must match the id attached to the key. |
type |
yes | Intent type. Must match the command artifact type. |
version |
yes | Human release version. Not a trust anchor. |
metadata |
no | Labels for humans. TKeeper does not enforce them. |
config |
no | Trusted intent config. |
policy |
yes | Verdict policy. |
TKeeper rejects the authority when the loaded document id does not match the configured key authority id.
Request Matching
For a concrete authority, the sign command must reference an authority attached to the key:
{
"keyId": "evm-treasury",
"command": {
"type": "evm.transaction",
"authorityId": "evm-mainnet-usdc",
"artifact": {
"message64": "..."
}
}
}
The command authorityId must exist on the key.
The command type must match the authority document type.
The authority document id must match the key authority id.
If policy returns ALLOW, TKeeper starts threshold signing. If the policy returns DENY, signing does not start.
For arbitrary, TKeeper only checks that the key allows arbitrary and that the command artifact type is arbitrary. No Verdict policy is loaded.
Intent Types
Authority type selects the payload format and policy context.
| Authority type | Build feature | Command data | Main policy surface |
|---|---|---|---|
custom |
core | typed JSON | declared fields and configured effects |
evm.transaction |
authority-evm |
unsigned serialized EVM transaction | transaction fields, decoded call, effects |
bitcoin.transaction |
authority-bitcoin |
unsigned tx, previous txs, signing input, sighash | inputs, outputs, fee, sighash, effects |
x509.tbs-certificate |
authority-x509 |
DER-encoded TBS certificate | subject, issuer, validity, extensions |
arbitrary |
core | raw bytes | no Verdict policy |
If a feature module is missing, TKeeper cannot process that command type and returns INVALID_AUTHORITY_ARTIFACT.
Build example:
./gradlew shadowJar -Pkeeper.features=authority-evm,authority-bitcoin,authority-x509
Effects
Effects are normalized consequences exposed to CEL as effects.
Raw request fields explain the input. Effects describe what the input does.
Example effect:
{
"type": "erc20.transfer",
"token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"to": "0x2222222222222222222222222222222222222222",
"amount": "1000000"
}
Common CEL pattern:
effect.onlyTypes(effects, ['erc20.transfer']) &&
effect.one(effects, 'erc20.transfer') &&
effect.any(effects, 'erc20.transfer', {
'token': tokenAddress,
'to': recipientAddress
}) &&
bigint.lte(effect.amount(effects, 'erc20.transfer'), maxAmount)
Native intent modules fail closed when they cannot describe a consequence.
Examples:
- EVM call to an unknown contract.
- EVM whitelisted function without an effect mapping.
- Bitcoin output script that cannot be classified.
- Bitcoin input without the previous transaction.
Typed JSON authorities produce only the effects declared in authority config.
Policy Format
Verdict policies use allow, deny, and fallback:
policy:
id: policy-id
fallback: DENY
variables:
maxAmount: "1000000"
allow:
- id: allow-example
where:
- "effect.one(effects, 'erc20.transfer')"
unless:
- "time.after(time.now(), expiresAt)"
deny:
- id: deny-example
where:
- "effect.has(effects, 'erc20.approval')"
Rules:
- policy id must be non-blank
- rule ids must be unique across
allowanddeny - a rule matches when every
whereexpression istrue - a rule does not match when any
unlessexpression istrue - empty
whereand emptyunlessmatch unconditionally - policy variables are available as root CEL variables
- policy variables override runtime variables with the same name
- deny matches override allow matches
- if no rule matches,
fallbackis returned
TKeeper allows signing only when the final decision is ALLOW.
The audit event stores the policy decision and matched rules.
For a policy-checked sign request, the audit event always carries the Verdict policy evaluation.
OCI Artifacts
An authority OCI artifact contains one authority document:
authority.jsonauthority.yamlauthority.yml
Use digest-pinned references:
oci://registry.example/verdict/authorities/evm-mainnet-usdc@sha256:...
Tags are mutable. They are fine for local development, but not as a production trust anchor.
For a local HTTP registry, enable insecure ORAS access:
oras.insecure = true
Custom Typed Authority
Use custom when the request is JSON and no native intent exists.
Authority:
schemaVersion: verdict.authority/v1
id: payments-small
type: custom
version: 1.0.0
config:
fields:
amount:
type: bigint
currency:
type: string
customer:
type: object
fields:
id:
type: string
effects:
- type: payment.transfer
fields:
asset: "$currency"
amount: "$amount"
customerId: "$customer.id"
policy:
id: payments
fallback: DENY
allow:
- id: small-usd-payment
where:
- "currency == 'USD'"
- "effect.one(effects, 'payment.transfer')"
- "bigint.lte(effect.amount(effects, 'payment.transfer'), '10000')"
deny: []
Command:
{
"type": "custom",
"authorityId": "payments-small",
"artifact": {
"scheme": "ECDSA",
"hash": "SHA256",
"typed": {
"amount": 5000,
"currency": "USD",
"customer": {
"id": "customer-42"
}
}
}
}
Only declared fields become CEL variables. Unknown JSON fields are ignored. effects is reserved.
For all supported field types, effect mapping rules, and CEL helpers, use the Verdict docs:
github.com/exploit-org/verdict
Frequent Problems
Key with arbitrary plus another authority is rejected
arbitrary means raw signing. Mixing it with concrete authorities makes the key ambiguous.
INVALID_AUTHORITY
The authority list is invalid, the OCI reference is malformed, the authority id is duplicated, the loaded document id does not match the configured id, or the authority policy is invalid.
INVALID_AUTHORITY_ARTIFACT
The command artifact type does not match the authority type, or the feature module for that intent is missing.
INVALID_INTENT
The command payload could not be decoded into the authority intent. Common causes are malformed transactions, missing previous Bitcoin transactions, unknown EVM contracts, or invalid typed JSON.
POLICY_VIOLATION
The Verdict policy evaluated to DENY.
OCI pull fails with TLS errors
Check oras.insecure. Local HTTP registry needs it set to true.