import { Hex } from 'viem'; /** * DVT combined-signature wire encoders — airaccount-contract #110 authoritative, * LIVE-verified format (deployed `AAStarBLSAlgorithm` beta.2 `0xA9EE4f8A…` on Sepolia). * * ## Why this supersedes the `signerMask` design * * An earlier SDK design (D1, {@link import('./blsSigner').BLSHelpers.encodeDVTProof}) * encoded the contributing nodes as a `uint256 signerMask` bitmask. The DEPLOYED * verifier does NOT use a bitmask: it takes an EXPLICIT list of `bytes32` `nodeId`s * and rebuilds the aggregate public key by walking them in order. This module * implements the explicit-`nodeIds` wire that the live contract actually decodes; * the `signerMask` helpers are retained only for backward compatibility and are * marked `@deprecated`. * * ## Wire layouts (byte-for-byte, confirmed against live Sepolia txs) * * Account-level (goes into `PackedUserOperation.signature`): * ``` * T2 (0x04): [0x04][P256 r(32)][P256 s(32)][nodeIdsLength(32)][nodeId_1(32)…nodeId_N(32)][blsSig(256)] * T3 (0x05): [0x05][P256(64)][nodeIdsLength(32)][nodeIds(N×32)][blsSig(256)][guardianECDSA(65)] * ``` * Verifier-level (the account strips `tier + P256 + nodeIdsLength`, then calls * `AAStarBLSAlgorithm.validate(userOpHash, …)`): * ``` * validate(userOpHash, [nodeId_1(32)…nodeId_N(32)][blsSig(256)]) // NO nodeIdsLength prefix * ``` * * Key invariants (all confirmed live): * - `nodeIds` are explicit `bytes32` IDs (NOT a `signerMask`). The encoders emit them STRICTLY * ASCENDING (#274): the v0.27.0 DVT validator requires ordered, distinct ids. Callers may pass any * order — BLS aggregation is commutative, so the (order-independent) aggregate `blsSig` still matches * the sorted ids. Duplicate ids throw (a valid M-of-N aggregate has distinct signers). * - `blsSig` is a 256-byte uncompressed G2 point in EIP-2537 layout (see {@link encodeG2Point}). * - `messagePoint` is NOT attached (issue #45): the verifier recomputes * `hashToG2(userOpHash)` on-chain. Do NOT pass `messagePoint`/`mpSig`. * * @module */ /** DVT account-signature tier byte: P256 primary + ≥threshold BLS aggregate (DVT co-sign). */ declare const DVT_TIER_T2: 4; /** DVT account-signature tier byte: T2 + a trailing 65-byte guardian ECDSA signature. */ declare const DVT_TIER_T3: 5; /** A DVT account-signature tier byte. */ type DVTTier = typeof DVT_TIER_T2 | typeof DVT_TIER_T3; /** * Account-signature algId byte for the `ALG_BLS` "legacy triple": a DVT BLS aggregate co-sign * PLUS a trailing owner-ECDSA factor (airaccount-contract `_validateTripleSignature`). Note this * is NOT a BLS-only path — the contract requires the owner signature too. */ declare const ALG_BLS: 1; /** * Produce the canonical 256-byte EIP-2537 G2 layout for a BLS aggregate signature, * byte-identical to the DVT node's `encodeG2Point` (YetAnotherAA-Validator * `src/utils/bls.util.ts`) and to what the contract pairs over. * * Layout: `x.c0 @ 16 / x.c1 @ 80 / y.c0 @ 144 / y.c1 @ 208`, each Fp a 48-byte * big-endian value right-aligned in its 64-byte slot (16 leading zero bytes). * * Accepts the signature in three forms: * - **256-byte EIP-2537** (what the node already emits): validated and passed through. * - **96-byte compressed** / **192-byte uncompressed** zkcrypto G2: re-packed via * `@noble/curves` (parse → affine → EIP-2537 slots). * * @param blsSig The aggregate BLS G2 signature (hex or bytes). * @returns The 256-byte EIP-2537 G2 point as hex. */ declare function encodeG2Point(blsSig: Hex | Uint8Array): Hex; /** * Sort nodeIds STRICTLY ASCENDING (by 32-byte big-endian value) for the BLS aggregation wire (#274). * * The DVT-unification validator (algId 0x01, airaccount-contract v0.27.0 / YetAnotherAA-Validator #170) * rejects unordered or duplicate nodeIds: a single node could otherwise submit `[nid, nid, …]` + k·sig to * fake an M-of-N quorum. Strictly-increasing ⇒ dedup, matching SP BLSAggregator's ordered signerMask. * * BLS aggregation is commutative (Σ sig / Σ pubkey are order-independent), so reordering the ids does NOT * change the aggregate signature — this only fixes the WIRE order. A duplicate nodeId is a malformed * aggregate → throw (rather than silently dropping an id whose signature is already summed in). */ declare function sortNodeIdsAscending(nodeIds: readonly Hex[]): Hex[]; /** * One committee signer's membership proof, accompanying its share of the aggregate BLS signature. * * `slot` and `merkleProof` authenticate `nodeId` against the FROZEN committee set root * `setRoot[e-1]` held by `AAStarCommitteeValidator`. They are NOT signed over — the BLS message is * still `bytes(userOpHash)` exactly as in legacy (authoritative: CC-103, dvt from * `AAStarValidator.sol` + `bls.util.ts`; DST `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`). */ interface CommitteeSigner { /** `bytes32` node id (`keccak256(EIP-2537 G1 pubkey)`). */ nodeId: Hex; /** Leaf index of `nodeId` in the frozen committee set. */ slot: bigint | number; /** `TREE_DEPTH` sibling hashes, leaf→root order. Length must equal the validator's TREE_DEPTH. */ merkleProof: readonly Hex[]; } /** * The validator's Merkle tree depth. `AAStarCommitteeValidator.TREE_DEPTH` is a `public constant` * = 14, giving `perSigner = 64 + 14*32 = 512`. Exported only as the DEFAULT for offline/test use: * production callers should read `TREE_DEPTH()` from the mounted validator and pass it through, so * a depth change upstream does not silently produce mis-shaped payloads here (CC-103 Q4 — raised by * KMS, endorsed by airaccount-contract, accepted by dvt). */ declare const COMMITTEE_TREE_DEPTH_DEFAULT: 14; /** Byte length of one committee signer entry: `nodeId(32) ‖ slot(32) ‖ proof(depth×32)`. */ declare function committeePerSignerLength(treeDepth?: number): number; /** * Sentinel returned by `AAStarCommitteeValidator.requiredQuorum()` when committee validation cannot * be satisfied at all — `epochLength == 0` (committee off) or the `e-1` snapshot is missing/stale. * * NOT documented in the CC-103 wire spec, which describes `requiredQuorum()` only as `⌈2·m_e/3⌉`; * found by reading it back on-chain (it returned `type(uint256).max` on the live validator) and * confirmed in `AAStarCommitteeValidator.sol:385`. A caller that compares `k >= requiredQuorum()` * without special-casing this can never pass, so {@link assertCommitteeQuorum} rejects it with a * message naming the real cause instead of an impossible-quorum error. */ declare const COMMITTEE_QUORUM_UNAVAILABLE: bigint; /** * Guard the collected signer count against the validator's live quorum. * * @param signerCount Number of committee signers whose partials are in the aggregate. * @param requiredQuorum Value read from `AAStarCommitteeValidator.requiredQuorum()`. */ declare function assertCommitteeQuorum(signerCount: number, requiredQuorum: bigint): void; /** * Encode the committee-framed BLS block: * ``` * [nodeIdsLength(32)][ (nodeId(32) ‖ slot(32) ‖ merkleProof(depth×32)) × k ][ blsSig(256) ] * ``` * versus the legacy block `[nodeIdsLength(32)][ nodeId(32) × k ][ blsSig(256) ]`. * * `nodeIdsLength` stays the SIGNER COUNT `k` (not a byte length) and stays in the same position, so * the two framings differ only in the per-signer stride — which is why the account must decide * legacy-vs-committee from `committeeValidator.committeeActive()` rather than from the payload * shape (the shape-collision that CC-103 calls out as the flip-order attack root). * * **`accountId` is never included.** The account prepends `address(this)` itself before calling * `validate()`; a submitter-supplied `accountId` would let an attacker shop for an account whose * committee draw favours their own nodes, breaking the 2/3 assumption outright (CC-103 §二, "命门 B2"). * * Signers are emitted in STRICTLY ASCENDING `nodeId` order, carrying each signer's `slot`/`proof` * with it, for the same reason legacy sorts ids (#274). */ declare function encodeCommitteeBLSBlock(signers: readonly CommitteeSigner[], blsSig: Hex | Uint8Array, treeDepth?: number): Hex; /** * Encode the VERIFIER-LEVEL proof passed to `AAStarBLSAlgorithm.validate`: * `[nodeId_1(32)…nodeId_N(32)][blsSig(256)]` — NO `nodeIdsLength` prefix (the contract * derives `nodeCount = (sig.length - 256) / 32`). * * `nodeIds` order MUST equal the nodes' signing/aggregation order. * * @param nodeIds Explicit `bytes32` node IDs of the contributing signers, in order. * @param blsSig The aggregate BLS G2 signature (256-byte EIP-2537, or 96/192-byte zkcrypto). */ declare function encodeDVTVerifierProof(nodeIds: Hex[], blsSig: Hex | Uint8Array): Hex; /** Parameters for {@link encodeDVTAccountSignature}. */ interface DVTAccountSignatureParams { /** Tier byte: {@link DVT_TIER_T2} (0x04) or {@link DVT_TIER_T3} (0x05). */ tier: DVTTier; /** P256 primary signature, as `{ r, s }` (each 32 bytes) or a 64-byte `r‖s` hex value. */ p256: { r: Hex; s: Hex; } | Hex; /** * Explicit `bytes32` node IDs of the contributing signers. LEGACY framing * (`committeeActive() == false`). Mutually exclusive with {@link committeeSigners}. */ nodeIds?: Hex[]; /** * COMMITTEE framing (`committeeActive() == true`, CC-98/CC-103): each signer carries its `slot` * and Merkle proof against the frozen set root. Mutually exclusive with {@link nodeIds}. */ committeeSigners?: readonly CommitteeSigner[]; /** Validator `TREE_DEPTH()`. Read it on-chain; defaults to {@link COMMITTEE_TREE_DEPTH_DEFAULT}. */ treeDepth?: number; /** Aggregate BLS G2 signature (256-byte EIP-2537, or 96/192-byte zkcrypto). */ blsSig: Hex | Uint8Array; /** REQUIRED for T3 (0x05): the trailing 65-byte guardian ECDSA signature. Forbidden for T2. */ guardianSig?: Hex; } /** * Encode the ACCOUNT-LEVEL combined signature that goes into * `PackedUserOperation.signature`, per airaccount-contract #110: * ``` * T2: [0x04][P256(64)][nodeIdsLength(32)][nodeIds(N×32)][blsSig(256)] * T3: [0x05][P256(64)][nodeIdsLength(32)][nodeIds(N×32)][blsSig(256)][guardianECDSA(65)] * ``` * `nodeIdsLength` is a 32-byte big-endian `uint256` count of `nodeIds`. */ declare function encodeDVTAccountSignature(params: DVTAccountSignatureParams): Hex; /** Parameters for {@link encodeBLSAccountSignature}. */ interface BLSAccountSignatureParams { /** Explicit `bytes32` node IDs — LEGACY framing. Mutually exclusive with {@link committeeSigners}. */ nodeIds?: Hex[]; /** COMMITTEE framing (CC-98/CC-103). Mutually exclusive with {@link nodeIds}. */ committeeSigners?: readonly CommitteeSigner[]; /** Validator `TREE_DEPTH()`. Read it on-chain; defaults to {@link COMMITTEE_TREE_DEPTH_DEFAULT}. */ treeDepth?: number; /** Aggregate BLS G2 signature (256-byte EIP-2537, or 96/192-byte zkcrypto). */ blsSig: Hex | Uint8Array; /** * The trailing 65-byte OWNER ECDSA signature over `toEthSignedMessageHash(userOpHash)` * (EIP-191). The contract recovers it and requires `recovered == owner`. */ ownerSig: Hex; } /** * Encode the ACCOUNT-LEVEL `ALG_BLS` (0x01) signature that goes into * `PackedUserOperation.signature` for an `EntryPoint.handleOps` BLS UserOp, per * airaccount-contract `AAStarAirAccountBase._validateTripleSignature`: * ``` * [0x01][nodeIdsLength(32)][nodeIds(N×32)][blsSig(256)][ownerECDSA(65)] * ``` * `nodeIdsLength` is a 32-byte big-endian `uint256` count. The BLS payload `[nodeIds][blsSig]` * (no length prefix) is the same blob {@link encodeDVTVerifierProof} hands to the verifier's * `validate(userOpHash, …)`; the account additionally binds the owner ECDSA over the * eth-signed `userOpHash`. (So `ALG_BLS` is a 2-factor BLS+owner path, not BLS-only — * it is distinct from the verifier-level `validate` which checks the aggregate alone.) */ declare function encodeBLSAccountSignature(params: BLSAccountSignatureParams): Hex; export { ALG_BLS as A, type BLSAccountSignatureParams as B, type CommitteeSigner as C, DVT_TIER_T2 as D, DVT_TIER_T3 as a, type DVTTier as b, COMMITTEE_TREE_DEPTH_DEFAULT as c, committeePerSignerLength as d, encodeG2Point as e, COMMITTEE_QUORUM_UNAVAILABLE as f, assertCommitteeQuorum as g, encodeCommitteeBLSBlock as h, encodeDVTVerifierProof as i, type DVTAccountSignatureParams as j, encodeDVTAccountSignature as k, encodeBLSAccountSignature as l, sortNodeIdsAscending as s };