Conversation
Adds 0x1::attestation, attestation_policy, attestation_authorization, zktls and merkle_proof with prover specs, unit tests, e2e tests, regenerated docs and SDK builder entries.
Primata
requested review from
areshand,
fEst1ck,
musitdev and
seanyoung
as code owners
September 25, 2026 17:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds an on-chain attestation framework to
0x1: independent sources assert facts about addresses (verified, at what level, with what attributes, who is excluded), and businesses consume them through policies without deploying any code. Five new framework modules:0x1::attestation0x1::attestation_policy0x1::attestation_authorization0x1::zktlssecp256k1attestor signatures over a claim, written as a fact.0x1::merkle_proofNothing here is globally trusted. A consumer names the sources it trusts or gets no answer, and two sources may disagree about the same subject without anything breaking.
Sources and facts (
attestation)A source is a resource account created with
account::create_resource_account, following the same pattern astimelock(#304). The deployer authorizes creation and pays gas but gains no role unless listed. A resource account has no owner, so it cannot be transferred or burned out from under integrators that hardcoded its address.Five roles per source, stored as
vector<address>with add/remove pairs:A fact is a
Recordper (source, subject): lifecycle state (NONE/ACTIVE/SUSPENDED/REVOKED), au8level, attributes (u16key → bytes), expiry, issuer id and epoch, the digest of the attestation it came from, and a bounded change history.All three positive write paths converge on one private
record_fact, so precedence lives in one place:issue_batch), the path that can grandfather an existing population with no user action.redeem_attestation) of an issuer-signed ed25519 attestation: anyone submits, the chain verifies, the submitter pays. The attestation must be newer than the stored record, can bind an optional nullifier, and the message format is published via theattestation_messageview.zktls(friend-onlyrecord_verified_claim), with no issuer key involved.Denials live in a separate table, written only by sentinels. No positive write path can create, modify or clear one.
Revocation primitives
bump_issuer_epochinvalidates everything one issuer wrote in a single O(1) write. Issuer id0is the zkTLS cohort.set_floor_epochinvalidates everything below a source-wide floor. It is strictly increasing.max(issuer epoch, floor), so writes made after a floor raise stay usable.Read path
is_verified/active_with_levelcheck, in order: denial, record exists, state isACTIVE, not expired, issuer epoch current, not below the floor. Configuration (Source) and facts (Facts) are separate resources. The check path reads onlyFacts, whose own fields never change after creation (only table entries do), so gated transactions don't conflict under Block-STM. Pausing blocks writes and never changes whatis_verifiedreturns.Policies (
attestation_policy)A policy is also a resource account. Its body contains
require_anyandrequire_allover(source, min_level),deny_anyover sources, an optionalchain_denysource, attribute predicates (IN,NOT_IN,EQ,GTE), and per-action step-up thresholds.evaluatereturns(decision, reason)in a fixed order:deny_anyrequire_allrequire_any(action, amount)Body changes are staged with an activation time (
stage_body,stage_attr_rules), and anyone can callactivate_pendingonce that time arrives. Source lists are capped atMAX_SOURCESand predicates atMAX_RULES, and a source that doesn't exist is rejected at staging time rather than failing later at evaluation.A consumer gates its own entry function with one call:
attestation_policy::require(POLICY, signer::address_of(user), ACTION_TRANSFER, amount); // or, when the policy may demand step-up: attestation_policy::require_authorized(POLICY, signer::address_of(user), ACTION_TRANSFER, amount, auth);simulateandsimulate_countsdry-run a policy against a list of subjects before a change is staged.Authorization (
attestation_authorization)The policy's authorizer key signs
(policy, subject, action, amount_bucket, nonce, issued_at, expires_at), bound to the chain id and a domain separator. The amount is committed as a power-of-ten bucket, not an exact value, because authorizations are public forever. The policy caps the TTL. The nonce is burned only after the signature verifies, andprune_noncescan free expired nonces permissionlessly, which is safe because expiry is checked before the nonce. A nonce table is used rather than binding to the sequence number, because orderless transactions leave the sequence number untouched.zkTLS (
zktls)A per-source
Verifierstores attestor sets per epoch with an m-of-n threshold, plus a template allowlist.enroll:secp256k1::ecdsa_recover(acceptingvas 0–3 or 27/28)Safeguards:
previous_grace_secs, where0means cut off immediately).This path is for enrollment only and is never used per action.
Not included
transaction_validation.moveneeds a new mapped validation status and a Rust converter arm, and should not ship without a dead-man deadline. It is deliberately out of scope.aptos move runworks today.V1wrappers. Resources are plain structs, matchingtimelock. Because Move forbids adding fields to a published struct, this should be decided before the first release.How has this been tested?
Move unit tests: 149 total.
attestation: 47, covering creation invariants, roles, the three write paths, lifecycle transitions, epoch and floor semantics, denial precedence, pause, attributes, relay replay/tamper/stale epoch/removed issuer, nullifiers, and roots.attestation_policy: 37, covering every evaluation branch and reason code, staging and activation timing, the pause, simulation, step-up, and authorization.zktls: 55, using fixed secp256k1 vectors, covering thresholds, duplicate and unknown signers, epoch grace windows, single-use claims, revoked templates, nullifier binding, the Ethereumvencoding, and cohort revocation.merkle_proof: 10, using vectors generated with@openzeppelin/merkle-tree.Rust e2e tests (
aptos-move/e2e-move-tests/src/tests/attestation.rs): 34 tests against the real VM. Every signed payload (the relay attestation, the authorization and the zkTLS claim digest) is built independently in Rust and compared byte for byte with the module's published view before being submitted. A consumer package (attestation.data/gated) exercisesrequireandrequire_authorizedfrom inside a third-party entry function:Move Prover:
movement move prove --filter <module>returnsSuccessfor all five modules. Each*.spec.movefile carries a<high-level-req>block mapping the invariants to the functions that enforce them. The key specs were checked for vacuity by deliberately breaking them and confirming the prover rejected each broken version.pragma verify = falseis used only on the twocreateentry points, because ofcreate_resource_account's cross-module effects; their*_internalbodies are verified.aborts_if_is_partialis used where aborts sit inside havocked loops (for_each_ref/while) or depend on early returns, and each use is annotated in the spec file.Other:
internal_indexer_test::test_db_indexer_datais updated with the five new module names and passes.cargo build -p aptos-cached-packagesregenerates the docs and SDK builder. It also adds the builder entry fordelegation_pool::enable_partial_governance_voting_if_needed, which was already stale onm1.Type of Change
Components Impacted
0x1::attestation,0x1::attestation_policy,0x1::attestation_authorization,0x1::zktls,0x1::merkle_proofmodules + specs)Checklist
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.