Context — two DVT contract systems that don't talk to each other
Today the DVT has two independent, disconnected on-chain authorization systems:
|
AAStarValidator (YetAnotherAA-Validator) — crypto layer |
DVTValidator + Registry + GTokenStaking (SuperPaymaster) — economic layer |
| Identity key |
nodeId (bytes32) + BLS G1 pubkey |
operator address (EOA) + ROLE_DVT + GToken stake |
| Stores |
registeredKeys[nodeId]→pubkey, isRegistered[nodeId] — no operator, no stake |
isValidator[address], GToken lock, role |
| Register |
registerPublicKey(nodeId, pubkey) — no stake gate (deployed AAStarBLSAlgorithm is onlyOwner) |
addValidator(address) — checks Registry.hasRole(ROLE_DVT) + locked stake ≥ minStake |
| Verify |
verifyAggregateSignature(nodeIds, sig, msgPoint) — checks isRegistered only, no stake/liveness |
_requireActiveValidator — real-time role+stake liveness on every consensus call |
| Job |
who can co-sign + verify the aggregate BLS signature |
who staked + slashing + liveness |
The problem is not overlap/override — it's a break. The signing identity
(nodeId+pubkey in AAStarValidator) and the economic identity (address+stake in
SuperPaymaster) are never bound to each other. Consequences:
- A BLS pubkey can be registered in AAStarValidator with zero stake — the signing
key has no skin in the game.
- An operator can stake in SuperPaymaster with no effect on who can actually co-sign.
- Slashing can't punish a misbehaving signing node: DVTValidator doesn't know which
nodeId belongs to which operator address.
- The production signing/verification path (dvt nodes →
registerPublicKey →
verifyAggregateSignature) is not connected to the economic layer at all.
Business goal
- Single stake source of truth — GToken staked under
ROLE_DVT in the SuperPaymaster
Registry/GTokenStaking is the only place stake is managed. Every other contract
reads it, never re-implements it.
- Bind signing ↔ stake — a
nodeId/pubkey is tied to a staked operator, so
co-signing rights require economic backing and a misbehaving signer can be slashed
through its operator.
- Permissionless self-registration — an operator can stake + register their own DVT
node without owner approval, with an owner toggle so we can bootstrap in a
permissioned (onlyOwner) mode first and flip to permissionless when ready.
- Keep the hot verification path cheap — no per-signature cross-contract reads.
Technical plan (proposed — to be challenged/finalized)
Bind by operator address; defer stake to SuperPaymaster; keep pubkey local.
YetAnotherAA-Validator — AAStarValidator
- Add
mapping(bytes32 => address) public nodeOperator; — the operator that owns a nodeId.
registerPublicKey(bytes32 nodeId, bytes pubkey) called by the operator
(msg.sender):
- record
nodeOperator[nodeId] = msg.sender.
- stake gate (when enabled): require
Registry.hasRole(ROLE_DVT, msg.sender) and
GTokenStaking.roleLocks(msg.sender, ROLE_DVT).amount ≥ Registry.getRoleConfig(ROLE_DVT).minStake.
- toggle:
bool public requireStake (owner-settable). false → bootstrap
(onlyOwner registers, as today). true → permissionless-but-staked. This is the
①stake-check + ②toggle asked for.
- Store the SuperPaymaster
Registry address (owner-settable), read-only.
- (Optional, later)
verifyAggregateSignature adds a real-time liveness read per
participating operator — off by default to keep gas/coupling low; discuss.
SuperPaymaster — Registry / GTokenStaking / DVTValidator
- Confirm/keep the read interfaces the crypto layer needs:
Registry.hasRole,
Registry.getRoleConfig(ROLE_DVT).minStake, Registry.GTOKEN_STAKING(),
GTokenStaking.roleLocks(user, roleId). (All appear to exist as of DVTValidator 0.6.0.)
- Slashing linkage: to slash a misbehaving signer, DVTValidator needs
nodeId → operator. Options: (a) AAStarValidator emits NodeBound(nodeId, operator)
and off-chain relays to a slash proposal; (b) a small view/callback. Discuss which.
ROLE_DVT = keccak256("DVT") — align the roleId constant across both repos.
Roles / responsibilities after unification
- SuperPaymaster = economic/governance single source of truth: GToken stake,
ROLE_DVT, slashing, liveness.
- AAStarValidator = cryptographic layer: BLS pubkey registry + aggregate
verification; registration reads SuperPaymaster for the stake gate and binds
nodeId → staked operator.
- Link = operator
address, stored per nodeId.
Why not fully consolidate into SuperPaymaster
verifyAggregateSignature is the hot path (called per UserOp). Keeping pubkeys local
avoids a cross-contract read per signature (gas + tight coupling). We read the economic
layer once at registration, not per verification.
Post-implementation flow
operator EOA
└─(1) SuperPaymaster: lock GToken → registerRole(ROLE_DVT) [stake: single entry]
└─(2) AAStarValidator.registerPublicKey(nodeId, pubkey) as operator
→ nodeOperator[nodeId]=operator
→ if requireStake: assert Registry.hasRole(ROLE_DVT,op) && lock≥minStake [①②③]
node co-signs UserOps → AAStarValidator.verifyAggregateSignature(nodeIds,…)
→ (optional) assert each nodeId's operator still active [liveness]
misbehavior → DVTValidator: slash operator via nodeId→operator binding [slashing]
discovery → gossip roster {nodeId, apiEndpoint}; coordinator maps nodeId→operator to
check active/slashed state before including in a quorum
Open questions (for the challenge / finalization)
- Slashing linkage — how does DVTValidator resolve
nodeId → operator? Event-relay
vs on-chain view vs storing the binding in SuperPaymaster instead.
- Real-time liveness at verify — worth the per-verify gas, or off-chain pruning
only (as DVTValidator already assumes)?
- Deployed-contract reconciliation — repo
AAStarValidator.sol (permissionless
source) vs deployed AAStarBLSAlgorithm v0.20.0 (onlyOwner): which is canonical,
and does this change target a redeploy?
- roleId + minStake governance — confirm
ROLE_DVT constant + who tunes minStake
(Registry.configureRole, owner/governance).
- One operator, many nodes? — does an operator run multiple nodeIds under one stake,
or one stake per nodeId? Affects the binding cardinality.
Cross-repo: AAStarCommunity/YetAnotherAA-Validator (AAStarValidator changes) +
AAStarCommunity/SuperPaymaster (interface confirm + slashing linkage). This issue is
filed to both. Related: YetAnotherAA-Validator#157 (N-of-M), #50 (hardening).
Context — two DVT contract systems that don't talk to each other
Today the DVT has two independent, disconnected on-chain authorization systems:
nodeId(bytes32) + BLS G1 pubkeyaddress(EOA) +ROLE_DVT+ GToken stakeregisteredKeys[nodeId]→pubkey,isRegistered[nodeId]— no operator, no stakeisValidator[address], GToken lock, roleregisterPublicKey(nodeId, pubkey)— no stake gate (deployedAAStarBLSAlgorithmisonlyOwner)addValidator(address)— checksRegistry.hasRole(ROLE_DVT)+ locked stake ≥ minStakeverifyAggregateSignature(nodeIds, sig, msgPoint)— checksisRegisteredonly, no stake/liveness_requireActiveValidator— real-time role+stake liveness on every consensus callThe problem is not overlap/override — it's a break. The signing identity
(
nodeId+pubkey in AAStarValidator) and the economic identity (address+stake inSuperPaymaster) are never bound to each other. Consequences:
key has no skin in the game.
nodeIdbelongs to which operatoraddress.registerPublicKey→verifyAggregateSignature) is not connected to the economic layer at all.Business goal
ROLE_DVTin the SuperPaymasterRegistry/GTokenStaking is the only place stake is managed. Every other contract
reads it, never re-implements it.
nodeId/pubkey is tied to a staked operator, soco-signing rights require economic backing and a misbehaving signer can be slashed
through its operator.
node without owner approval, with an owner toggle so we can bootstrap in a
permissioned (
onlyOwner) mode first and flip to permissionless when ready.Technical plan (proposed — to be challenged/finalized)
Bind by operator address; defer stake to SuperPaymaster; keep pubkey local.
YetAnotherAA-Validator —
AAStarValidatormapping(bytes32 => address) public nodeOperator;— the operator that owns a nodeId.registerPublicKey(bytes32 nodeId, bytes pubkey)called by the operator(
msg.sender):nodeOperator[nodeId] = msg.sender.Registry.hasRole(ROLE_DVT, msg.sender)andGTokenStaking.roleLocks(msg.sender, ROLE_DVT).amount ≥ Registry.getRoleConfig(ROLE_DVT).minStake.bool public requireStake(owner-settable).false→ bootstrap(
onlyOwnerregisters, as today).true→ permissionless-but-staked. This is the①stake-check + ②toggle asked for.
Registryaddress (owner-settable), read-only.verifyAggregateSignatureadds a real-time liveness read perparticipating operator — off by default to keep gas/coupling low; discuss.
SuperPaymaster — Registry / GTokenStaking / DVTValidator
Registry.hasRole,Registry.getRoleConfig(ROLE_DVT).minStake,Registry.GTOKEN_STAKING(),GTokenStaking.roleLocks(user, roleId). (All appear to exist as of DVTValidator 0.6.0.)nodeId → operator. Options: (a) AAStarValidator emitsNodeBound(nodeId, operator)and off-chain relays to a slash proposal; (b) a small view/callback. Discuss which.
ROLE_DVT = keccak256("DVT")— align the roleId constant across both repos.Roles / responsibilities after unification
ROLE_DVT, slashing, liveness.verification; registration reads SuperPaymaster for the stake gate and binds
nodeId → staked operator.
address, stored per nodeId.Why not fully consolidate into SuperPaymaster
verifyAggregateSignatureis the hot path (called per UserOp). Keeping pubkeys localavoids a cross-contract read per signature (gas + tight coupling). We read the economic
layer once at registration, not per verification.
Post-implementation flow
Open questions (for the challenge / finalization)
nodeId → operator? Event-relayvs on-chain view vs storing the binding in SuperPaymaster instead.
only (as DVTValidator already assumes)?
AAStarValidator.sol(permissionlesssource) vs deployed
AAStarBLSAlgorithmv0.20.0 (onlyOwner): which is canonical,and does this change target a redeploy?
ROLE_DVTconstant + who tunesminStake(Registry.configureRole, owner/governance).
or one stake per nodeId? Affects the binding cardinality.
Cross-repo: AAStarCommunity/YetAnotherAA-Validator (AAStarValidator changes) +
AAStarCommunity/SuperPaymaster (interface confirm + slashing linkage). This issue is
filed to both. Related: YetAnotherAA-Validator#157 (N-of-M), #50 (hardening).