seal. Docs Try the sandbox ↗
Mainnet is not yet live. Explore with demo funds or test tokens. Review status ↗

SEAL Pool Protocol Specification

Date: 10 October 2026
Implementation reference: the source and machine-readable interfaces in this repository
Statement schema: 3
Fixed verifier generation: 0
Publisher: Polylog Labs

Abstract

SEAL Pool is a shielded asset protocol implemented on an Ethereum-compatible execution environment. It represents claims on registered ERC-20 assets as private notes. Commitments to those notes are recorded in a shared append only Merkle forest. A spend publishes nullifiers and a zero knowledge proof of ownership, membership and conservation of value. Each asset has separate backing and share accounting. Supported state transitions include deposits, private payments with a delayed sender-reclaim branch, settlement, public withdrawal and atomic asset exchange.

This document specifies the SEAL state machine, cryptographic relations, execution bindings and interoperable client formats. It also defines the archive and intent service profiles used by the reference client. SEAL inherits transaction ordering, execution atomicity and finality from its host chain; it does not define a separate blockchain consensus protocol.

Status. This is a specification of the referenced implementation, not an independently accepted production release. The included proof artifacts are development artifacts. Acceptance of the circuits, prover, verifier and deployment remains a separate release requirement.

1. Scope and conformance

1.1 Specification layers

The following layers have distinct authority:

Layer Defined behavior Enforcement
Pool protocol Asset registration, note-state transitions, backing, fees and authorities EVM contracts and selected verifier
Proof relation Knowledge of valid openings, spending authority, membership and per asset conservation Operation-specific arithmetic circuit
Client formats Key derivation, payment addresses, ciphertexts, recovery records and backups Compatible wallets and SDKs
Service profile Archive storage, fee quotes, intent service submission and retries The selected service implementation
Deployment policy Chain selection, accepted artifacts, signer configuration and launch commitments Release tooling and operators

A client-format requirement does not imply that the contract validates that format. In particular, the contracts do not decrypt ciphertexts, verify archive availability, interpret account backups, or establish the real-world identity of multisig signers.

MUST and MUST NOT identify requirements for conformance to the layer under discussion. SHOULD identifies a recommendation whose omission requires a considered interoperability or security justification. MAY identifies permitted behavior. These terms do not introduce requirements absent from the specified implementation into the onchain validity rules.

The abstract relation assumes the specified cryptographic primitives and a sound selected verifier. The implementation details in Sections 8, 16 and 26 determine the concrete encodings. When a discrepancy is discovered, implementers MUST identify the applicable contract and artifact revision; they MUST NOT infer that a documentation change modifies an existing deployment.

1.2 Protocol boundary

A pool instance is identified by its host-chain identifier and contract address. Its immutable scope binds notes to that instance. There is no native cross chain transfer, cross pool note redemption, protocol token, token-staking requirement, mining rule or SEAL block reward in SEAL.

The pool contract is not a proxy. The guardian address is fixed at construction. Assets, vault selection and stablecoin classifications become permanent on registration. The guardian also receives every protocol fee. Verification is fixed at deployment as specified in Section 17.

There is no administrative pool pause, asset deregistration, general sweep of unsolicited pool balances, or arbitrary replacement of a registered vault.

2. Notation, types and constants

2.1 Basic notation

Bytes[n] denotes an octet string of length n. UInt[k] denotes an integer in [0, 2^k). Array indices start at zero. x || y denotes byte concatenation; len(x) denotes byte length. floor and ceil have their usual mathematical meanings. Amounts are integers in the base units of the indicated token or share system.

BE32(x) is the 32-byte big-endian encoding of x. OS2IP(b) interprets bytes as a big-endian nonnegative integer. UTF8(s) encodes a string without a byte-order mark. Textual hexadecimal encodings use the prefix 0x. Addresses in arithmetic expressions are their 160-bit integer values.

ABI_T(x) denotes standard Solidity ABI encoding with the explicitly stated type or type sequence T. It is not packed encoding. An ABI word is 32 bytes; an address occupies the low 20 bytes of its word. Dynamic members have standard offsets and length prefixes. Function calldata additionally starts with the four-byte function selector. See the Solidity ABI specification.

K(b) denotes Ethereum Keccak-256, not SHA3-256. SHA256(b) denotes SHA-256. F(b) = OS2IP(K(b)) mod r. Reduction is used only where specified; it is not an alternative to range checking a purported canonical field element.

The BN254 scalar field is Fr = Z/rZ, where:

r = 21888242871839275222246405745257275088548364400416034343698204186575808495617
p = 21888242871839275222246405745257275088696311157297823662689037894645226208583

p is the BN254 base-field modulus. A canonical scalar-field representation is an integer less than r. Unless explicitly stated otherwise, circuit arithmetic is in Fr; token and share arithmetic is checked unsigned integer arithmetic. mulDiv(a,b,c) means the full-precision integer quotient floor(a*b/c), with a nonzero denominator and a representable result. An explicit ceil changes the rounding direction.

2.2 Poseidon

H(x1,...,xk) is the circomlib Poseidon hash over Fr with k inputs, a zero initial capacity element, and the first output element. SEAL uses arities 2, 3, 4, 5 and 8. Their state widths are 3, 4, 5, 6 and 9; each uses eight full rounds and respectively 57, 56, 60, 60 and 63 partial rounds with the fifth-power substitution. The pinned parameters are not freely selectable. The SDK uses poseidon-lite 0.3.0 and the circuits use circomlib 2.0.5. The published reference file includes all five arities. The linked Solidity implementations compute the same hashes for public funding and tree operations.

The constants and implementation correspondence are identified in Section 26. Hash arity is significant: H(x,y,z) is not H(H(x,y),z).

The concrete parameter file docs/public/spec/seal-poseidon.json supplies the unoptimized round constants C and mixing matrix M for each supported arity. Values are canonical decimal field elements. For arity k, set t=k+1, RP to the corresponding partial-round count, and state=[0,x1,...,xk]. Compute in Fr:

for round = 0, ..., 7 + RP:
    for i = 0, ..., t-1:
        state[i] = state[i] + C[round*t + i]
    if round < 4 or round >= 4 + RP:
        for i = 0, ..., t-1:
            state[i] = state[i]^5
    else:
        state[0] = state[0]^5
    next[i] = sum(M[i][j] * state[j], j = 0, ..., t-1)
               for each i = 0, ..., t-1
    state = next
return state[0]

The matrix multiplication uses the complete pre-mixing state; it is not an in-place sequential update. The parameter file defines matrix rows as output indices and columns as input indices. Some optimized circomlib representations transpose matrices or move round constants and partial-round multiplications. Those representations MUST produce the same permutation; their arrays must not be substituted directly into this reference algorithm.

The field domain constants 1 through 9 in Section 5 are ordinary inputs to this permutation. They do not replace its zero capacity element. Hashing accepts a fixed number of field elements; no byte padding, message-length prefix or arbitrary-length sponge mode is implicit.

2.3 Fixed protocol bounds

Constant Value Meaning
MAX_BATCH 8 Input slots and ordinary output slots per proof
MAX_ASSETS 128 Registered assets over the lifetime of one pool
MAX_TREE_DEPTH 32 Maximum depth of each commitment-tree epoch
Epoch capacity 2^32 Maximum leaves in an epoch
Note-value bound 2^128 - 1 Maximum positive ordinary, pending or shielded-fee value
Withdrawal-share bound Strictly less than 2^132 Per-slot aggregate withdrawal shares
Public-signal count 71 monetary; 11 account-only Four operation circuits
Statement byte length 2,272 monetary; 352 account-only Fixed ABI words
PLONK proof byte length 768 24 ABI words
REFUND_DELAY 604,800 seconds Seven days measured from the chosen root's creation
Fee denominator 10,000 Basis-point denominator
Yield-fee cap 1,000 bps 10% of eligible gain
Withdrawal-fee cap 20 bps 0.20% of gross ordinary exit assets
Deposit fee cap 20 bps 0.20% of gross pool deposit assets
Swap-fee cap 50 bps 0.5% of exact trade input
Fee-increase notice 1,209,600 seconds Fourteen days

A day in a contract means 86,400 seconds. Timestamps are host-chain block.timestamp values. These are not wall-clock service guarantees.

2.4 Units

The protocol distinguishes underlying assets, ERC-4626 vault shares, and pool shares. Notes contain pool shares. Ordinary withdrawal minima and public execution fees use underlying base units. Pool shares are internal accounting units and are not ERC-20 balances held by the user. Guardian pool shares are included in total pool-share supply.

3. Components and protocol state

3.1 Components

Component Function
PrivacyPool Holds vault shares, maintains the note forest and nullifier set, and executes deposits and proof authorized transitions
AaveV3VaultFactory Creates permanent fixed code Aave or custody vaults
AaveV3Vault ERC-4626 supply-only wrapper for one Aave reserve
IdleVault ERC-4626 custody wrapper with no investment strategy
ProtocolFees Capped rate policy and canonical swap-adapter registration
VerifierRegistry Immutable operation verifier bundle fixed at construction
PlonkVerifierAdapter Fixed-length decoding and raw-verifier code-hash binding
SealDepositAdapter EIP-712 authorized deposit and optional ERC-2612 permit
SealSwapAdapter Bounded exact-input V3 swaps with funded private output/refund notes
ProtocolFeesFactory Immutable linked library used at construction to create the fee controller
Wallet Keys, openings, witnesses, local proof generation and chain reconciliation
Archive Content-addressed ciphertext availability and discovery indexes
Intent service Quote validation, transaction signing, gas payment and durable retries

3.2 Pool state

For each registered asset a, define:

vault[a]       permanent ERC-4626 vault
mode[a]        AAVE = 0 or IDLE = 1
stable[a]      permanent stablecoin classification
S[a]           total pool shares
T[a]           accrued guardian pool shares, included in S[a]
M[a]           high-water mark in underlying base units
V[vault[a]]    accounted vault-share backing
D[a]           whole underlying fee units not yet converted to pool shares
R[a]           fractional fee numerator, 0 <= R[a] < 10,000
W[a]           accrued public-withdrawal fee balance in underlying units
F[a]           accrued deposit fee balance in underlying units

V records shares minted by verified pool deposits minus shares redeemed. It is not the vault token's balanceOf(pool). The distinction excludes unsolicited vault-share transfers from note backing.

The pool also maintains an ordered registered-asset list; epoch trees; an active epoch number; permanent sets of inserted commitments and spent nullifiers; root metadata; and a global deposit nonce. Root metadata is (createdAt: UInt64, epoch: UInt64, depth: UInt32, known: bool). Epoch numbering and the deposit nonce initially equal zero. Empty state contains no registered roots.

The fee controller stores five default/current rates, an optional earned yield override and pending proposal for each lending asset, one pending proposal per rate kind, a shared fee-policy version, the immutable guardian fee recipient, and a set of canonical swap adapters. The verifier registry state is specified in Section 17. Adapter-specific state is specified with each adapter.

3.3 Atomicity

A host transaction either completes all its contract effects or reverts them all. A later payout, vault deposit, swap or funded output failure reverts earlier nullifier writes, note insertions and fee accounting in the same transaction. Gas expenditure by the submitting account is outside that rollback.

Fund-moving pool entry points use a shared transient reentrancy guard. The swap callback is a deliberately authorized nested call with separate transient authority.

4. Keys and payment addresses

4.1 Key components

A payment identity consists of:

The spending-key hash is a field element, not an elliptic-curve public key. Spending requires knowledge of sk; delivery encryption uses pkView. A viewing key is not a spending key. A wallet MUST reject use of a zero spending-key hash as a note owner.

Random spending keys and note rho values are sampled by drawing 32 random bytes and rejecting integers equal to zero or at least r. Reducing a uniformly random 256-bit integer modulo r is not the specified sampling procedure. Ephemeral encryption secrets, nonces, note secrets and randomized opening material require a cryptographically secure random source. Proof blinding requires independent secure randomness.

The circuit can check a nonzero or range condition, but cannot establish that a supplied value was sampled randomly. Predictable or reused secrets can violate confidentiality even when the corresponding transaction is valid. An implementation must separate test-vector randomness from its live key, note and proof-generation paths.

4.2 Wallet-derived account profile

The reference account profile derives payment keys, a recovery key and an indexing key from an Ethereum personal-message signature. The exact unlock text, with LF separators and no terminal newline, is:

SEAL private account unlock
Key format: 1
Account: <lowercase 0x-prefixed account address>
Sign only inside the SEAL app you trust.
This signature unlocks your private funds and encrypted history.
Never share this signature. This is not a transaction or server login.

Let nSecp be the secp256k1 group order:

0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

Parse the signature as (rSig,sSig,recoveryBit). Require 0 < sSig < nSecp. If sSig > nSecp/2, replace it by nSecp-sSig and flip the recovery bit. Recover the signer under Ethereum personal-message semantics and require equality with the requested account. Serialize the canonical signature as 65 bytes: two 32-byte big-endian integers rSig || sSig, followed by one recovery byte v=27+recoveryBit. For message bytes m=UTF8(unlockMessage), the personal-message digest is:

K(0x19 || UTF8("Ethereum Signed Message:\n")
       || UTF8(decimal(len(m))) || m)

Here \n denotes one LF byte. The decimal length has no leading zeros. The address comparison is over its 20-byte value, not its checksum capitalization.

Define:

salt = SHA256(UTF8(unlockMessage))
Derive(purpose) = HKDF-SHA256(
    IKM = canonicalSignature,
    salt = salt,
    info = UTF8("seal.account." || purpose),
    length = 32)

Take sk = OS2IP(Derive("spending/" || decimal(counter))) for the first counter starting at zero for which 0 < sk < r. The viewing private input, recovery key and indexing key are respectively Derive("viewing"), Derive("recovery") and Derive("indexing"). X25519 applies RFC 7748 scalar decoding to the viewing input: clear the lowest three bits of byte 0, clear the highest bit of byte 31, then set bit 6 of byte 31. The public key is the little endian encoding of the resulting Montgomery u coordinate, obtained with basepoint 9. These 32 encoded bytes become the view field, rather than an integer converted to big endian. HKDF is instantiated as extract-and-expand, as defined in RFC 5869.

This profile does not include chain or pool identity in key derivation. Pool separation occurs in note scope and recovery encryption. Wallet implementations MUST reproduce the same canonical signature to reproduce the identity. Possession of the same EOA key does not by itself require a wallet to emit the same signature. The profile uses recoverable ECDSA verification, not a general ERC-1271 account-signature procedure.

The unlock signature is equivalent to access to the derived private account material. It MUST remain local; it is not an archive or intent service authentication credential.

The contracts do not store a directory of user X25519 public keys or seal. addresses. A sender obtains the recipient address outside the contract. The address carries the public viewing key; encrypted envelope headers carry fresh ephemeral sender public keys. These serve different roles and do not constitute an onchain identity registry.

4.3 Payment address encoding

The reference address writer emits:

"seal." || Base64urlWithoutPadding(UTF8(JSON.stringify({
  chainId: <JSON integer>,
  spend:   <decimal pkSpend string>,
  view:    <0x-prefixed 32-byte pkView hex>,
  checksum: <"v1:" followed by 8 lowercase hexadecimal digits>
})))

Keys are emitted in the order shown, without JSON whitespace. The parser requires the seal. prefix, a total string length no greater than 1,024 characters, a chain identifier equal to the selected network, a one-to-77-digit decimal spending-hash string with 0 < pkSpend < r, and a 32-byte hexadecimal viewing key. The parser accepts some equivalent JSON/decimal representations, including leading zeros in spend; the writer's representation is the interoperable output form. The checksum is the first four bytes of Keccak-256 over UTF-8 seal-payment-address:v1: followed by the canonical JSON object containing only chainId, spend and view in that order. A missing, unsupported or incorrect checksum rejects on every recipient entry path. The checksum detects accidental corruption; it does not authenticate a recipient or establish successful X25519 key agreement.

An address contains public delivery information only. It contains no pool address, private key, expiration, amount or onchain registration. It is not an ERC-20 destination address. Reusing it allows counterparties to recognize the same identity.

5. Notes, commitments and nullifiers

An opening is the private tuple of values required to recompute and interpret a particular commitment. A hash preimage is any input whose hash equals a specified output. A valid note opening supplies the structured hash preimages for its nested commitment construction; the terms can refer to the same witness when that structure is explicit. They are not universally interchangeable: a preimage of an intermediate hash need not identify a valid note or prove spending authority. The local variable named opening in the equations below denotes an intermediate hash value, not the entire private tuple.

5.1 Scope

For chain identifier chainId and pool address pool:

scope = F(ABI_(string,uint256,address)("seal.multiasset", chainId, pool))

The pool computes and stores this scope at construction. It is immutable even if the execution environment later reports a different chain identifier. Clients MUST use the configured deployment's verified scope.

5.2 Ordinary note

An ordinary note opening is:

(scope, asset, value, ownerKeyHash, rho, salt)

For a spendable note, asset is a nonzero 160-bit address, 1 <= value < 2^128, and ownerKeyHash and rho are nonzero field elements. salt is a field element and may equal zero in the abstract relation. Define:

tag = H(1, scope, asset)
precommitment = H(ownerKeyHash, rho)
opening = H(precommitment, salt)
cm = H(tag, value, opening)
nf = H(3, scope, H(rho, cm))

The note becomes a funded claim only when cm is inserted by an accepted pool transition. Knowledge of an opening without the matching spending key is insufficient to spend it. A shielded execution fee note uses this same ordinary-note format.

5.3 Pending-payment note

A pending-payment opening is:

(scope, asset, value, recipientKeyHash, refundKeyHash,
 rho, secret, recipientPayloadHash, refundPayloadHash)

The asset and value have the same positive bounds as above. rho, recipient and refund key hashes are nonzero. Recipient and refund key hashes differ. secret is a field element; the circuit does not require it to be nonzero. Define:

tag = H(2, scope, asset)
cm = H(tag, value, recipientKeyHash, refundKeyHash,
       rho, secret, recipientPayloadHash, refundPayloadHash)
nf = H(4, scope, H(rho, cm))

The two payload hashes commit to separately encrypted recipient and refund envelopes. A new transfer requires both hashes to be nonzero and equal to the corresponding public signals. The relation binds the hashes; it does not validate encryption or prove delivery availability.

A recipient spend proves H(5,sk) = recipientKeyHash. A refund spend proves H(5,sk) = refundKeyHash and is additionally subject to the contract's maturity check. Both branches publish the same nf. Recipient claims remain valid after refund maturity until a successful spend consumes that nullifier.

5.4 Note uniqueness and absence

The contract rejects an inserted commitment equal to zero, at least r, or already present in the global commitment set. The nullifier commits to both rho and the commitment; repeating a rho does not intentionally merge distinct commitments' nullifiers. Hash collisions remain a cryptographic failure assumption.

Zero-valued ordinary outputs are absent outputs. They publish commitment zero and are not inserted. They are not spendable zero-value notes. Pending payments are always positive. There is no explicit deletion of a spent commitment; spentness is represented by the permanent nullifier set.

6. Commitment forest and membership

6.1 Epoch trees

Each epoch is a Lean Incremental Merkle Tree. A parent with two children is H(left,right); a parent with one child has the child's value. Missing children are not replaced by a fixed recursively hashed empty-node sequence. A single-leaf tree has depth zero and root equal to the leaf. Capacity is 2^32 leaves.

Before a batch inserts its outputs, let c be the number of nonzero ordinary, fee and pending commitments. If size(activeEpoch)+c > 2^32, increment the epoch and insert the whole batch in the new tree. Equality does not roll the epoch. An output-free withdrawal inserts nothing. A rolled epoch may have an unused tail. Old epochs, roots and nullifiers remain valid state.

Output insertion order is ordinary slots 0 through 7, followed by the fee commitment, the pending commitment and the successor account commitment. Each insertion records its resulting root. Root metadata is written only if that root has not previously been recorded, preserving its first creation time. A separate epoch number is not a public proof input; the known root identifies the recorded tree state.

6.2 Membership calculation

For leaf cm, a 32-bit path index j, sibling array s[32], and public depth d, require 0 <= d <= 32. For i >= d, require s[i]=0 and bit i of j equal to zero. Start with x=cm; for each i=0,...,31:

if s[i] = 0:        x = x
else if bit(j,i)=0: x = H(x, s[i])
else:              x = H(s[i], x)

The final x MUST equal the public input root. The contract separately requires that root to be known and the supplied depth to equal its recorded depth.

The SDK uses LeanIMT's compressed sibling path and compressed direction index on uneven right edges, then pads the sibling list to 32 with zero. The public depth remains the actual tree depth. The compressed proof index is not necessarily the event's leaf index. Implementations MUST NOT substitute the uncompressed leaf index without converting the path representation.

6.3 Reclaim maturity

For a refund input using root root, the contract requires:

block.timestamp >= roots[root].createdAt + 604800

This is a root-age check, not a lookup of payment-creation time. Retaining the first inclusion path permits reclaim as soon as that root matures. A later root may postpone reclaim eligibility. Roots do not expire in SEAL.

7. Batch proof relation

7.1 Operations and public statement

Three circuits instantiate the same bounded transition with different mode constants:

Mode Circuit Permitted value destinations
0: settlement multiAssetSettle Ordinary owner outputs and optional ordinary fee note
1: transfer multiAssetTransfer Ordinary owner outputs, exactly one pending payment and optional fee note
2: withdrawal multiAssetWithdraw Ordinary owner outputs and positive public withdrawals

The 71-element public vector u has this order:

Indices Name Interpretation
0-7 outputCommitments[8] Ordinary outputs; zero means absent
8 feeCommitment Optional ordinary fee output
9 pendingCommitment New pending payment, transfer only
10-17 nullifiers[8] Enabled input nullifiers or zero
18 scope Pool scope
19 context Bound execution context
20-27 stateRoots[8] Inclusion roots
28-35 stateDepths[8] Recorded depths
36-43 inputKinds[8] Disabled, ordinary, claim or reclaim
44-51 withdrawAssets[8] Token address for a positive withdrawal, otherwise zero
52-59 withdrawnValues[8] Pool shares withdrawn in the corresponding asset slot
60 recipientPayloadHash New payment's recipient envelope hash
61 refundPayloadHash New payment's refund envelope hash
62 expiration Unix seconds bounding proof use; zero means no bound

| 63 | accountRoot | Prior checkpoint membership root; zero at initialization | | 64 | accountDepth | Prior checkpoint depth; zero at initialization | | 65 | accountInit | Boolean deterministic initialization transition | | 66 | accountNullifier | Initialization marker or prior checkpoint nullifier | | 67 | nextAccountCommitment | Successor checkpoint commitment | | 68 | bindingKind | 0 ordinary transition; 2 private margin | | 69 | bindingSubject | Margin account address for kind 2; otherwise zero | | 70 | bindingValue | Owner tag for kind 2; otherwise zero |

The first 18 entries are circuit outputs; the remaining entries are declared public inputs. Both sets are part of the verified public statement. All conforming client representations use canonical Fr elements. Section 16 describes the concrete verifier's checks separately from this typed relation.

The account-only relation has eleven public inputs: scope, context, expiration, then the eight checkpoint fields in the order above. It permits binding kind 1 for a deposit precommitment, as well as kinds 0 and 2. The deposit binding has zero subject and value H(ownerKeyHash, bindingRho).

For spending key sk, owner key hash K, scope S, private revision j and randomness rho, the checkpoint commitment is H(6,S,K,j,rho). Its spend nullifier is H(7,S,sk,commitment). Initialization uses H(8,S,sk) with revision zero and no membership path. The successor uses revision j+1 and fresh nonzero randomness; both revisions are bounded to 64 bits. Normal transitions prove membership of the prior checkpoint under a known root and use the same spending key as the financial inputs. The margin owner tag is H(9,S,sk,accountAddress).

The pool consumes the marker/nullifier and appends the successor atomically with the action. Initialization is unique per key and scope; every complete withdrawal still creates a successor. Public deposits to another person's precommitment can create incoming notes without advancing that person's checkpoint. A checkpoint therefore certifies continuity of covered owner actions, not completeness of incoming delivery or correctness of current valuations. See account state and recovery.

7.2 Private witness

The private witness consists of:

Witness member Shape and meaning
spendKey One nonzero spending key shared by every enabled input and ordinary user output
assets Eight 160-bit asset identifiers; nonzero entries are pairwise distinct
Input selectors Eight indices into assets, each in [0,8)
Input openings Eight values, rhos, ordinary salts and pending-note fields
Membership data Eight path indices and eight arrays of 32 siblings
Ordinary output openings Eight asset indices, values, rhos and salts
Fee opening Asset index, value, owner-key hash, rho and salt
Payment opening Asset index, value, recipient-key hash, rho and secret
Checkpoint opening Prior private revision, randomness and membership path; successor randomness; binding randomness when required

The asset table is private. Zero entries may repeat. Unused entries need not form a canonical suffix under the circuit relation. Every selector, including a disabled slot's selector, is checked by a one-hot selection relation; an out-of-range selector is invalid.

The SDK's interoperable construction order is positive-withdrawal assets sorted numerically, then sorted private-only assets, then zero padding. This ordering is a wallet convention, not an additional circuit sorting constraint. Public withdrawal minima follow these table positions.

The concrete witness-calculator input names are listed below. The public inputs in Section 7.1 are also supplied to the witness calculator under the names shown there; the first 18 statement entries are calculated outputs. Every scalar is represented as a canonical field element, conventionally a decimal string in JSON. Array nesting and lengths are significant.

Private name Shape Interpretation
spendKey Scalar Common spending secret
assets [8] Private asset table
inputAssetIndices [8] Table position for each input
inputValues, inputRhos, inputSalts [8] each Input value, rho and ordinary salt
inputRecipientKeyHashes, inputRefundKeyHashes [8] each Pending ownership branches
inputSecrets [8] Pending opening secret
inputRecipientPayloadHashes, inputRefundPayloadHashes [8] each Hashes already bound into each pending input
indices [8] Compressed membership direction indices
siblings [8][32] Membership siblings
outputAssetIndices, outputValues, outputRhos, outputSalts [8] each Ordinary owner-output openings
feeAssetIndex, feeValue, feeOwnerKeyHash, feeRho, feeSalt Scalar each Optional fee-note opening
paymentAssetIndex, paymentValue, recipientKeyHash, pendingRho, pendingSecret Scalar each New pending-payment opening

inputRecipientPayloadHashes describes existing inputs; the singular public recipientPayloadHash describes the newly created payment. They cannot be interchanged. Ordinary input/output owner hashes and the new pending refund hash are derived from spendKey; they are not additional independent witness members. No epoch number, wall-clock timestamp, ciphertext, Ethereum address of the spender, or vault valuation is a private circuit input.

7.3 Input validity

Each input kind is a two-bit value:

Kind Meaning Spending authority
0 Disabled None
1 Ordinary Ordinary owner equals H(5,spendKey)
2 Pending recipient claim Recipient hash equals H(5,spendKey)
3 Pending sender reclaim Refund hash equals H(5,spendKey)

At least one input is enabled. An enabled input has a positive 128-bit value, nonzero selected asset, valid note opening, nonzero nullifier and valid membership path. Enabled nullifiers are pairwise distinct.

For kind 0, the input value, asset index, rho, root, depth, path index, all siblings, ordinary salt and all pending metadata are zero. Kind 1 has all pending metadata zero. Kinds 2 and 3 have ordinary salt zero. These canonical disabled branches prevent unused witness fields from carrying unconstrained value. Internally evaluated dummy note components use fixed nonzero substitutes where necessary; they do not create commitments or value.

The circuit does not read contract storage, timestamps, token contracts or archive contents. It constrains expiration to 64 bits but does not compare it with time. Known-root status, spentness, reclaim maturity and proof expiration are contract checks.

7.4 Output validity

Each positive ordinary output selects a nonzero asset, has a 128-bit value and nonzero rho, and belongs to H(5,spendKey). Its commitment is nonzero. A zero-valued output has zero asset index, rho, salt and public commitment.

A positive shielded fee selects a nonzero asset and has an ordinary opening with nonzero owner hash and rho. It may belong to a different spending identity. An absent fee has zero value, asset index, owner hash, rho, salt and commitment. Withdrawal mode requires the fee to be absent.

Transfer mode requires one positive pending payment. Its refund owner is H(5,spendKey) and its recipient owner differs. Its commitment is nonzero. In settlement and withdrawal modes, all new-payment witness fields, both new payload hashes and the pending commitment are zero.

No circuit requires ordinary output commitments to be distinct from each other or from the historical commitment set. The pool enforces that condition when inserting them.

7.5 Conservation

For private asset-table slot a, define I[a] as the sum of selected input values, O[a] as the sum of selected ordinary output values, E[a] as the fee value if selected to that slot, P[a] as the pending-payment value if selected to that slot, and X[a] as withdrawn pool shares. Require:

I[a] = O[a] + E[a] + P[a] + X[a]

For settlement, P[a]=X[a]=0. For transfer, X[a]=0. For withdrawal, E[a]=P[a]=0 and at least one X[a] is positive. Each X[a] < 2^132; withdrawAssets[a] equals assets[a] when X[a]>0, otherwise zero. A zero table asset cannot carry a withdrawal.

Range bounds keep all possible sums far below r, so these equations imply ordinary integer conservation rather than merely equality modulo r. There is no cross-asset conversion inside the relation. A swap is an external token operation between a withdrawal and funded deposits.

7.6 Context binding

The circuit includes the public context in the nonlinear relation contextSquared = context * context. It does not compute the context's Keccak digest. The contract recomputes that digest from execution data and requires equality with signal 19. The proof system binds the public vector, including this field, to the relation.

7.7 Dummy evaluation and disabled branches

The fixed circuit evaluates note subcomponents even for unused branches. This is distinct from declaring those branches to be funded notes. Let eO indicate an ordinary input and eP indicate a pending input. Both are Boolean and at most one is one. For input slot i, the internally evaluated components use:

ordinary rho       = eO * (inputRhos[i] - 1) + 1
ordinary owner     = H(5, spendKey)
pending value      = eP * (inputValues[i] - 1) + 1
pending recipient  = inputRecipientKeyHashes[i] + 1 - eP
pending refund     = inputRefundKeyHashes[i] + 2*(1 - eP)
pending rho        = eP * (inputRhos[i] - 1) + 1
selected cm        = eO * ordinary cm + eP * pending cm
selected nf        = eO * ordinary nf + eP * pending nf

The ordinary component always uses the input's value; the pending component uses the positive substitute above. Both components use the selected asset and common scope. Metadata constraints in Section 7.3 set unused opening fields to zero. Membership is evaluated for selected cm; with a disabled slot, its zero siblings and index give root zero. Only the selected branch contributes value and nullifier output.

For an absent ordinary output, the evaluated rho is outputRhos[i]+1, but its public commitment is multiplied by zero. An absent fee similarly substitutes owner hash and rho equal to one and suppresses the public commitment. Outside transfer mode, the new pending-note component is omitted and its public commitment is zero. These substitutions are part of the witness relation, not wallet-selectable dummy-note parameters.

7.8 Acceptance predicate and enforcement

For operation op, public vector u and private witness w, define R_op(u,w) to hold exactly when the field relations, range constraints, branch constraints, membership calculations, ownership checks and per asset equalities above hold. A verifier accepts evidence of an existential witness; it does not receive w as calldata. Host execution then imposes additional conditions:

Predicate Circuit Pool/controller Reference client or service
Knowledge of spending secret and valid note opening Required Relies on selected verifier Constructs witness
Membership in supplied roots Required Checks recorded root and depth Reconstructs forest
Unspentness, nullifier uniqueness and reclaim age Pairwise uniqueness only Global spent set, duplicate check and timestamp Preflight and reconciliation
Output uniqueness across history Not checked Global commitment set Rejects known local duplicates
Per-asset share conservation Required Relies on selected verifier Checks proposed balance
Current scope, context and verifier generation Public-vector binding Recomputes context and selects generation Constructs matching statement
Proof expiration 64-bit range only Rejects at or after a nonzero expiration Sets it to the reviewed deadline
Backing, asset registration, fee arithmetic and minimum payout Not checked Enforced on affected public asset paths Quotes and simulates
Valid delivery encryption and recoverable archive record Hash binding only Nonzero execution/recovery bindings Encrypts, validates and obtains acknowledgments
Random sampling and confidential network access Not established Not established Required operational assumptions

This table does not permit a verifier or a client to omit a rule because another layer performs a related check. Successful simulation is evidence about execution against the simulated state; it does not reserve nullifiers, freeze policy or guarantee inclusion.

8. Execution encoding and context binding

8.1 Proof and operation structures

The EVM proof tuple is:

Proof(bytes data, uint64 verifierVersion, uint256[71] signals)
Settlement(address executor, uint256 deadline, bytes32 recoveryHash)
Transfer(address executor, uint256 deadline,
         uint256 recipientPayloadHash, uint256 refundPayloadHash,
         bytes32 recoveryHash)
Withdrawal(address recipient, address executor, address feeAsset,
           uint256 fee, uint256[8] minAssetsOut, uint256 deadline,
           bytes32 recoveryHash)

These are tuple layouts, not textual encodings. The canonical statement passed to IProofVerifier.verify(bytes,bytes) is abi.encode(signals), exactly 2,272 bytes for the monetary array or 352 bytes for the account-only array. data contains the proof only, without the raw verifier's function selector.

8.2 Domain and context construction

Let generation be the proof's UInt64 verifier version. Let domain be the Keccak hash of the indicated UTF-8 label, and let execution be the indicated ABI byte string:

Pool path Label execution
settle seal.multiasset.settle abi.encode(Settlement)
transfer seal.multiasset.transfer abi.encode(Transfer)
withdraw seal.multiasset.withdraw abi.encode(Withdrawal)
withdrawForAction seal.multiasset.action abi.encode(Withdrawal, bytes32 actionHash)

For all paths:

context = F(ABI_(bytes32,uint256,uint64,uint32,bytes)(
    domain, scope, generation, 3, execution))

In particular, the final argument is dynamically encoded bytes, including its ABI length and offset. Replacing this expression by packed encoding or by concatenating execution fields produces a different context.

8.3 Execution conditions

For all proof-bearing pool paths, recoveryHash MUST be nonzero and block.timestamp <= deadline. If the public expiration is nonzero, block.timestamp < expiration MUST also hold; zero sets no proof expiration. The deadline bound is inclusive and the expiration bound exclusive. The reference client sets expiration to the reviewed transaction deadline, or to an earlier swap-quote expiry. If executor != 0, msg.sender MUST equal executor; otherwise any caller is permitted. recipient MUST be nonzero for withdrawals.

For an ordinary withdrawal, a zero fee requires feeAsset=0; a positive fee requires nonzero fee asset and nonzero executor. The fee asset MUST appear among positive public withdrawal slots. The execution context binds the fee, recipient, all minima, executor, deadline and recovery hash. It does not fix the transaction's native gas-price fields.

The recovery hash is an opaque bytes32 binding at the contract layer. The reference archive profile uses BE32(F(ciphertext)). Contract acceptance requires nonzero bytes but does not validate that the hash is a correctly reduced archive identifier or that a corresponding record exists.

9. Pool state transitions

9.1 Construction and registration

Pool construction requires a nonzero guardian address, deployed Aave pool and registry code. It creates an immutable vault factory and, through the linked ProtocolFeesFactory library, a fee controller whose POOL is the new pool address. The fixed registry has no governor or guardian. Safe identity is a deployment-policy check; the bare constructor does not require every authority address to be a Safe.

Only GUARDIAN may register an asset. The asset must not already be registered and the asset count must be below 128. The chosen immutable factory creates or returns the fixed vault for the selected mode. Registration stores vault, mode and stablecoin flag, appends the asset, and approves that vault to receive deposits. There is no mode or classification edit after registration.

9.2 Deposit

deposit(asset, amount, precommitment, minShares, recoveryHash) requires a registered asset, amount>0, 0<precommitment<r, and nonzero recovery hash.

The transition checkpoints eligible yield fees, transfers exactly amount underlying units from the caller and calculates depositFee = floor(amount * depositBps / 10000). A registered canonical swap adapter instead pays zero deposit fee on mandatory redeposits. It deposits amount - depositFee into the registered vault, checks the actual vault share increase, and requires the final idle underlying balance to equal the preexisting balance plus the fee. The fee increases the separate depositFees[asset] ledger and creates no private or guardian pool shares. Section 10 defines the credited pool shares. Their value must be in [1,2^128) and at least minShares.

The pool increments depositNonce and uses the new value as salt. It inserts:

H(H(1, scope, asset), creditedShares, H(precommitment, salt))

A conforming depositor sets precommitment=H(ownerKeyHash,rho) and retains that opening before submission. Deposit does not prove knowledge of a usable opening. A malformed precommitment can create an unspendable funded commitment. The confirmed event supplies the actual shares and salt; the client MUST use those values when reconstructing the note.

9.3 Common spend checks

Before accepting a proof-bearing batch, the pool:

  1. Rejects a nonzero proof expiration once block.timestamp >= expiration, then checks scope and recomputed context.
  2. Requires at least one enabled input.
  3. For every disabled input, requires zero nullifier, root and depth.
  4. For every enabled input, checks kind, nonzero canonical nullifier, known root and exact depth, unspent status, within-batch uniqueness, and refund maturity where applicable.
  5. Checks that the operation permits the supplied payment, fee and public-withdrawal outputs.
  6. For withdrawal modes, validates public withdrawal slots as specified below.
  7. Requires acceptance by the active operation verifier and active generation.
  8. Marks enabled nullifiers spent and inserts all nonzero outputs in the order specified in Section 6.

Subsequent token operations are within the same atomic transaction. A proof accepted by a raw verifier alone is not sufficient to satisfy these stateful conditions.

9.4 Settlement and transfer

Settlement inserts ordinary owner outputs and an optional fee note. It may consume ordinary notes, recipient claims and mature refunds in any combination authorized by the single spending key.

Transfer additionally requires nonzero matching recipient/refund payload hashes and inserts exactly one pending-payment commitment. It emits that commitment and both hashes. Neither settlement nor transfer moves tokens or calls a vault; they change private share ownership while leaving aggregate backing unchanged.

Claim and reclaim are input kinds, not separate pool entry points. They may be included in settlement, transfer or withdrawal. A recipient can withdraw a pending payment without first settling it into an ordinary note.

9.5 Ordinary withdrawal

A positive withdrawal slot requires a nonzero 160-bit asset identifier, shares less than 2^132, registration, and no earlier positive slot for that asset. A zero-share slot requires zero asset and zero minimum. At least one slot must be positive.

After common proof checks and note-state updates, each positive asset is processed once. Recovery-mode assets reject ordinary withdrawal. Eligible yield fees are checkpointed before redemption. The withdrawn shares must not exceed total supply. Let gross be actual underlying units received from redeeming backing, bW the current withdrawal rate, and serviceFee the bound executor fee for this asset or zero:

protocolFee = floor(gross * bW / 10000)
net = gross - protocolFee - serviceFee

The subtraction must be nonnegative and net >= minAssetsOut[slot]. Protocol fees accrue in the pool's separate underlying ledger. The recipient receives net, and the bound executor receives serviceFee, with exact recipient-balance checks. Guardian payout is a separate permissionless claim, so an unavailable guardian does not have to receive tokens during the user exit.

9.6 Action-bound withdrawal

withdrawForAction(data, actionHash, proof) additionally requires a nonzero action hash, data.recipient = data.executor = msg.sender, zero executor fee and zero fee asset. It uses the action domain and withdrawal circuit. The caller is responsible for enforcing the separately bound action in the same transaction.

The source exit is exempt from the ordinary protocol withdrawal fee only when this action-domain caller is registered by ProtocolFees as a canonical swap adapter. Arbitrary external executors receive no such exemption. An ordinary withdrawal proof cannot authorize an action-domain withdrawal.

9.7 Authority limits

The guardian cannot pause an asset, activate forced recovery, redirect user backing, or replace an asset's vault. Users authorize ordinary withdrawals with their proofs. An upstream lending pause, lack of liquidity or token restriction can still prevent redemption; the pool cannot override those external conditions.

10. Backing and share accounting

10.1 Accounting definitions

For one asset, omit the asset subscript. S is total pool shares, T guardian pool shares, V accounted vault shares, and A=vault.convertToAssets(V). Asset ledgers do not share backing. A note amount is not a fixed underlying redemption amount.

Correctly supported token/vault behavior gives T <= S and accounted backing no greater than the pool's actual vault-share holdings. The aggregate private share claim plus guardian shares equals pool supply under the specified transition relation. This statement assumes sound proof verification and does not establish external-vault solvency.

10.2 Deposit credit

After the fee checkpoint and deduction of the protocol deposit fee, let m be the exact vault shares minted by the net deposit and A0 the backing value before it. The pool credits:

x = m                                  if S = 0
x = floor(m * (S + 1) / (V + 1))       otherwise
V' = V + m
S' = S + x
M' = M + (vault.convertToAssets(V') - A0)

The observed backing increase must be representable without unsigned underflow. The +1 terms are pool level virtual units, distinct from the vault's six virtual share decimals.

10.3 Redemption

For 0 < x <= S pool shares, redeem:

v = floor(x * V / S)
V' = V - v
S' = S - x
M' = 0                                 if x = S
M' = ceil(M * (S - x) / S)             otherwise

Call vault.redeem(v,pool,pool). Require its reported underlying result to equal the pool's observed underlying-balance increase. Yield checkpoints occur before this calculation for eligible underlying exits.

Let q=S-x. Fee dust is scaled using the implementation's separate integer quotients:

h  = (D*q) mod S
D0 = floor(D*q/S)
R0 = floor(R*q/S) + floor(h*10000/S)
D' = D0 + floor(R0/10000)
R' = R0 mod 10000

Combining the two floors in R0 into one division can change rounding and is not the specified algorithm. A final burn clears all fee dust. This prevents new depositors from inheriting unpaid fees associated with departed shares.

10.4 Yield checkpoint

Custody assets skip yield fee accrual. For lending assets with zero supply, the quoted new fee and dust are zero; the checkpoint may advance the high water mark. With nonzero supply and A <= M, no new fee is minted and existing dust is retained.

For A > M and nonzero supply, with the asset's effective yield rate bY = FEES.assetYieldRate(asset):

g = A - M
z = (g*bY mod 10000) + R
f = floor(g*bY/10000) + D + floor(z/10000)
R' = z mod 10000
t = floor(f*S/(A-f))
D' = f                                 if t = 0
D' = 0                                 otherwise
S' = S + t
T' = T + t
M' = A

Division, subtraction and resulting storage values must satisfy checked integer arithmetic. A whole fee that would mint zero pool shares remains in D; a fractional base-unit fee remains in R. If no new gain occurs, dust is not independently converted into shares. The high water mark is not reduced merely because backing value falls. Recovery to the prior mark is therefore not charged again as new gain.

Applying the default yield rate checkpoints all eligible assets at their old effective rates. Applying an individual asset override checkpoints only that asset at its old effective rate. An unrelated reverting valuation can prevent the global checkpoint but cannot block a healthy asset's individual update. It does not require unrelated ordinary user withdrawals to enumerate all assets.

10.5 Guardian share claims

claimFees(asset) checkpoints eligible gain. If T=0, it returns zero. Compute v=floor(T*V/S) and payout=vault.previewRedeem(v). If payout=0, burn no shares. If T != S, set the shares to burn to:

ceil(vault.previewWithdraw(payout) * S / V)

Otherwise burn the full remaining guardian balance. Reduce T, perform the ordinary share redemption, and pay the current guardian the actual result. This retains fractional guardian entitlement rather than discarding all guardian shares for a rounded-down whole-unit payout.

claimWithdrawalFees and swap-adapter claimFees claim separate underlying-fee ledgers. They clear the selected ledger before the exact payout; a failed payout reverts the clearing.

10.6 Donations

Underlying donated to a backing vault can change the value of its shares. A direct transfer of underlying or vault shares to the pool creates no note and does not increase V. Such unsolicited pool balances are outside private redemption. Neither the pool nor its vaults promise protection from token-level confiscation or transfer restrictions.

10.7 Accounting invariants

For reasoning about an accepted execution history, associate every funded commitment with its asset and assigned pool-share value. This is an abstract accounting ledger, not a publicly enumerable list of note openings. A malformed deposit whose usable opening is unknown still consumes backing and retains its assigned value; it does not release that value to other note holders. Count a pending payment once, irrespective of the number of eligible ownership branches.

Let L[a] be the sum of values assigned to unconsumed funded commitments for asset a. Under sound proofs for the specified relation, binding note hashes and conforming token/vault behavior, the intended invariant is:

S[a] = L[a] + T[a]
0 <= T[a] <= S[a]
0 <= V[vault[a]] <= vault[a].balanceOf(pool)
0 <= R[a] < 10000
0 <= W[a] + F[a] <= underlying[a].balanceOf(pool)

A deposit increases L and S by its credited shares and increases V by the measured vault-share mint. An internal transition replaces consumed note value with equal-value ordinary, fee or pending outputs for the same asset. Withdrawals reduce L and S by burned pool shares and reduce V by the accounted vault shares removed. A yield checkpoint increases T and S equally. A guardian-share claim reduces T and S equally. The separate underlying fee ledgers W and F do not represent additional note shares. Claiming either reduces the corresponding ledger and idle underlying by exactly the paid amount without changing S, T or V.

Per-asset conservation and the restriction of public deposits to registered assets imply that a sound sequence cannot create positive note value in a previously unfunded unregistered asset merely by choosing that address in a private table. The circuit does not independently consult the registry. An unsound verifier fixed at deployment, nonconforming token behavior or a cryptographic failure can invalidate the premises of this reasoning.

Neither S nor the inequalities assert a fixed exchange rate to underlying assets. Losses, gains, rounding and donation affect that rate. A solvency check must distinguish the quantity of accounted vault shares from the economic value and immediately redeemable liquidity of those shares.

10.8 Read-only accounting and quotes

The pool's query methods have the following precise semantics for a registered asset. Unknown assets revert rather than being treated as zero balances.

Query Result
accountedVaultShares(asset) Stored V, excluding unsolicited transfers
totalAssets(asset) vault.convertToAssets(V); may revert when valuation is unavailable
performanceFeeEnabled(asset) True exactly for Aave mode
assetFeeBPS(asset) Current yield rate when performance fees are enabled; otherwise zero
previewTotalShares(asset) Stored S plus the fee-share mint quoted at current NAV without changing state
previewDeposit(asset,amount) Deduct the caller's deposit fee, then apply Section 10.2 to vault.previewDeposit(netAmount) and the previewed supply
previewRedeem(asset,shares) Reject shares above previewed supply; return zero for zero shares; otherwise return vault.previewRedeem(floor(shares*V/previewedSupply))
maxDeposit(asset) vault.maxDeposit(pool), or zero if that external query reverts

previewSharesForAssets(asset,amount) applies the same share conversion as a fee free deposit. It is a valuation helper for existing share obligations, including shielded execution fees, and does not authorize a fee exemption. maxDeposit returns the underlying vault's net capacity; it is a conservative bound on a gross deposit when a positive deposit fee applies. previewDeposit is caller dependent because canonical swap adapters are exempt. Clients MUST use the intended caller and simulate the final transaction.

previewRedeem quotes gross underlying before protocol and executor withdrawal fees. It does not establish that the vault presently has enough liquidity. These queries do not checkpoint yield, allocate a deposit nonce, reserve capacity or consume a note.

11. Backing vaults and asset admission

11.1 ERC-4626 conversion

Each registered vault has one underlying asset. The pool owns ERC-4626 vault shares; its note holders own claims denominated in the pool's distinct share system. Pool-share conversion in Section 10 MUST NOT be substituted for the vault's own conversion.

Both vault implementations use the OpenZeppelin ERC-4626 virtual-asset and virtual-share convention with decimal offset 6. For vault supply sV and total underlying assets aV, the conversion ratio is:

vault shares per underlying base unit = (sV + 10^6) / (aV + 1)

convertToShares, convertToAssets, previewDeposit and previewRedeem round down. previewMint and previewWithdraw round up. Deposits producing zero shares are rejected. The vault share decimal count is the underlying decimal count plus six; custody-vault construction additionally rejects a resulting count above 24. A vault deposit measures the exact underlying increase; fee-on-transfer assets are not supported by the accounting model.

11.2 Custody mode

IdleVault.totalAssets() is its underlying-token balance. A deposit transfers underlying and mints vault shares. A redemption burns vault shares and transfers underlying. Withdrawals enforce allowances when the caller differs from the share owner and check exact vault and recipient balance changes. There is no administrator-selected strategy, borrowing, vault management fee or upgrade path. The reported supply rate is zero.

Custody mode eliminates the Aave supply dependency but retains ERC-20 issuer, token-code and transaction-execution risks. Unsolicited underlying donations affect the vault conversion rate.

11.3 Aave mode

AaveV3Vault is a supply-only wrapper around one reserve of a fixed Aave V3 pool. Construction verifies that the reserve's aToken is deployed and reports the expected underlying asset and Aave pool. The vault grants that pool an underlying allowance. It does not borrow or open leveraged positions.

totalAssets = underlying.balanceOf(vault) + aToken.balanceOf(vault)

A deposit pulls the exact underlying amount, supplies that amount to Aave and mints the calculated vault shares. Existing idle underlying remains part of vault backing. A withdrawal uses idle underlying first and requests any shortfall from Aave. The returned amount and received balance increase must agree with the request. A reserve liquidity shortfall can therefore prevent an ordinary withdrawal without invalidating ownership of the corresponding vault shares.

The reported supply rate is Aave's current liquidity rate in ray units, where one ray is 10^27. It is a rate observation, not a fixed return or a guaranteed annual percentage yield.

11.4 Aave capacity and availability

The vault reads the reserve configuration directly. An inactive or paused reserve has zero withdrawal availability. Freezing prevents new deposits but does not itself prevent withdrawals. Subject to those flags:

available underlying = idle underlying + underlying.balanceOf(aToken)
maxWithdraw(owner) = min(owner's ERC-4626 entitlement, available underlying)

maxRedeem returns the full owner share balance when all of that entitlement is available. Otherwise it is bounded by the available assets converted down to vault shares.

maxDeposit is zero when the reserve is inactive, frozen or paused. A zero Aave supply cap means no configured cap. For a nonzero cap, let cap be the configured whole-token cap, d the underlying decimal count, I the normalized income index, and q the aToken scaled supply plus accrued Aave treasury units:

capAssets = cap * 10^d
suppliedAssets = ceil(q * I / 10^27)
remaining = max(0, capAssets - suppliedAssets - pendingReserveFees)

When applicable, pendingReserveFees includes the reserve-factor share of variable-debt interest since the stored debt index, with upward rounding. The implementation uses the normalized variable-debt index and scaled variable-debt supply. Ignoring these accrued units can overstate capacity near a supply cap. These availability calculations depend on the deployed Aave interface and configuration, not merely the ERC-4626 standard.

11.5 Admission and exit limitations

The factory can create vaults permissionlessly and caches one vault for each asset/mode pair. Factory creation does not register an asset with a pool. Only the pool guardian can register it. The registered vault, mode and stable classification cannot subsequently be replaced.

Asset admission must consider token transfer behavior, decimals, reserve configuration and issuer controls. Exact accounting rejects many incompatible tokens; it does not establish that an admitted token is economically safe. The pool cannot override upstream lending pauses, liquidity shortages or issuer freezes.

12. Protocol fees and guardian governance

12.1 Rate state

ProtocolFees maintains the following rate kinds:

Kind Integer Initial rate Maximum rate Charging basis
YIELD 0 0 1,000 bps Eligible gain above the adjusted high water mark
WITHDRAWAL 1 0 20 bps Gross assets in an ordinary withdrawal
STABLE_SWAP 2 0 50 bps Exact-input trade amount when both assets are classified stable
OTHER_SWAP 3 0 50 bps Exact-input trade amount for other registered pairs
DEPOSIT 4 0 20 bps Gross amount received by the pool, before vault deposit

A stable classification is an admission-time Boolean. It does not consult an oracle or assert that a token currently trades at its target price. The deposit rate is kind 4, initially zero and capped at 20 bps of the gross amount received by the pool. There is no separate private transfer, claim or reclaim fee parameter. An intent service may charge for providing service, independently of the protocol rates.

The current fee-policy version starts at zero. Applying a rate different from the current rate increments it. Scheduling or canceling a proposal does not increment it. An asset's policy version is the current fee-policy version. These values are used by swap execution to detect changed policy.

12.2 Rate changes

The guardian can set or propose rates. The pool itself can call the yield-rate entry point. A requested rate above its cap is invalid.

  1. Clear any previous pending proposal for that rate kind.
  2. If the requested rate is greater than the current rate, store it with activateAfter = block.timestamp + 14 days.
  3. Otherwise apply it immediately. An equal-rate request can still clear a pending proposal and perform a yield checkpoint, but does not change the policy version.

Anyone may activate the currently pending proposal once its activation time is reached. The guardian may cancel it beforehand. A replacement increase starts a new notice period. Applying a yield rate checkpoints eligible assets at the old rate before changing the rate, so accrued gain is not retrospectively assessed at the new rate.

Individual lending asset rates use setAssetYieldRate, activateAssetYieldRate and cancelAssetYieldRate. The guardian can set only registered assets with performance fees enabled. Each rate has the same 1,000 bps cap and fourteen day increase delay. Explicit overrides, including zero, take precedence over the default YIELD rate. An asset without an override follows that default. Overrides have no reset to inheritance operation; the guardian can assign a new explicit rate. Activation rechecks the registered asset mode. A numerical effective rate change increments the shared policy version.

Launch-policy volume thresholds and observation periods described in Fees are operator commitments. The contracts enforce caps, authority and notice; they do not automatically measure those thresholds or enable fees.

12.3 Guardian fee recipient

The immutable guardian receives all protocol fees, including fees from the separately deployed margin adapters. There is no independent fee-recipient address or address-rotation transition. Claim functions are permissionless triggers and cannot choose a different recipient.

Production deployment tooling qualifies a canonical Safe with at least two signatures. Only the guardian address is pinned; its owners and exact threshold are read live. Signer changes require the Safe's current quorum and retain the same contract address. See Multisig administration.

12.4 Canonical swap adapters

The fee controller can create fixed code swap adapters through its factory and records the resulting addresses as canonical. This creation is permissionless, but an adapter's usable pools remain subject to the pool-admission procedure in Section 14. Only canonical swap adapters receive the action withdrawal fee treatment described there and exemption from deposit fees on their mandatory funded outputs. A caller cannot obtain that treatment merely by presenting a compatible interface.

12.5 Guardian funding of pool activity

Protocol revenue is intended to fund useful actions inside the pool, including subsidized private payments, claims, settlement and transaction submission. The objective is to reduce participation costs and support sustained activity by independent users. Guardian spending is a governance policy: the contracts collect fees but do not earmark them, set an allocation percentage or automatically execute funded actions. Any budget, eligibility rules and service reimbursements must be published separately.

More transactions do not imply proportionally more anonymity. A useful increase requires additional plausible, indistinguishable candidates for the particular observer and spend. Guardian controlled accounts, repeated self transfers and activity whose inputs are known to a cooperating operator may add little or no uncertainty. Timing, asset, public amount, input kind, service metadata and participant collusion still constrain the effective set. Funding should favor genuine use and broad participation; reports should distinguish actions funded from independently supported privacy measurements. No reward or token issuance is implemented.

12.6 Deposit fee claims

The pool holds accrued deposit fees as underlying tokens outside vault backing. Anyone may call claimDepositFees(asset), which clears that asset's ledger and transfers exactly its balance to the current guardian. Reversion restores the ledger. A blocked guardian can prevent a claim but does not cause a deposit to transfer to that address. The claimant cannot choose the recipient. Yield and withdrawal fee claims remain separate.

13. Authorized deposit adapter

13.1 Purpose and request

SealDepositAdapter lets an executor pay gas for a deposit authorized by the underlying-token owner. It uses an EIP-712 signature, not a zero knowledge proof. Its pool address and finite set of permit-supported tokens are fixed at construction. The list contains between one and 128 distinct deployed token addresses.

The signed request has the following exact field order:

struct Deposit {
    address owner;
    address asset;
    uint256 amount;
    uint256 precommitment;
    uint256 minShares;
    bytes32 recoveryHash;
    address executor;
    uint256 fee;
    uint256 nonce;
    uint256 deadline;
    bytes32 accountUpdateHash;
}

amount is the gross underlying amount taken from owner; fee is the underlying amount paid to executor. The pool receives amount - fee and then deducts its protocol deposit fee before funding the vault. The deposit adapter is not a canonical swap adapter and receives no deposit fee exemption. The auxiliary authorization contains signature: bytes and permitSignature: bytes.

13.2 Signature encoding

The EIP-712 domain has name SEAL Deposit, version 2, the current chain identifier and the adapter address as verifying contract. The exact type string is:

Deposit(address owner,address asset,uint256 amount,uint256 precommitment,uint256 minShares,bytes32 recoveryHash,address executor,uint256 fee,uint256 nonce,uint256 deadline,bytes32 accountUpdateHash)

Let typeHash = K(UTF8(typeString)). The signed digest is:

K(0x1901 || domainSeparator || K(ABI(typeHash, request fields in order)))

The adapter uses ECDSA recovery to require the signer to equal owner. The signed-deposit authorization uses the 65-byte r || s || v encoding, a low s value no greater than half the secp256k1 order, and v equal to 27 or 28. The bytes-signature entry point does not accept a 64-byte compact signature. It does not implement ERC-1271 contract-wallet signature validation. Owner nonces are independent counters initialized to zero.

13.3 Execution

The caller must equal executor; the owner must be nonzero; amount > fee; the deadline must not have passed; and the nonce must equal the owner's next nonce. The nonce is consumed before external calls and is restored if the transaction reverts.

A nonempty permit signature must have the supported 65-byte encoding. The adapter attempts an ERC-2612 permit for the gross amount and the same deadline. A failed permit attempt is tolerated because the permit may already have been submitted; the actual allowance must still cover the gross amount.

The adapter pulls the exact gross amount, approves the pool for the net amount, calls depositWithAccountState with the signed precommitment, minimum, recovery hash and eleven-field account proof, and resets the approval. It pays the executor the fee and preserves any preexisting adapter balance. The ordinary pool deposit rules still apply. A zero service fee does not make gas free; it means the executor receives no fee through this request.

The signed accountUpdateHash is K(ABI(scope,uint256[8] checkpointFields)). The account proof binds the deposit precommitment to the checkpoint owner and its context to the signed deposit action. The service sees the proof and public fields; it never receives checkpoint openings or the account file.

14. Atomic swap adapter

14.1 Action encoding

SealSwapAdapter spends private pool shares, trades a specified public amount through approved V3 pools, and deposits the output and any input refund into new ordinary notes. It uses a withdrawal proof with the external-action context from Section 8.

The exact action tuple is:

struct SwapAction {
    address tokenIn;
    address tokenOut;
    address uniswapPool;
    uint24 poolFee;
    uint256 amountIn;
    uint160 sqrtPriceLimitX96;
    uint256 minAssetsOut;
    uint256 minSharesOut;
    uint256 outputPrecommitment;
    uint256 refundPrecommitment;
    uint256 minRefundShares;
    address relayer;
    uint256 relayerFee;
    uint256 deadline;
    bytes32 recoveryHash;
    uint256 sourcePolicyVersion;
    uint256 destinationPolicyVersion;
    bytes path;
}

Using the standard ABI tuple above:

actionHash = K(ABI_(bytes32,uint256,address,address,SwapAction)(
    K(UTF8("seal.swap.v1")), chainId, pool, adapter, action))

The caller must be action.relayer. A self-submitting user can name their own address and set the service fee to zero. The caller cannot be the adapter itself. The deadline, recovery hash, policy versions and output terms are part of the action binding.

14.2 Withdrawal and fee reservation

Only withdrawal slot zero is used: its asset is tokenIn, and its shares are positive. Every other withdrawal-share slot is zero. The execution recipient and executor are the adapter, and the execution's separate fee fields are zero.

Let bS be the current pair-tier swap rate and bW the current withdrawal rate:

swapFee = floor(amountIn * bS / 10000)
relayerPrivacyFee = floor(relayerFee * bW / 10000)
requiredInput = amountIn + swapFee + relayerFee + relayerPrivacyFee
minWithdrawnAssets[0] = requiredInput
minWithdrawnAssets[1..7] = 0

For a canonical swap adapter, the pool does not charge the ordinary withdrawal fee on the full action withdrawal. The adapter instead accounts for the swap fee and the withdrawal-rate fee on the public intent service payment. The service fee is additional to these protocol fees.

The source and destination policy versions must match before and after external execution. An effective fee change during execution causes the action to revert.

Clients must read the current controller rates when preparing an action. The SDK's standalone swap-fee helper has a ten-basis-point convenience default; that default is not an onchain rate and must not replace deployment-state lookup. Newly constructed fee controllers start at zero.

14.3 Pool admission

Each adapter fixes its V3 factory, guardian and a positive pool-admission delay at construction. This delay is a deployment parameter, separate from the fourteen-day fee-increase delay.

The guardian may schedule a pool only if it is deployed, its ordered tokens are registered pool assets, and the configured factory identifies it as the canonical pool for those tokens and fee tier. After the adapter's delay, only the guardian may activate the proposal; activation revalidates the pool. Replacement resets the pending time. The guardian can disable a pool and clear its pending proposal. Disabling trading does not remove notes or asset backing from the core pool.

14.4 Routes and limits

The input and output tokens must differ. amountIn is positive and at most the largest signed 256-bit integer. sqrtPriceLimitX96, minAssetsOut and minSharesOut are positive. Both precommitments are nonzero canonical field elements.

An empty path denotes the explicit single pool in the action. A nonempty path has V3 packed encoding:

token[0]: Bytes[20] || fee[0]: UInt24 big-endian || token[1]: Bytes[20]
                  || fee[1]: UInt24 big-endian || token[2]: Bytes[20] || ...

There are one to three hops, so the path has 43, 66 or 89 bytes. Tokens must not repeat. The first and last tokens equal the action's input and output. The first pool and fee agree with the explicit action fields. Every hop must resolve to an enabled canonical pool with registered tokens.

The explicit price limit applies to the first hop. Subsequent hops use the permitted near-boundary limit for the direction:

token zero to token one: 4295128740
token one to token zero: 1461446703485210103287273052203988822378723970341

These are limits on square-root price in Q64.96 encoding, not minimum-output checks. The final output must independently satisfy minAssetsOut and the funded note must satisfy minSharesOut.

14.5 Callback and settlement

For each hop, transient state authorizes one pool, one input token, one direction and the full input budget. A callback must originate from that pool, carry the expected empty callback data, request a positive input delta and report a negative output delta. The requested input must equal the unconsumed budget; the adapter clears that budget before paying. Partial-input fills and a second payment callback are rejected.

After the swap, reported deltas and observed balance changes must agree exactly. The output of a hop funds the next hop. On final settlement, the adapter accrues the protocol fees, pays the service fee, deposits all output under outputPrecommitment, and deposits any excess withdrawn input under refundPrecommitment. A zero refund requires a zero refund-share minimum. Temporary pool allowances are cleared.

Final balances must preserve the adapter's preexisting balances plus the newly accrued protocol fees. Any swap, fee payment, output deposit, refund or balance-check failure reverts the complete action, including the initial private spend. Routes, traded assets, amounts, price limits, output terms and callback transfers are public.

15. External execution boundary

15.1 Pool authority

SEAL supplies the dedicated deposit adapter and Uniswap V3 swap adapter. It does not deploy a general external call executor.

The pool's withdrawForAction entry point remains available for proof-bound external actions, including private margin funding. Its proof binds the caller, destination and action hash. The pool does not grant a caller authority to invoke arbitrary operations as the pool or a vault. A valid authorized exit can transfer the owner's own assets to a contract; SEAL cannot prevent a recipient from subsequently borrowing against its own collateral outside the pool.

Aave vaults expose supply and withdrawal of their own reserve and do not expose borrow, flashLoan or approveDelegation. The pool has no arbitrary call or delegatecall entry point. The canonical swap adapter invokes admitted V3 pools with a constrained callback and returns purchased assets and change to the pool. This construction does not authorize Aave debt against pool or vault collateral. This claim concerns SEAL contract authority, not all downstream uses of withdrawn assets.

15.2 Spot margin extension

The margin extension uses SealMarginAccount, SealMarginAdapter, SealPublicMarginAdapter and MarginFees. Private actions require a checkpoint transition. Deployments MUST explicitly configure and qualify these contracts.

Each account is a deterministic clone with an immutable adapter, Aave lender and Uniswap factory. Several markets can share one account. Aave aggregates supplied collateral, variable debt and health by account address, rather than by trade lot. Separate accounts have separate nonces and collateral. Neither the pool nor its backing vaults delegates borrowing power to a margin account or adapter.

Public actions require controller authorization, either by direct caller or a relayed EIP-712 signature, and advance its public revision. Every private action requires a controller signature and proof of the position-specific secret owner tag. The private signing domain is SEAL Margin, version 2. AccountMarginAction(bytes32 actionHash,bytes32 accountUpdateHash) wraps the inner action hash; AccountMarginBatch(bytes32 batchHash,bytes32 accountUpdateHash) wraps the ordered batch hash. Funded execution requires a 71-field withdrawal proof and an empty account-only proof; unfunded execution requires an empty monetary proof and an 11-field account-only proof. A private batch consumes one checkpoint for all its steps. Authorization binds the chain, adapter, pool, account, nonce, deadline, market, amounts, fee ceilings, swap limits and settlement terms. A failed action MUST revert funding, account changes and nonce consumption atomically.

Operation Execution
OPEN Fund collateral, flash additional collateral, supply, borrow the debt asset and swap to repay the flashloan. Also used for increases and account reuse.
ADD_COLLATERAL Supply freshly funded collateral without initiating additional borrowing.
REPAY Repay variable debt with fresh funding; return excess funding.
REDUCE Flash debt, repay part, withdraw a bounded collateral amount and swap to repay the flashloan.
CLOSE Repay all selected debt, withdraw selected collateral and return residual proceeds, or hold private proceeds pending settlement.
SHIELD Deposit retained private proceeds into the pool.
WITHDRAW_PENDING Pay retained proceeds to the explicitly authorized public recipient.

OPEN, ADD_COLLATERAL and REPAY require fresh funds. REDUCE and CLOSE do not require fresh funding when released collateral covers execution. Remaining shared debt may prevent a selected collateral withdrawal. A CLOSE action does not promise to close every reserve in a shared account.

Aave enforces reserve eligibility, lending limits and liquidation conditions. The extension adds no leverage cap. A nonzero signed minHealthFactor makes OPEN, REDUCE and CLOSE revert unless Aave's post-action health factor meets it; ADD_COLLATERAL and REPAY ignore it so rescues remain available. Flash callbacks require the active Aave request and exact parameters; swap callbacks require the active canonical pool and enforce the authorized input budget. Temporary token approvals are cleared. There is no arbitrary account call or administrative sweep.

Both margin protocol fee rates start at zero, with an immutable 10 basis point cap. Decreases apply immediately; increases need fourteen days' notice, which a new schedule restarts and the guardian can cancel. CLOSE and REDUCE charge the floor of actual gross collateral withdrawn from Aave times the rate divided by 10,000. The base includes collateral sold to repay debt. OPEN, ADD_COLLATERAL and REPAY carry no SEAL margin fee. Each close or reduction enforces its signed maximum protocol fee and net receipt minimum; failure reverts the complete action or batch. Pending-shield closes deduct once; SHIELD and WITHDRAW_PENDING release settled proceeds without another margin charge. Accrued fees stay in the adapter's separate ledger; permissionless claimFees pays only that ledger to the configured guardian, so a blocked guardian cannot prevent a position close and donations cannot be swept. The planned later private rate is 5 bps and public rate is zero, subject to the published volume policy and full notice. Lending interest, flash premiums, DEX costs, gas and execution-service fees are separate.

Margin balances, amounts and liquidation activity are public. Private funding aims to conceal the wallet link; it does not conceal Aave state. Liquidation is permissionless when Aave's conditions hold, and can affect all collateral in the same account. It grants no authority over separate SEAL notes or other accounts.

15.3 Client valuation and execution review

The client distinguishes read-only position details from management. P&L is a display-only account metric: current collateral minus debt plus pending proceeds, plus cumulative net returns, minus cumulative contributed capital. Values use USD oracle prices at the valuation block or historical cash-flow block. Prior closed trades remain part of a reused account's lifetime result. A client MUST NOT present missing funding or price history as zero P&L, or label shared account totals as independent market profits. A displayed valuation does not authorize execution.

The display includes percentage return on cumulative contributed capital. Saved sandbox positions without an opening record use an explicitly labelled, persisted tracking reference. Their P&L and denominator include this reference and later cash flows; the client does not claim a return before tracking began. Resetting the sandbox clears positions, scenario price overrides and unfinished Settings drafts.

Profit-taking is an explicitly reviewed REDUCE or CLOSE action. The protocol does not provide automatic conditional orders or keeper authority. A swap-page draft carries only the selected pair and input amount into the detailed review; the review obtains a fresh quote and applies the existing minimum-output, slippage, fee, expiry and submission checks. See Margin trading for user guidance and Margin Risk for limitations.

The swap page requests an indicative output after one second without input. Each amount or pair change invalidates that estimate and cancels obsolete requests. The indicative output is not an executable quote; review still obtains fresh execution bounds and fees. Sandbox fallback pricing is explicitly identified.

16. Concrete proof system

16.1 Instantiation and artifacts

SEAL instantiates the batch relation using PLONK with KZG commitments over BN254. There are separate proving and verification keys for settlement, transfer and withdrawal. Recovery and action withdrawal use the withdrawal circuit with distinct context domains. They are not separate proof relations.

The reference toolchain pins snarkjs 0.7.6 and its associated arithmetic implementation. Circom compilation uses the pinned compiler and --O1, followed by the repository's certificate-checked elimination of eligible private affine constraints. Public-signal ordering and witness indices are preserved. This optimization is a transformation of the constraint system, not permission to delete arbitrary constraints.

The development keys use n=2^19 rows for monetary circuits and n=2^15 rows for the account-only circuit. Domain size, selector commitments and permutation commitments are key properties; they are not encoded independently in every proof. Production acceptance requires matching circuit sources, compiled relation, proving key, verification key, generated verifier, browser artifacts and release manifest. A specification alone does not establish that this correspondence has been independently reviewed.

16.2 Groups and verification key

Let G1 and G2 be the prime-order BN254 groups used by the host chain's pairing precompile, with scalar field Fr. G1 points satisfy y^2=x^3+3 over the base field Fp. Let e: G1 × G2 -> GT be the bilinear pairing. Point addition and scalar multiplication below are group operations; all coefficients are computed in Fr.

The verification key contains:

The generated verifier fixes these values in its code. The key coordinates in the pinned generated sources are authoritative; substituting a key with the same public-input count does not preserve the proof relation.

G1 has generator (1,2). G2 is the order-r subgroup on the twist y^2=x^3+3/(9+i) over Fp[i]/(i^2+1). The concrete generator and setup coordinates are supplied in the contract reference docs/public/spec/seal-interfaces.json. Its extension-field pairs are [a,b] for a+b*i. For the EVM pairing call, each coordinate is encoded with the imaginary component first, then the real component, as specified by EIP-197. Thus a pairing pair occupies six words: G1 x,y, then G2 x_imag,x_real,y_imag,y_real. The two-pair check in Section 16.5 occupies 384 bytes. Precompile group validation includes the G2 subgroup requirement; a curve-equation check alone is insufficient for arbitrary G2 inputs.

The keys use k1=2, k2=3. Each circuit's exact domain size, primitive root and complete verification key are published in the contract reference. An equal public-input count does not imply an equal relation or key.

16.3 Proof serialization

The proof is exactly 24 consecutive 32-byte big-endian words, equivalent to ABI encoding uint256[24]. There is no point-compression flag, length prefix or operation byte inside this proof value.

Word indices Value
0–1 A.x, A.y
2–3 B.x, B.y
4–5 C.x, C.y
6–7 Z.x, Z.y
8–9 T1.x, T1.y
10–11 T2.x, T2.y
12–13 T3.x, T3.y
14–15 Wxi.x, Wxi.y
16–17 Wxiw.x, Wxiw.y
18 a
19 b
20 c
21 s1
22 s2
23 zw

The nine points are commitments and opening witnesses. The six scalars are polynomial evaluations. They do not encode plaintext notes.

Verifier range behavior. The generated raw verifier checks each of the 18 coordinate words against the base-field modulus p and each of the six evaluations against the scalar modulus r. The encoding (0,0) is accepted as the group identity, following EIP-196; every other point must satisfy the curve equation modulo p. Both bounds are strict, so modular aliases are rejected. An empty proof is rejected.

The adapter checks the proof and statement lengths and the raw verifier's runtime code hash before forwarding the static arrays. It does not add its own public-input range check. The raw verifier checks every public word against r before hashing them into the transcript, so a public-input alias >=r is rejected. The pool independently constrains several public values, including the expiration.

16.4 Fiat–Shamir transcript

Define enc as concatenation of 32-byte big-endian scalar words and uncompressed point words (x,y). It has no ABI dynamic offsets. Write pub[0..61] for the statement words in Section 7. The transcript challenges, each reduced using F, are:

beta  = F(enc(Qm, Ql, Qr, Qo, Qc, S1, S2, S3, pub[0..61], A, B, C))
gamma = F(BE32(beta))
alpha = F(enc(beta, gamma, Z))
xi    = F(enc(alpha, T1, T2, T3))
v     = F(enc(xi, a, b, c, s1, s2, zw))
u     = F(enc(Wxi, Wxiw))

The preimage lengths are respectively 2,688, 32, 128, 224, 224 and 128 bytes. There is no additional SEAL string prefix in this inherited PLONK transcript. SEAL's pool, operation and execution binding is supplied through the public inputs and selected relation.

16.5 Polynomial checks

All arithmetic in this subsection is in Fr. Let:

xn = xi^n
Zh = xn - 1
L_i = omega^i * Zh / (n * (xi - omega^i))       for i = 0..61
PI = -sum(pub[i] * L_i, i = 0..61)
r0 = PI - alpha^2 * L_0
     - alpha * (a + beta*s1 + gamma)
             * (b + beta*s2 + gamma) * (c + gamma) * zw

The required denominator inverses must exist. The implementation's batched inversion verifies the inverse rather than accepting an unchecked modular-exponentiation result.

Form the linearization commitment:

D = Qc + (a*b)*Qm + a*Ql + b*Qr + c*Qo
    + (alpha*(a + beta*xi + gamma)
             *(b + beta*k1*xi + gamma)
             *(c + beta*k2*xi + gamma) + alpha^2*L_0 + u)*Z
    - (alpha*beta*zw*(a + beta*s1 + gamma)*(b + beta*s2 + gamma))*S3
    - Zh*(T1 + xn*T2 + xn^2*T3)

Fc = D + v*A + v^2*B + v^3*C + v^4*S1 + v^5*S2
Ec = (-r0 + v*a + v^2*b + v^3*c + v^4*s1 + v^5*s2 + u*zw)*G1

The final acceptance equation is:

e(-(Wxi + u*Wxiw), X_2)
  * e(xi*Wxi + u*xi*omega*Wxiw + Fc - Ec, G2) = 1

The verifier uses host precompiles for modular exponentiation, BN254 addition, multiplication and pairing. Failure of required precompile operations or of the final pairing rejects the proof. The registry treats a verifier revert as verification failure. These equations describe the pinned verifier; they do not assert a security proof for custom changes to the transcript or arithmetic.

16.6 Setup and prover randomness

KZG binding depends on an appropriate structured reference string and the setup's toxic waste remaining unknown. The repository's development pipeline uses the pinned Powers of Tau input:

file:   ppot_0080_19.ptau
bytes:  604073106
SHA256: d0cadfb7d5bad6c5e0b012fb1c4bbaa3cc98fe249988f9f99d1818175e4c2015

A digest identifies bytes; it does not prove ceremony honesty or independent production acceptance. The setup provenance and verification procedure are part of the release evidence. PLONK uses the universal setup for circuit-specific key generation; this pipeline does not introduce a separate per-circuit Groth16 ceremony.

The prover must use fresh cryptographically secure blinding randomness. Witnesses contain spending keys and note openings and remain local in the reference client. Sending a witness to a remote prover changes the privacy and key-custody model and is not the reference submission profile.

16.7 Compact proving artifacts

The browser distribution can use the seal-plonk-rows-v1 compact key format. It is a prover-storage format, not a different onchain proof. The container uses magic zkcr, version 1, and the pinned parser and binary-section conventions. Its sections are:

Section Size in bytes Purpose
1 4 Protocol identifier
2 796 Key header
3 72*nAdditions Witness additions
4–6 4*nConstraints each Three wire maps
7–11 32*n each Five selector arrays
12–13 0 Omitted derived sections
14 64*(n+6) Required setup points

Wire-map indices are little-endian 32-bit integers; they are not ABI words. The runtime reconstructs permutation polynomials from the three wire maps, including padded rows assigned to wire zero, and computes required derived polynomials. It rejects malformed section counts, sizes, variable references and unsupported domain sizes. Consumers must use the matching parser, arithmetic representation and integrity manifest; converting this table into an ad hoc ABI decoder is incorrect. The reference parser and build pipeline are linked in Section 26.

17.1 Fixed bundle

VerifierRegistry is a fixed dispatcher. Construction takes four deployed verifier adapter addresses and a nonzero artifact manifest hash. The addresses, their runtime code hashes and manifest hash are immutable. There is no owner, governor, guardian, pending bundle, proposal, cancellation, activation or calendar gate. No account can replace a route after construction.

The array order is SETTLE, TRANSFER, WITHDRAW, ACCOUNT. The deployment helper accepts raw verifier addresses in release manifest order WITHDRAW, TRANSFER, SETTLE, ACCOUNT and rearranges them explicitly. SEAL uses one proof system, PLONK over BN254, with a separate circuit and verification key for each operation. These are four fixed operation routes, one per operation.

17.2 Acceptance

activeVersion is the constant zero. The execution field verifierVersion is part of the execution format and domain and MUST equal zero. An unsupported operation, a nonzero version or a selected adapter whose runtime code hash differs from its constructor pin is rejected. The PLONK adapter separately pins its raw verifier's code hash and checks the proof and statement encodings. A verifier revert is treated as failure.

An accepted deployment MUST use immutable verifier implementations without proxies or mutable verification dependencies. A code hash identifies bytecode; it does not establish soundness or fix mutable behavior behind that code. Release acceptance includes the complete dependency closure, verification keys and matching circuits. The constructor's code existence check is not a cryptographic review.

17.3 Successor pool

18. Encrypted payment delivery

18.1 Purpose and keys

A commitment hides its opening but does not communicate that opening to a recipient. A payment therefore creates authenticated encrypted delivery records. Each pending payment has a recipient envelope and a refund envelope, encrypted to the corresponding viewing public keys. A shielded execution fee can have a fee envelope. The sender knows the pending opening; encryption protects its transmission and storage from parties without the relevant viewing key.

The viewing primitive is X25519. Its public keys and shared secrets use the native 32-byte X25519 encoding, not the big-endian scalar encoding used for note fields. Encryption uses HKDF-SHA-256 and ChaCha20-Poly1305 with a 32-byte key, 12-byte nonce and 16-byte authentication tag.

18.2 Envelope layout

Every payment or fee envelope is exactly 384 bytes:

Offset Length Value
0 1 Format byte 0x01
1 32 Ephemeral X25519 public key E
33 12 AEAD nonce
45 2 Discovery tag
47 321 Encrypted plaintext
368 16 Poly1305 authentication tag

The plaintext starts with one role byte followed by ordered 32-byte big-endian integers:

Role Byte Ordered fields Random padding
Recipient 0 scope, asset, value, recipientKeyHash, refundKeyHash, rho, secret 96 bytes
Refund 1 Same seven fields 96 bytes
Fee 2 scope, asset, value, ownerKeyHash, rho, salt 128 bytes

The pending plaintext deliberately excludes its two payload hashes. This avoids a circular dependency between an encrypted opening and its own ciphertext hash. Both hashes are attached when reconstructing the final pending note.

18.3 Encryption algorithm

For receiver public key P, generate fresh random padding, ephemeral secret e, and nonce. Compute:

E = X25519.publicKey(e)
shared = X25519(e, P)
salt = E || P
key = HKDF-SHA256(shared, salt, UTF8("seal.multiasset.payload-key"), 32)
viewTag = HKDF-SHA256(shared, salt, UTF8("seal.multiasset.view-tag"), 2)
header = 0x01 || E || nonce || viewTag
envelope = header || ChaCha20Poly1305.Encrypt(key, nonce, plaintext, AD=header)
payloadHash = F(envelope)

The SDK rejects invalid X25519 shared-secret cases through the underlying primitive. All fresh randomness must come from a cryptographically secure source. A deterministic random source is appropriate only for explicitly marked test vectors.

A transfer constructs both envelopes first, computes their hashes, and then computes the pending commitment containing those hashes. The public transfer statement includes both hashes. The archive identifier is their 32-byte big-endian, field-reduced hexadecimal representation, not the unreduced Keccak digest.

18.4 Discovery and validation

A scanner can derive the expected two-byte tag locally from its viewing secret and an envelope header. Under independent uniformly distributed tags, an unrelated candidate passes this filter with probability 2^-16. The tag is an efficiency filter; it is not sufficient authentication or an anonymity-set measurement.

After fetching the candidate ciphertext, the client must validate its size and format, content hash, tag, AEAD authentication, role, note-field ranges and expected scope. To recognize a pending note, it combines the decrypted core with the two public payload hashes and recomputes the onchain commitment. The recipient can use the recipient envelope without possessing the refund ciphertext, and conversely for the sender. A fee envelope reconstructs the ordinary fee-note commitment.

A viewing key reveals openings addressed to that key. It does not replace the spending secret needed to satisfy the ownership constraint. Because addresses reuse a viewing key, later disclosure of that secret can reveal retained historical envelopes; SEAL does not provide forward secrecy against that disclosure.

After reconstructing an opening, its holder can derive the corresponding nullifier and observe whether it has been published. A pending-payment sender can therefore recognize consumption of that payment even when the spending proof hides its leaf position. A viewing-key holder has the same observation capability for successfully decrypted openings. Neither observation by itself reveals the spender's new private output openings or proves which eligible branch was chosen beyond the public input-kind information and available correlations.

The reference client's privacy profile downloads the full public ciphertext candidate set for its scan range before privately filtering it. A remote service must not be asked only for tag matches if the client intends to hide which public candidates matched. This fetch policy reduces one access-pattern leak; it does not make traffic timing or network origin private.

19. Encrypted operation recovery

19.1 Recovery record versus payment envelope

Payment envelopes deliver individual note openings to viewing keys. Operation recovery records store account state needed to restore the initiating wallet, including funded outputs, operation history and relevant pending state. They are encrypted with an independently derived recovery key. Neither record type can be reconstructed from a public commitment alone.

A pool transaction binds a nonzero bytes32 recoveryHash. The contract treats it as an opaque binding and event value: it does not parse a recovery record, verify archive replication, or require that the hash is a reduced field element. The reference client uses the content-addressed construction below and ensures availability before submission.

19.2 Plaintext and padding

Recovery data is UTF-8 JSON. The SDK's recovery serializer represents a bigint as the sole-key object:

{ "$sealBigInt": "123456789" }

The marker name is reserved in input objects. On decoding, the string must match ^-?(0|[1-9][0-9]*)$; semantic field validation follows deserialization. This tag differs from the portable account file's $bigint tag.

Let j be the JSON bytes. len(j) must be at most 262,140. Construct:

paddedLength = max(1024, 1024 * ceil((4 + len(j))/1024))
plaintext = BE4(len(j)) || j || random padding to paddedLength

BE4 is an unsigned four-byte big-endian integer. The maximum padded plaintext is 262,144 bytes. Padding hides exact serialized length within a 1,024-byte bucket; it does not make all recovery records equal in size.

19.3 Encryption and content addressing

Let rk be the 32-byte recovery key and scope the pool scope. Generate a fresh 12-byte nonce:

info = UTF8("seal.recovery.record")
key = HKDF-SHA256(rk, BE32(scope), info, 32)
header = 0x01 || nonce
AD = info || BE32(scope) || header
record = header || ChaCha20Poly1305.Encrypt(key, nonce, plaintext, AD)
recordHash = BE32(F(record))

The ciphertext length is paddedLength + 29, between 1,053 and 262,173 bytes. A decoder checks the format, bounds, block-size relation, authentication, internal JSON length, fatal UTF-8 decoding and deserialized value. An archive response must also match its requested content identifier.

19.4 Application record and reconciliation

The reference application's operation record has format seal-operation and contains scope, a millisecond createdAt timestamp, ordinary-note openings and history. Optional fields carry input identifiers, payment envelopes, transaction state and relay-job state. These fields are an application recovery schema, not public proof signals.

A decrypted record is not sufficient evidence that a note was funded or remains unspent. The wallet must reconcile it against canonical pool events, observed commitments, receipts and nullifiers. Restoring a fabricated or abandoned local draft must not create a spendable balance.

The wallet stores its outbox before broadcasting and obtains the configured archive-write acknowledgments for required records and envelopes. Public-chain recovery discovers hashes attached to confirmed pool operations, obtains their ciphertexts, and attempts decryption locally. Unsubmitted drafts and some pending operation state have no public-chain reference and require the portable backup or surviving local state.

19.5 Public scan and optional journal

The default profile performs recovery discovery from public chain hashes and public archive candidates. It does not read or write a stable account journal locator. Public scan caches must be populated from public enumeration, not selectively seeded with the account's private receipts, because that can make subsequent network requests reveal ownership.

The optional journal derives a stable locator:

locator = HKDF-SHA256(indexingKey, BE32(scope), UTF8("seal.recovery.journal"), 32)

A journal entry refers to an encrypted index-pointer record with format seal-index, the scope and the operation record hash. The journal stores hashes of those encrypted pointers. Encryption conceals their content but the repeated locator still groups requests for the archive operator. Journal use is therefore an explicit change in the metadata-privacy profile.

19.6 Availability and replicas

The reference archive client supports one to four distinct archive instances and a configured write quorum from one through the replica count; the default requires all configured replicas. Writes are attempted across replicas and succeed only after the quorum acknowledges. Reads accept the first valid content-addressed result. Each replica has its own pagination cursor; cursors must not be transferred between services, and merged results are deduplicated by hash.

Replication improves availability only to the extent that the replicas fail independently and retain data. A transaction binding a hash does not force any party to store its preimage. Loss of all usable encrypted records and local openings can permanently prevent spending even while the onchain commitment remains valid.

20. Portable account file

20.1 Outer format

Current portable exports use seal-account envelope version 1, with AES-256-GCM and UTF-8 additional authenticated data seal-account:1. The other envelope fields are as below. Their plaintext is ordinary JSON:

interface MultichainAccount {
  format: "seal-multichain-account";
  version: 1;
  identity: string;
  networks: Record<string, Record<string, string>>;
}

The first key is the decimal chain ID. The second is the existing deployment context seal:account:v1:<chainId>:<deploymentId>:<lowercaseWalletAddress>. Each value is an encrypted seal-account-vault v1 deployment vault, preserving the original graph context and revision hashes. identity is the canonical digest of the full encoded private-key identity, as in the graph format. The schema has no fixed network list. Unsupported networks survive imports and exports without supplying an RPC URL or being activated.

A portable account file is UTF-8 JSON with:

format:    "seal-account"
version:   1
keyFormat: 1
iv:        base64 encoding of 12 random bytes
ciphertext: base64 encoding of AES-256-GCM ciphertext and authentication tag

20.2 Inner account schema

The decrypted account vault has version=1, keyFormat=1 and a mandatory validated graph. The checkpoint and durable pending transitions are mandatory for covered private actions. It contains key material, notes and activity, with optional contacts, transactions, relay jobs, payment envelopes, recovery records, outbox state, balance cache and state/backup timestamps.

New vaults record createdAt and the canonical deployment enrollment anchor. Unlock decrypts and validates the saved ledger, checkpoint, margin positions and balance projection, then renders them without a chain scan. A background canonical-block check queries the checkpoint marker; an unexpected spent marker blocks private spending and asks for the latest file or Google synchronization. Public margin uses the controller revision counter. Incoming delivery and live valuations refresh independently. Membership paths are constructed when spending, using bounded cached public ranges and chain-checked frontiers. A timeout preserves the saved view but cannot authorize a spend. Pending transitions are persisted before proofs or signatures are exposed, and receipts commit financial outputs and the checkpoint together.

transferRecords uses chainId:lowercasePool:scope:commitment keys, with authenticated nullifier and payload hashes, transfer transaction, recipient/refund role and the canonical observation block/hash. Settlement evidence records claim/reclaim, transaction hash, block/hash and the block's UTC timestamp. A confirmed settlement suppresses older unspent display observations; later canonical scans may invalidate it after a reorganization. These records provide immediate file recognition, never proof inputs.

The account serializer uses $bigint string objects for bigint values and $bytes arrays for byte values. These are distinct from the recovery-record serializer. Import validates supported formats and key shape and checks the account identity against the currently unlocked account. Merges require matching key identity and compatible checkpoint lineage. A newer confirmed checkpoint replaces the monetary view; equal-checkpoint files may merge independently discovered incoming notes. Conflicting checkpoints require explicit synchronization. Display caches are not authoritative chain state.

The current importer bounds input at 8,000,000 JavaScript string code units. It does not treat every authenticated historical activity field as an established chain fact. Individual notes must still be scoped to the appropriate chain/pool and reconciled before being counted as spendable.

The file is intended for the same wallet-derived account. A cloud-storage identity does not derive the encryption key. A browser's nonextractable-key flag does not protect against malicious code executing in that same origin and invoking the key. Backups and unlocked browser state therefore require confidentiality even though the exported file is encrypted.

20.3 Payment receipt file

A portable payment receipt uses JSON format seal-transfer, version 1. It includes the chain identifier, pool address, decimal scope and commitment, the two decimal payload hashes, a transaction hash and at least one of the recipient/refund 384-byte envelopes encoded as hexadecimal. The reference parser limits the textual receipt to 32,000 characters.

A receipt is a delivery aid, not a bearer token or a proof of finality. The consumer must verify its pool identity, payload hash, successful decryption, reconstructed commitment and canonical funding event. Supplying a receipt does not give a third party the spending secret. Payment-request URLs are a separate user-interface format and may disclose the requested asset and amount to anyone who obtains the URL.

21. Archive and intent service profiles

21.1 Transport and trust boundary

The reference service exposes JSON over HTTP. Production clients should use authenticated HTTPS transport. Decimal strings encode large integers; hexadecimal strings encode byte values. Service URLs, transport limits, admission difficulty, retention and pricing are operator configuration, not pool consensus parameters.

The archive receives ciphertexts and public metadata, not spending keys. The intent service receives public proof and execution data, not a private witness. Neither service's positive response substitutes for canonical-chain inclusion. Conversely, an unavailable service does not add an onchain authorization requirement: a user with the necessary openings, artifacts and gas can submit an otherwise valid supported operation directly.

21.2 Archive endpoints

Method and path Behavior
GET /health Service readiness and configured capabilities; readiness failure returns 503
POST /archive/batch Store content-addressed blobs and optional discovery headers or optional journal entries
GET /archive/blobs/{hash} Return the bytes identified by the reduced content hash
GET /archive/headers?after=N&limit=L Enumerate public discovery headers
GET /archive/journals/{locator}?after=N&limit=L Enumerate optional optional journal entries

The batch request contains a required blob collection and may contain headers and one journal update:

type ArchiveBatch = {
  blobs: { hash: string; data: string }[];
  headers?: { hash: string; ephemeralKey: string; viewTag: string }[];
  journal?: { locator: string; hashes: string[] };
};
type Page<T> = { entries: T[]; next: number; hasMore: boolean };

Blob data is hexadecimal ciphertext. Hashes and journal locators are 32-byte hexadecimal strings; an ephemeral key is 32 bytes and a view tag is two bytes. A successful batch response is { "stored": true }. Blob retrieval returns { hash, data }, rather than an unframed binary body. Header entries add a numeric sequence to the header object; journal entries are { sequence, hash }. The default request-body limit is 2,097,152 bytes. Hash/length validation prevents a service acknowledgment from legitimately naming different bytes, but the client must still verify responses.

Pagination uses an increasing service-local sequence cursor. The default page limit is 100; permitted explicit limits are 1 through 500. The next cursor is the final returned sequence. Clients must reject malformed pagination such as a nonadvancing cursor or an empty page claiming more data. Service indexes accelerate discovery; canonical pool events remain the source of funding and spent-state facts.

21.3 Admission work

Archive writes and quote requests can require admission work to limit unsolicited load. This is service-level anti-abuse work, not mining, staking or a condition for onchain proof validity.

A challenge is returned with HTTP 428 and an admission object containing token, difficulty and expiresAt. The token has one to twelve decimal digits encoding a five-minute epoch, a period, then 64 lowercase hexadecimal digits containing the server's authentication code. Difficulty is a leading-zero-bit count between 1 and 25; a server configuration of zero disables the requirement. Expiration is a Unix timestamp in seconds.

For the challenged request path and compact JSON serialization of its parsed body, compute:

bodyHash = SHA256(UTF8(JSON.stringify(parsedBody)))
workHash = SHA256(UTF8("seal.admission.v1:" + token + ":" + path + ":")
                  || bodyHash || UTF8(":" + nonce))

The nonce is 24 lowercase hexadecimal characters. The reference solver concatenates eight random bytes encoded as 16 hexadecimal characters with an eight-character hexadecimal counter. The first difficulty bits of workHash, in most-significant-bit order, must be zero. The retry includes header x-seal-work: token:nonce. The current server checks the parsed body's JSON serialization; hashing an arbitrarily whitespace-formatted HTTP body instead is not the specified client procedure. The work is path- and body-bound, single use while tracked, and subject to expiration and server verification.

21.4 Quote and submission

POST /relay/quote accepts an operation and fee asset. Supported operation names are settle, transfer, withdraw, swap and deposit, plus margin and publicMargin when the corresponding adapters are configured (Section 15.2); general external actions are not paid-relay operations in this profile. The quote request limit is 4,096 bytes.

A quote identifies a random 32-byte identifier, operation, chain, pool, scope, executor, fee asset, fee, deadline and applicable adapter addresses. For shielded fees it supplies the intent service's spending-key hash and viewing public key. It may include a network-cost estimate. Settlement and transfer fees are pool shares; public withdrawal, swap and deposit fees are underlying base units. A caller must not compare them without the appropriate conversion.

The reference service's default quote lifetime is 1,200 seconds, derived using its wall-clock/chain-time policy. Quotes are operator promises and may expire; the contract checks the execution terms actually bound into a proof or signature, not an opaque quote identifier.

POST /relay/submit accepts the quote identifier, operation execution data, an operation-appropriate proof or deposit authorization, and a fee envelope when needed. Its current transport limit is 786,432 bytes. Transport permits a bounded proof blob, while the active SEAL adapter still requires exactly 768 proof bytes. The service validates the 71 canonical batch fields, rejects an expiration that is out of 64-bit range or already reached, and checks the 64-bit verifier generation, quoted executor/deadline/fee terms, execution context, required ciphertext availability and operation specific constraints before simulation and signing. For a shielded fee, it decrypts and validates its own fee note against the quoted value and owner.

Service-side validation is additional defense and resource control. A direct transaction remains governed by the contracts and selected verifier, including the distinctions in Section 16.3.

21.5 Durable jobs and retries

The intent service persists signed raw transaction bytes and their fee budget before broadcasting. A serialized worker manages its signing-account nonce. Reuse of a quote with different submission data is rejected; an identical retry can recover the existing job.

GET /relay/status/{id} reads job status. POST /relay/retry/{id} rebroadcasts stored signed bytes rather than constructing a second independent spend. A lost HTTP response or an uncertain broadcast result must lead to status/receipt reconciliation before a client attempts a replacement operation.

Quoted, signed and broadcast service stages are distinct from chain inclusion and finality. Included transactions can succeed or revert. Expired unsigned records are subject to configured cleanup; signed records are retained for recovery and nonce reconciliation. An intent service's fee estimate and profit margin are service pricing choices. SEAL requires neither a protocol token nor an intent service stake.

22. Synchronization, finality and submission

22.1 Canonical note state

A wallet maintains openings and derived ownership locally, but derives funding, tree membership and spent state from the host chain. It consumes ordered pool logs, verifies contiguous leaf indices within each epoch, recomputes Poseidon roots and checks them against emitted roots. A mismatch must fail closed rather than produce a witness from inconsistent state.

Block-number/hash anchors detect reorganization. A changed canonical anchor requires rolling back affected leaves, roots, receipts, funding decisions and nullifier observations and replaying the new canonical range. Confirmed state in a backup is a synchronization hint, not immunity from later reorganization. Multiple devices using the same keys can race to spend the same note; at most one transaction can consume its nullifier in canonical pool state.

22.2 RPC qualification

The reference client qualifies endpoints against the selected chain, expected history/checkpoints and head-freshness policy. Qualification results are cached for at most 30 seconds and no longer than the remaining head-freshness allowance. Raw-transaction broadcast requires a fresh qualification. A stale or incorrect endpoint is rejected and other configured endpoints may be tried.

A contract revert is not evidence of an unhealthy endpoint and does not justify selecting another endpoint solely to obtain a successful response. Raw-transaction retries use the same signed bytes. These checks reduce accidental chain confusion and stale-node use; they are not an independently verifying light client or proof that all configured RPC providers report an honest chain.

22.3 Finality labels

The client distinguishes submission, inclusion, safety and finality. Where supported, it queries the host chain's safe and finalized heads and verifies the referenced hashes against the corresponding block numbers. The finalized head cannot be later than the safe head. Missing or inconsistent tags do not justify inventing an equivalent fixed confirmation count.

A status label applies to an observed canonical transaction and its success result. Archive availability, intent service acknowledgment and a displayed balance cache have no independent finality meaning. Chain-specific finality and sequencer assumptions remain those of the selected host network.

22.4 Proof preparation and submission choice

Before generating a proof, the reference UI requires the user to choose self-submission or a supported intent service. This is a protocol-relevant choice because it can change executor, deadline, fee note, public fee and action fields bound into the proof.

A compatible flow is:

  1. Reconcile spendable notes and select inputs, outputs and execution mode.
  2. Obtain current verifier generation, fee policy, registered-asset data and, if applicable, a relay quote.
  3. Construct delivery/recovery records and persist the required local and archive state.
  4. Construct the statement and private witness; generate and locally verify the proof using the selected artifacts.
  5. Check that execution terms remain current, then submit the exact authorized call through the chosen channel.
  6. Track transaction success, canonical inclusion and finality; reconcile note and outbox state.

Changing a bound term requires a new proof. Switching submission mode after proof generation is not a UI-only toggle. Once a transaction may have been broadcast, the client must reconcile its hash and relay job before treating it as an abandoned draft.

A zero protocol rate does not eliminate network gas, DEX fees, service fees or slippage.

23. Security and privacy properties

23.1 Required assumptions

The intended ownership and conservation properties assume soundness and zero knowledge of the selected proof system, binding of the commitments, secure key generation, correct circuit/compiler/verifier correspondence, an appropriate setup, and correct host-chain execution. Delivery confidentiality additionally assumes X25519, HKDF and AEAD security and confidentiality of the viewing and recovery keys.

The application must also preserve spending secrets, randomness, witness data and wallet-unlock signatures. Malicious browser code or a compromised unlocked device can bypass the protections of encrypted storage. An encrypted backup does not compensate for an attacker controlling its decryption environment.

Backing is subject to the admitted token and vault behavior, reserve liquidity and host chain availability. Governance is trusted within its explicit powers, particularly asset admission, swap routes, fee policy and fee spending. It has no verifier replacement authority. Archive and intent service availability are operational dependencies; their censorship powers differ from spending authority.

23.2 What is public

Observation Public or visible information
Deposit Asset, gross/net transfers, depositor or adapter, amount, credited shares, commitment, recovery hash and time
Private transfer or settlement Input count/kinds, nullifiers, roots/depths, output commitments, operation, context-bound execution data, submitting address, recovery hash and timing
Pending payment Commitment and both delivery hashes; public service headers/ciphertexts where published
Ordinary withdrawal Asset slots, pool shares burned, assets paid, destination, minima, public fee and submitting address
Swap Route, pools, asset identities, input/output transfers, limits, fees and action data
Archive access Requested ranges/hashes, request timing, network origin and any journal locator used
Intent service use Public submitted operation, quote terms, service fee, requester metadata available to that operator and its own fee-note opening
Local compromise Potentially all unlocked keys, witnesses, openings, private history and recovery material

A hidden asset or amount inside an ordinary internal note does not imply that the same asset or amount is hidden at the public entry or exit. Event omissions also do not hide values present in calldata, token logs or transaction traces. Internal transaction shape, selected root and reclaim timing can distinguish subsets of activity.

23.3 Ownership, double spending and reclaim

Under the assumptions above, a successful proof demonstrates knowledge of a key whose hash matches the selected ownership branch. A nullifier identifies a consumed note without publishing its opening. The spent-nullifier set prevents a second canonical spend. Recipient and refund branches of the same pending note use the same nullifier, so only one can succeed.

A pending recipient can claim before or after the refund delay, provided the note remains unspent. The delay grants the sender an additional eligible spending branch; it does not automatically transfer funds or revoke the recipient. Once both branches are eligible, transaction ordering determines which valid spend succeeds. A sender cannot revoke a completed claim.

Output commitments and delivery hashes are binding, but the circuit does not prove that a sender actually encrypted a usable opening to the intended viewing key. A malformed or unavailable envelope can prevent discovery even though its hash is committed. Clients must construct and validate delivery correctly and retain recovery material.

23.4 Anonymity with N users and repeated actions

There is no protocol-wide formula equating N registered users, N actions or N^2 total actions with an anonymity guarantee. Users are not registered identities onchain, one entity can create many addresses, and an adversary may control a substantial part of the observed activity.

For a specified inference task and observer, let C be the candidate set of honest users consistent with all observations. Let p_i be the observer's posterior probability that candidate i is responsible. Define:

best single-guess success = max_i p_i
H = -sum_i p_i * log2(p_i)
effective anonymity = 2^H

If all N users are plausible and equally likely, then p_i=1/N, guessing succeeds with probability 1/N, and H=log2(N) bits. This is an idealized conditional statement, not an assurance provided by the pool. For a user-identification task, 2^H <= |C| <= N. If the inference task instead concerns a particular input note, its candidate set and upper bound are counts of eligible notes, not users.

If each of N users performs N actions, there are N^2 action events, but there are still only N candidate people before side information. More indistinguishable honest activity may enlarge note-level candidate sets. Repeated activity may also provide more correlated observations and reduce uncertainty.

Examples of conditioning information include a distinctive deposit/withdrawal amount, a narrow timing window, a rarely used asset, a reused public destination, a selected root that predates some deposits, sender-known pending payments, service IP logs, and intersection of multiple sessions. Splitting activity into equal amounts does not eliminate timing or ownership information. Sybil-generated cover volume does not enlarge the honest candidate set by the number of Sybil accounts.

Any quantitative privacy claim must state the observer, inference target, honest-user population, traffic distribution, adversarial participation, auxiliary data and measured posterior or attack success. SEAL does not compute or certify such a metric from its leaf count.

23.5 Service correlation and recovery privacy

Downloading public candidates before local filtering prevents a simple archive query from directly revealing which tag matched. It does not conceal the client's IP address, scan interval, request size, online times or subsequent intent service submission. Colluding RPC, archive and relay providers can combine those observations with public transactions.

The default absence of a stable journal locator removes one persistent service identifier. It does not make a wallet's network behavior unlinkable. Optional journal use, selective receipt retrieval, a cloud-backup account and authenticated service sessions can introduce additional grouping. Independent services and appropriate network-privacy arrangements may reduce some correlations, but those are deployment and user choices outside the cryptographic validity relation.

23.6 Cryptographic horizon and exclusions

BN254 pairings, X25519 and ECDSA rely on elliptic curve assumptions that do not provide security against a sufficiently capable quantum computer. SEAL makes no post quantum confidentiality or soundness claim. In particular, an observer can retain encrypted delivery records now and attempt to decrypt them after a future break of X25519. Changing future verification does not protect those recorded ciphertexts. See NIST's explanation.

The security boundary includes more than SEAL. Ethereum currently relies on BLS validator signatures and classical account signatures, and Bitcoin uses elliptic curve signatures, including BIP 340 Schnorr signatures. A local proof upgrade would not by itself make the supporting chain secure against quantum attacks. The Ethereum Foundation's September 2026 roadmap targets December 2029 for full quantum resistance and discusses reduced contingency guarantees. This is a research and engineering target, not a contractual deadline or a guarantee. SEAL has no calendar behavior tied to it. Ethereum Foundation priorities, Ethereum PQ research.

Research directions for a successor include FRI based STARKs, Spartan with WHIR polynomial commitments and possible lattice based constructions. Transparent, hash based commitments avoid the particular elliptic curve assumption but still need suitable quantum security parameters, transcript analysis and zero knowledge proofs. Proof size, prover resources, EVM verification cost and client delivery require measurements; large proofs do not establish impossibility. PSE describes WHIR verification on the EVM as feasible but expensive and is researching combinations including Spartan. No such system is selected, implemented or accepted for SEAL. PSE client proving research.

The Merkle construction does not make a broken hash secure. Binding depends on the relevant collision and second preimage properties of the exact hash and encoding. A collision attack does not automatically give every chosen preimage attack, but there is no general guarantee that an insecure hash remains safe inside a tree. MD5 is unsuitable where collision resistance is required. RFC 9162, Section 2.1, RFC 6151, Section 2.

The Ethereum Foundation Poseidon Initiative records suspension of its Poseidon1 collision prize in August 2026. That program concerns its published parameter choices; its status neither proves a break of SEAL's BN254 circomlib instantiation nor establishes its safety. The broader claim that Poseidon has been abandoned for every Ethereum Foundation use is not established by that primary source. SEAL uses Poseidon for commitments, nullifiers and key hashes as well as tree nodes, so review must cover every use. Poseidon Initiative.

The protocol does not guarantee protection against misuse of remaining governance powers, token issuer controls, reserve insolvency, chain reorganizations beyond the adopted finality assumptions, client malware, loss of recovery data or a global traffic analysis adversary. Partial cryptographic improvements can still be valuable, but must be described without claiming complete post quantum security.

23.7 Key-compromise capabilities

The consequences of disclosure depend on both the secret and the data available to the observer:

Disclosed material Direct capability
seal. address Identify the reused public payment identity and encrypt to its viewing key; no decryption or spending authority
Viewing private key Decrypt retained addressed payment/fee envelopes, reconstruct their openings and derive their nullifiers; cannot independently satisfy spending-key ownership
An ordinary or pending opening Derive its commitment and nullifier and monitor consumption; spending still requires the selected branch's spending key
Spending secret plus a usable unspent opening and path Generate an authorized spend under the corresponding ownership branch
Recovery key plus operation records Decrypt those records and the openings/history they contain
Recovery key plus a portable account backup Decrypt the backed-up key material, including the spending secret; this can confer spending authority
Indexing key Derive optional journal locators for known scopes; does not independently decrypt pointer or operation records
Canonical unlock signature Derive spending, viewing, recovery and indexing keys under key format 1

There is no circuit-enforced key revocation, note expiration or viewing-key rotation in SEAL. Moving value into notes controlled by fresh keys changes future spending authority; it does not erase old ciphertexts, undo disclosure, or revoke a still-unspent pending payment's existing branches. Reusing the same EOA-derived account profile reproduces the same keys when its canonical unlock signature is reproduced.

24. Deployment identity and release

24.1 Deployment identity

A compatible deployment description must identify the host chain, pool, registry, raw verifiers and adapters, vault factory and Aave dependency, fee controller, enabled entry adapters, governance addresses and registered assets. It must bind these addresses to expected runtime code and artifact digests. An address alone does not identify the intended protocol revision on a different chain.

The contracts use features requiring the repository's supported EVM target, including transient storage. A deployment must provide the required BN254 and modular-exponentiation precompiles and compatible ERC-20, ERC-4626, Aave and V3 interfaces for the selected features. Chain-specific configuration and accepted artifact pins are release inputs, not constants inferred from this document.

24.2 Governance profile

The supported administration profile uses one guardian Safe for administration and fee receipts. Existing canonical Safe 1.4.1 and 1.5.0 accounts with at least two signatures are supported; the fresh-Safe helper starts at two-of-three. Deployment scripts can create fresh accounts and prepare owner addition, removal, replacement and threshold changes. The onchain pool binds the account addresses, not a permanent list of human signers.

A Safe's owner/threshold change is authorized by that Safe's current rules. The pool does not impose a notice period on those internal signer changes. The guardian address cannot rotate. Key custody, signer independence and recovery procedures remain operational responsibilities. Fresh deployment scripts do not mean that production accounts have already been created.

24.3 Wire identifiers

The statement schema is 3: 71 public monetary words or 11 account-only words. The verifier generation is always zero. Deposit and private-margin EIP-712 domains use version 2; public margin uses 1. These are exact signing constants. Fee-policy counters identify applied rate changes. Account encryption, key derivation, delivery and compact proving keys carry their own strict format identifiers as specified above. A decoder rejects an unsupported identifier.

24.4 Release status

The current proof artifacts are development artifacts. Production pins are zero and independent qualification and owner acceptance are outstanding. Sepolia requires a fresh deployment with all four verifiers, the matching pool/adapters and the matching browser and relayer configuration. Deployment-specific authority, runtime identity and network preflight checks remain mandatory. See security review requirements.

25. Events and failure semantics

25.1 State-reconstruction events

The pool emits events for asset registration and mode, stable classification, deposit, leaf insertion, epoch rollover, payment creation, batch execution, public withdrawal, fee accrual and claims. The following have particular synchronization roles:

Event Interpretation
LeafInserted(epoch,index,commitment,root) Append position and resulting epoch root
EpochRolled(epoch,root,nextEpoch) Closed epoch root and selected successor
Deposited Public funding terms and resulting ordinary note
PaymentCreated(commitment,recipientPayloadHash,refundPayloadHash) Pending commitment and delivery references
BatchExecuted Operation, published batch outputs/nullifiers and recovery reference
Withdrawn Public asset exit and paid amounts
RecoveryActivated Irreversible change in an asset's exit mode
VaultSharesRecovered Pool-share burn and direct vault-share delivery

The source ABI specifies exact indexed fields, names and types. Logs must be interpreted together with transaction success, emitting address and canonical block identity. A similarly named log from a different contract is not a pool event. A transaction reverted by the EVM has no committed pool events or state changes.

Fee proposals and adapter-pool admission emit their respective controller events. These events support notice and monitoring; observers must still query current state when preparing execution.

DepositFeeAccrued(asset, amount) records a deposit fee, and DepositFeesClaimed(asset, amount) records its guardian payout. The fee controller additionally emits AssetYieldScheduled, AssetYieldCancelled and AssetYieldChanged for individual rates. Full indexed fields and argument names are specified in the ABI reference.

25.2 Core event ABI

The following declarations give the exact pool event field order. Contract/interface address types have been written as address, which has the same ABI representation. No event is anonymous.

event StablecoinRegistered(address indexed asset, bool stablecoin);
event AssetRegistered(address indexed asset, address vault);
event AssetModeSet(address indexed asset, uint8 mode);
event FeeSet(uint256 feeBPS);
event FeeAccrued(address indexed asset, uint256 shares);
event FeesClaimed(address indexed asset, uint256 shares, uint256 amount);
event PrivacyFeeAccrued(address indexed asset, uint256 amount);
event PrivacyFeesClaimed(address indexed asset, uint256 amount);
event RecoveryActivated(address indexed asset);
event VaultSharesRecovered(address indexed asset, address indexed recipient,
                          uint256 poolShares, uint256 vaultShares);
event LeafInserted(uint64 indexed epoch, uint256 index,
                   uint256 commitment, uint256 root);
event EpochRolled(uint64 indexed epoch, uint256 root, uint64 nextEpoch);
event Deposited(address indexed depositor, address indexed asset,
                uint256 assets, uint256 shares, uint256 salt,
                uint256 commitment, bytes32 recoveryHash);
event Withdrawn(address indexed asset, address indexed recipient,
                uint256 shares, uint256 assets, uint256 fee);
event PaymentCreated(uint256 indexed commitment,
                     uint256 recipientPayloadHash, uint256 refundPayloadHash);
event BatchExecuted(uint8 indexed operation, uint256[8] nullifiers,
                    uint256[8] outputCommitments, uint256 feeCommitment,
                    uint256 pendingCommitment, uint256[8] inputKinds,
                    bytes32 recoveryHash);

Withdrawn.assets is the gross redeemed underlying amount and Withdrawn.fee is the combined public service and protocol withdrawal fee. The recipient's net payout is their difference. Deposited.depositor is the pool caller, which can be an adapter. BatchExecuted.operation uses the circuit operation numbers: action withdrawals emit operation 2, and their distinct paths must be identified from transaction execution.

25.3 Failure classes

Invalid authority, expired execution, unsupported asset/mode, invalid field or shape, unknown or mismatched root, immature refund, spent or repeated nullifier, reused commitment, wrong context, wrong verifier generation, proof failure, insufficient backing, unmet minima and incompatible transfer behavior cause rejection at their enforcing layer.

An archive timeout, proof-generation failure, RPC qualification error or failed local simulation does not itself consume a note. A broadcast transaction may still be pending or included despite a client timeout; the client must reconcile before retrying. A reverted onchain transaction consumes gas from its payer but leaves its attempted pool transition unapplied.

Minimum-output fields constrain successful execution. They are not price oracles, promises of available liquidity, or guarantees of successful inclusion before a deadline. Public transaction ordering may affect swaps, reclaim races and which independently valid spend reaches the nullifier set first.

25.4 Contract interface reference

The complete ABI reference docs/public/spec/seal-interfaces.json provides constructor arguments, every public/external function and getter, return types, state mutability, events with indexed fields, and declared custom errors for the pool, fee controller, vault factory, both vault types, the deposit and swap adapters, fee controller construction library, registry, verifier adapter and four development raw verifiers. Function/error selectors and event signature topics are included separately for inspection.

For a canonical function signature s, calldata is the first four bytes of K(UTF8(s)) followed by the standard ABI encoding of its arguments. Structs appear as parenthesized component-type tuples; names and data-location keywords are omitted from s. Custom errors use the analogous four-byte selector and argument encoding. Nonanonymous events place the full signature hash in topic zero; the remaining topic/data layout follows the indexed flags in the ABI. Solidity panic and error-string encodings and revert data from external dependencies may also occur; the declared custom-error list does not imply that all failures originate in the pool.

The proof-bearing pool entry points use the exact tuple expansion from Section 8:

settle(Settlement, Proof)
transfer(Transfer, Proof)
withdraw(Withdrawal, Proof)
withdrawForAction(Withdrawal, bytes32 actionHash, Proof)

Deposit returns (commitment,shares,salt) in that order. All these entry points are nonpayable. Native transaction value is not a deposit mechanism; ordinary fees described here are denominated in registered ERC-20 assets or private pool shares.

The reference specifies canonical client encodings. It does not add a generic requirement that the Solidity dispatcher reject every otherwise decodable noncanonical calldata arrangement or trailing byte. The proof adapter's exact inner lengths remain mandatory. A transaction identifier is the host chain's signed-transaction hash, not the proof hash, commitment or recovery hash.

The verification interfaces are:

VerifierRegistry.verify(uint8 operation, uint64 version,
                        bytes proof, bytes statement) -> bool
PlonkVerifierAdapter.verify(bytes proof, bytes statement) -> bool

The registry catches a selected verifier's revert and returns false. The pool converts a false registry result into InvalidProof. Read-only verification by a third party neither changes state nor reserves the proof's inputs.

26. Source correspondence and reference vectors

26.1 Source map

The implementation reference at the start of this document fixes the meaning of the source links below. Branch links are convenient for navigation; reproducibility requires checking out that revision or the explicitly accepted successor.

Subject Reference source
Pool transitions and accounting PrivacyPool.sol seal-pool/packages/contracts/src/contracts/PrivacyPool.sol
Public-input offsets ProofSignals.sol seal-pool/packages/contracts/src/contracts/lib/ProofSignals.sol
Merkle insertion Pool insertion routines above; @zk-kit/lean-imt.sol 2.0.0 pinned in package-lock.json package-lock.json
Circuit relation batchTransition.circom seal-pool/packages/circuits/circuits/batchTransition.circom, multiAssetNote.circom seal-pool/packages/circuits/circuits/multiAssetNote.circom, boundedMerkleTree.circom seal-pool/packages/circuits/circuits/boundedMerkleTree.circom
Note and context derivation multiAsset.ts seal-pool/packages/sdk/src/multiAsset.ts, contexts.ts seal-pool/packages/sdk/src/contexts.ts, batch.ts seal-pool/packages/sdk/src/batch.ts
Account keys keys.ts seal-pool/packages/sdk/src/keys.ts
Delivery encryption multiAssetPayload.ts seal-pool/packages/sdk/src/multiAssetPayload.ts
Recovery encryption and archive client recovery.ts seal-pool/packages/sdk/src/recovery.ts
Forest reconstruction epochForest.ts seal-pool/packages/sdk/src/epochForest.ts
Fee policy ProtocolFees.sol seal-pool/packages/contracts/src/contracts/ProtocolFees.sol
Backing vaults IdleVault.sol seal-pool/packages/contracts/src/contracts/vaults/IdleVault.sol, AaveV3Vault.sol seal-pool/packages/contracts/src/contracts/vaults/AaveV3Vault.sol
Deposit authorization SealDepositAdapter.sol seal-pool/packages/contracts/src/contracts/SealDepositAdapter.sol, deposit.ts seal-pool/packages/sdk/src/deposit.ts
Swap action SealSwapAdapter.sol seal-pool/packages/contracts/src/contracts/SealSwapAdapter.sol, swap.ts seal-pool/packages/sdk/src/swap.ts
Verifier routing VerifierRegistry.sol seal-pool/packages/contracts/src/contracts/verifiers/VerifierRegistry.sol, PlonkVerifierAdapter.sol seal-pool/packages/contracts/src/contracts/verifiers/PlonkVerifierAdapter.sol
Raw verifier algorithm plonk-v1.sol.ejs seal-pool/packages/circuits/scripts/verifier-templates/plonk-v1.sol.ejs
Verifier provenance plonk-provenance.json seal-pool/packages/circuits/scripts/verifier-templates/plonk-provenance.json
Compact key parser and derivation plonk-key.ts seal-pool/packages/circuits/scripts/plonk-key.ts, build-plonk.ts seal-pool/packages/circuits/scripts/build-plonk.ts
Archive and intent service delivery.rs intent-service-rs/src/storage/delivery.rs, direct.rs intent-service-rs/src/runtime/direct.rs
RPC qualification rpc.ts seal-pool/packages/sdk/src/rpc.ts

The reference client’s address and receipt readers are in files.ts apps/web/src/files.ts; portable backup serialization and merge behavior are in storage.ts apps/web/src/storage.ts, with application record types in types.ts apps/web/src/types.ts. Operation-record reconciliation and public candidate fetching are in application recovery apps/web/src/recovery.ts and public archive scanning apps/web/src/public-archive.ts. These sources fix application-profile details without making display caches or history entries authoritative pool state.

26.2 Deterministic format vectors

The current format vectors seal-pool/packages/sdk/test/fixtures/seal-vectors.json provide reproducible values for scope, wallet-derived keys, payment-address encoding, ordinary and pending commitments, shared claim/refund nullifiers, encrypted delivery, recovery records and execution contexts. The vector generator scripts/spec/spec-vectors.ts constructs them from fixed synthetic inputs, checks round-trip decoding, and supports comparison with the checked-in JSON.

The vectors also include a three-leaf forest, compressed membership paths and a complete 71-word withdrawal statement with its synthetic witness. The statement is an encoding/relation example, not a submitted transaction or a proof. To verify the current file, run from the repository root:

npm run build -w @seal-pool/sdk
node --experimental-strip-types scripts/spec/spec-vectors.ts

Regeneration after a reviewed format change uses the additional --write argument. The generator verifies rejection of a wrong viewing key, tampered envelope, wrong recovery key, wrong recovery scope, wrong payment-address chain and a checksum-free payment address, as well as successful decoding. The repository test suite runs the comparison, so a format change that alters the checked-in vector bytes fails until the vectors are reviewed and regenerated.

Every secret and random source in those vectors is public test data. They MUST NOT be used for live keys, notes, encryption or deposits. Deterministic encryption exists only to make the example bytes reproducible. The vectors are interoperability aids, not a proof of circuit soundness or an independently verified cryptographic implementation.

Proof acceptance and rejection vectors, including operation specific keys, are separately maintained in verifier-vectors.json seal-pool/packages/contracts/test/fixtures/verifier-vectors.json and verifier-common-vectors.json seal-pool/packages/contracts/test/fixtures/verifier-common-vectors.json. These belong to the pinned development artifacts; they must not be relabeled as production vectors without the corresponding release acceptance.

26.3 Concrete parameter and interface files

Two additional machine readable files accompany this document. The Poseidon parameter file contains the complete note/checkpoint arity set. The interface file describes the current contracts and all four development verification keys. Accepted production identities must be established by the release manifest.

The SHA-256 digests of the exact UTF-8 JSON files, including their terminal newline, are:

seal-poseidon.json
86fb86bb0c47503f03b329d93e57b97c60ef9df2cbd8d25e47f54ef1ddbff4d9

seal-interfaces.json
aced0a9cd605c58d97f3e03944b6a88ce0b243883324bc04715636b44ec7af01

Run the reference generator/checker scripts/spec/spec-reference.ts after compiling the referenced implementation:

npm run build -w @seal-pool/sdk
npm run build:contracts
node --experimental-strip-types scripts/spec/spec-reference.ts

The checker verifies contract source bytes against the stated implementation revision and rejects a stale compiled artifact when its source hash differs. It compares interface selectors to the compiler's method identifiers. Its straightforward field-arithmetic implementation of Section 2.2 is compared with the SDK's optimized hash path and also reconstructs the published ordinary and pending commitment/nullifier vectors.

It separately implements the transcript and equations in Sections 16.4–16.5, using only the arithmetic library's group operations and pairing rather than invoking the snarkjs or generated Solidity verifier. That implementation checks all 25 recorded proofs, covering one through eight inputs in each monetary operation and the account-only circuit, and twelve rejection cases changing context, evaluation or coordinate range. This tests the documented equations against accepted artifact vectors; it is not a proof of the circuit's completeness, proof-system soundness or implementation correctness for all inputs.

The optional --write mode regenerates the reference files after a deliberate review; the document digests must then be updated if the bytes change.

These files fix concrete interoperability choices. They are not a release-signing mechanism, release approval record or replacement for deployment-specific runtime-code verification.

26.4 Specification coverage

The scope is the complete SEAL pool construction at the identified implementation revision. Its requirements are divided as follows:

Requirement Specification location Concrete correspondence
Protocol state, units, constants and authorities Sections 1–3, 9–12, 17 Pool/controller source and complete ABI reference
Keys, addresses, note commitments and spent identifiers Sections 4–5 Key/format vectors and concrete Poseidon parameters
Membership, branch authorization and conservation Sections 6–7 Circuit sources, full witness schema and 71-word monetary and 11-word account statements
Transaction binding and every value-moving entry path Sections 8–9, 13–15 Tuple definitions, context vectors, adapter source and interfaces
Exact backing, fee and rounding rules Sections 10–12 Accounting equations, quote semantics and vault/controller source
Fixed proof verification and successor boundary Sections 16–17 Transcript/equations, proof vectors, verification keys and registry source
Delivery, recovery, backups and services Sections 18–22 Byte/JSON layouts, round-trip/rejection vectors and application/service source
Security, leakage, anonymity and key disclosure Section 23 Explicit adversary observations, conditional anonymity model and capability table
Deployment identity, compatibility and failure behavior Sections 24–25 Wire identifiers, exact ABI/events/errors and release requirements

This coverage does not imply that protocol correctness or production safety follows from documentation or passing tests. It identifies which construction is specified and which artifacts allow its details to be checked without silently substituting a historical deployment or a proposed future design.

27. References

The following documents identify standard primitives and interfaces. The SEAL-specific choices and constraints in this document take precedence over optional behavior in a general interface standard.

  1. Zcash Protocol Specification, used as a structural reference for separating abstract relations, concrete encodings and validity rules. SEAL does not implement the Zcash transaction or consensus protocol.
  2. RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function.
  3. RFC 7748: Elliptic Curves for Security, including X25519.
  4. RFC 8439: ChaCha20 and Poly1305 for IETF Protocols.
  5. EIP-712: Typed structured data hashing and signing.
  6. EIP-4626: Tokenized Vaults.
  7. EIP-1167: Minimal Proxy Contract.
  8. Solidity ABI specification.
  9. SEAL cryptography and artifact workflow, security model, end-to-end explanation and multisig administration.
  10. EIP-196: BN254 addition and scalar multiplication, EIP-197: BN254 pairing check, and EIP-198: Modular exponentiation. Later host-chain upgrades may change their gas pricing; this specification uses their arithmetic interfaces, not the original gas schedules.