This is an advanced topic. Guards that back keys with attestation — such as the custom issuer guard — are the primary consumers of this contract. If you are integrating NEAR Auth with the default Auth0 provider, you do not need it.
Why attestation exists
A guard verifies a JWT’s RS256 signature against a set of RSA public keys. Whoever controls those keys controls what the guard will accept — so key management is a security-critical surface. There are two ways to feed keys to a guard:- Owner-managed keys. A single admin account writes the keys directly. This is what the Auth0 guard does, and it is simple, but it concentrates trust in one key holder.
- Attestation-managed keys. Keys are only accepted once a configurable number of independent attesters submit the same key set. No single party can rotate the keys on their own.
The data model
The contract keeps three pieces of state.| Field | Type | Description |
|---|---|---|
attestations | IterableMap<AccountId, Attestation> | The current pending attestation from each attester |
quorum | u32 | Number of matching attestations required to update the active keys |
public_keys | Vector<PublicKey> | The currently active public keys guards read |
hash is what lets the contract cheaply decide whether two attesters proposed the same keys. It is computed by concatenating every key’s modulus and exponent bytes in order and hashing the result:
Initializing the contract
The contract is initialized once with the quorum and the initial roster of participants:The number of attesters that must submit matching keys before the active key set is updated.
Accounts with full administrative privileges. Each is also granted the
DAO role so it can manage quorum, attesters, pausing, and upgrades.The initial accounts authorized to submit attestations (granted the
Attester role).quorummust be greater than0.- There must be at least one super admin.
quorumcannot exceed the number of attesters — otherwise no key set could ever reach quorum.
DAO (full administrative access, and may also attest) and Attester (may submit attestations). The contract also defines operational roles — CodeStager, CodeDeployer, DurationManager, PauseManager, and UnpauseManager — for staged upgrades and emergency pausing.
Reaching quorum
Attesters install keys by callingattest_public_keys. Only accounts holding the Attester or DAO role may call it, and it is disabled while the contract is paused:
Validate the input
The submitted list must be non-empty, and every key must have a non-empty modulus (
n) and exponent (e).Record the attester's proposal
The attestation (hash plus keys) is stored under the caller’s account id, replacing any previous attestation from that same attester.
Count the agreement
The contract counts how many stored attestations share this hash — that is, how many attesters currently agree on this exact key set.
Example flow
With a quorum of2 and three attesters:
- Attester A submits
[PK1, PK2]. The attestation is stored; the matching count is1— below quorum, so nothing changes. - Attester B submits the same
[PK1, PK2]. The hash matches A’s, the count reaches2, quorum is met. The active keys become[PK1, PK2]and all attestations are cleared. - If Attester C had instead submitted a different key set, its hash would not match, the count for
[PK1, PK2]would stay at1, and the keys would not update.
Until quorum is reached for the very first time,
public_keys is empty and any guard reading it gets an empty list — so no signatures can be verified against uninstalled keys.How a guard reads the keys
A guard consumes attested keys through a single view method:- Calls
get_public_keys()on this Attestation contract. - Validates the returned keys and stores them.
- Uses them to verify the RS256 signatures on the issuer’s JWTs.
| Method | Returns |
|---|---|
get_public_keys() | The currently active public keys (empty until quorum is first reached). |
get_attestation(account_id) | The pending attestation from a specific attester, if any. |
get_quorum() | The current quorum threshold. |
get_attesters(from_index, limit) | A paginated list of accounts holding the Attester role. |
Managing quorum and attesters
Only accounts with theDAO role can adjust the roster or the threshold, and each guarded method preserves the contract’s core invariants.
Change the quorum with set_quorum(quorum). The new quorum must be greater than 0 and cannot exceed the current number of attesters.
Add an attester with grant_attester(account_id), granting them the Attester role.
Remove an attester with revoke_attester(account_id). This is guarded by a safety check: the revocation is rejected if it would leave fewer attesters than the quorum requires (quorum >= remaining_attesters), which would make quorum permanently unreachable. Add more attesters or lower the quorum first.
Why this matters for security
The value of attestation is that it removes a single point of failure from key management:- No unilateral key rotation. Installing a new key set requires
quorumindependent attesters to submit byte-for-byte identical keys. Compromising one attester is not enough to change what a guard trusts. - Tamper-evident agreement. Because agreement is decided by a SHA256 fingerprint over the exact key bytes and their order, attesters cannot “almost” agree — a mismatched or reordered key silently fails to count, so subtle substitution attacks do not slip through.
- Recoverable by construction. The quorum-vs-attester invariants — enforced at init, on
set_quorum, and onrevoke_attester— guarantee the contract can never be configured into a state where no key set could ever reach quorum. - Clean trust boundary. Guards trust the outcome of the attestation process, not any individual key holder, so authentication logic and key custody evolve independently.
Next steps
Custom issuer guard
Verify JWTs from your own issuer with attestation-managed keys.
Contracts overview
How the NEAR Auth contract, router, and guards fit together.
Custom issuer
Bring your own identity provider to NEAR Auth.