Binding
Off-chain profile: JCS (RFC 8785) and SHA-256. Amounts are decimal strings in smallest units. Duplicate keys are rejected. Critical unknown fields are rejected. Strings are not renormalized after authorization.
C(x) is UTF-8 JCS. D(tag, x) is SHA-256 of UTF8("VAP/0.1/" + tag) || 0x00 || C(x).
intentHash = D("intent", intentBody)proposalHash = D("proposal", proposalBody)bindingHash = D("binding", { protocolVersion, profile, tenantId, intentHash, proposalHash, operationDigest, policyHash, trustSetHash, executorId, authorizationNonce, expiresAt })attestationDigest = D("attestation", attestationBody)signature = Sign(roleKey, attestationDigest)The signature field is excluded from the signed body. Every required attestation MUST carry the same bindingHash.
Baseline: Ed25519. EVM profile: EIP-712 / secp256k1, allowlisted. Do not “convert” a signature into another scheme because an object named an algorithm.
On EVM, a JSON hash does not prove calldata. Bind to the native digest (EIP-712; for ERC-4337, userOpHash of a specific EntryPoint).
Signing “all good”, a tool name, or a prose summary is not allowed.
Independent role signatures are the v0.1 default. Threshold / MPC is a later profile. Do not reconstruct a full private key at the executor. FROST is Schnorr, not a generic ECDSA fix for EOAs.
Role policy is first. A bare 2-of-3 can skip a mandatory role.
{ "protocolVersion": "0.1", "profile": "offchain-ed25519-v1", "bindingHash": "…", "role": "semantic-verifier", "keyId": "verifier-b/key-2026-09", "verdict": "PASS", "reasonCodes": ["RECIPIENT_MATCH", "AMOUNT_MATCH", "ACTION_MATCH"], "signature": "…"}evidenceRoot binds to bytes. It does not make those bytes true. Missing required evidence is not PASS.