feat(operator): DVT node-registration wizard (CC-17 / aastar-sdk#279)#424
Conversation
…-sdk#279) Guided operator onboarding for DVT signing-node registration, reusing the existing operator deploy wizard scaffolding (StepCard / WizardButton / WizardProgress / FormField / useTxStep) and the Track-C browser-signing path. Flow: connect -> eligibility -> key & PoP -> register -> done. The eligibility step short-circuits to done when the operator already runs a node. The BLS secret key is generated/imported in-browser and never persisted or sent to the backend; nodeId is previewed from the PoP before any tx. All chain/crypto calls sit behind lib/sdk/dvtOperator.ts, stubbed with DVT_SDK_READY=false until @aastar/sdk 0.37.4/0.38.0 (PR #288) publishes dvtOperatorActions + buildDvtPop. Wiring is a one-file change (flip the flag, add the import, fill the WIRE-HERE bodies). Adds a DVT-registration entry card to the operator hub and zh/en i18n. Also adds .goutou.json (repoId: yaaa). Claude-Session: https://claude.ai/code/session_01RajETCvboSvhadpqMbekNx
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…-17) @aastar/sdk 0.38.0 ships buildDvtPop + dvtOperatorActions + DVT_VALIDATOR_ADDRESS (aastar-sdk#288, on-chain registerWithProof E2E LIVE PASS on Sepolia, tx 0x216a7ed5…, Codex APPROVE). Fill in the lib/sdk/dvtOperator.ts boundary: - flip DVT_SDK_READY, drop the pending-stub scaffolding - buildDvtPop -> sdk buildDvtPop; eligibility/register/isRegistered -> dvtOperatorActions(DVT_VALIDATOR_ADDRESS)(client): reads on a public client, register on the operator's wallet client - validator address from the SDK canonical DVT_VALIDATOR_ADDRESS (post ensureSdkConfig), never hardcoded Bump @aastar/sdk ^0.35.0 -> ^0.38.0 in aastar-frontend (surgical lockfile version/resolved/integrity; deps unchanged 0.35->0.38 so no tree churn; backend range ^0.35.0 still satisfied by the hoisted 0.38.0). Frontend + backend type-check, frontend eslint, and backend build all pass. Claude-Session: https://claude.ai/code/session_01RajETCvboSvhadpqMbekNx
clestons
left a comment
There was a problem hiding this comment.
#424 — APPROVE
PK: 1 round (Codex) · 2 confirmed · 1 challenged (F3 rejected) · 1 MISSED (Low) · head 9c3aeee7
DVT 节点注册向导结构清晰,BLS 私钥全程仅驻留浏览器内存,未持久化也未传后端。DVT_SDK_READY 机制保证 0.38.0 前 UI 可走完但链上步骤被明确 stub,二次 commit 的 wiring 也正确:readActions() 用 getPublicClient() 读,writeActions(walletClient) 用 operator 钱包写。
[Confirmed, Medium] F1 — generateBlsSecretKey() 约 55% 生成无效密钥,注释严重错误
lib/sdk/dvtOperator.ts:86-95
BLS12-381 标量域阶 r = 0x73eda753…,约 0.45 × 2^256,因此均匀随机 256-bit 值超过 r 的概率约 55%。每次点"生成新密钥"约每隔一次 buildDvtPop 就会抛出 invalid BLS secret key: out of range,用户看到错误并需要重试。注释声称 "~2^-128 chance" 系错误估算,实际差了约 2^127 倍。
建议在生成循环内拒绝越界值:
export function generateBlsSecretKey(): Hex {
while (true) {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
const key = `0x${hex}` as Hex;
try { buildDvtPop(key); return key; } catch { /* out of range, retry */ }
}
}或直接用 BigInt(rand) % r + 1n 生成均匀标量。
[Confirmed, Low] F2 — isDvtNodeRegistered 返回值未检查,注册失败可能被静默吞掉
aastar-frontend/app/operator/dvt-register/steps/Step4Register.tsx:52-58
try {
if (nodeId) await isDvtNodeRegistered(walletClient, nodeId); // bool 被丢弃
} catch (err) {
setConfirmError(...)
}
onNext(); // 无论 isRegistered 是否为 true若 isRegistered 返回 false(链上确认延迟或 RPC 抖动),向导静默前进到 "Registration complete",实际节点未注册。建议:
const isReg = await isDvtNodeRegistered(walletClient, nodeId);
if (!isReg) setConfirmError("Node ID not yet visible on-chain — please refresh eligibility.");[PK-added, Low] M1 — applyKey 在 buildDvtPop 抛错后仍将无效 key 写入 wizard state
aastar-frontend/app/operator/dvt-register/steps/Step3KeyPoP.tsx:46-53
const applyKey = (secret: Hex) => {
setError(undefined);
let pop: DvtPop | undefined;
if (DVT_SDK_READY) {
try {
pop = buildDvtPop(secret);
} catch (err) {
setError(...);
// ← 没有 return
}
}
update({ blsSecretKey: secret, pop }); // 越界 key 也被写入
};buildDvtPop 抛出时 pop = undefined 但 blsSecretKey = secret 被写入。hasKey = !!data.blsSecretKey 为 true → Continue 按钮可用,用户可带无效 key 进入 Step4,Step4 的 register 调用会再次失败。建议 catch 内 return,不调用 update()。
Rejected
- F3(
fetchDvtEligibility参数名变更为非下划线):diff 中实际未发生重命名,_walletClient依然保留,系审查侧误读。
PK Summary
| Finding | Verdict |
|---|---|
| F1 generateBlsSecretKey ~55% 越界 | [CONFIRM] Medium |
| F2 isDvtNodeRegistered bool 被丢弃 | [CONFIRM] Low |
| F3 参数名重命名误读 | Rejected |
| M1 applyKey 越界 key 写入 state | [PK-added] Low |
APPROVE
…ey commit) - F1 [Medium] generateBlsSecretKey: a single 256-bit draw is ≥ the BLS12-381 scalar order ~55% of the time (r ≈ 0.453·2^256), so "Generate" produced a key buildDvtPop rejects roughly half the time. Rejection-sample until the draw is in [1, r-1]; fix the bogus "~2^-128" comment. - F2 [Low] Step4: isDvtNodeRegistered's boolean was awaited but ignored — a false read-back now surfaces a warning (tx already confirmed, so still advance). - M1 [Low] Step3 applyKey: an out-of-range imported key was still written to wizard state (Continue enabled) despite buildDvtPop throwing. Commit the key only when its PoP derives; clear it on failure. Frontend type-check + eslint green. Claude-Session: https://claude.ai/code/session_01RajETCvboSvhadpqMbekNx
clestons
left a comment
There was a problem hiding this comment.
#424 — APPROVE(增量)
增量 scope:9c3aeee7..d99a5af1(1 commit: fix 上轮 review)
PK: 1 round · 3 confirmed · 0 challenged · 0 MISSED · head d99a5af1
上轮三个 findings 均已正确修复:
- F1
generateBlsSecretKey改用 rejection-sampling,BLS12_381_R常量正确,循环边界[1, r-1]无误,注释更新为"~2 draws on average"。✓ - F2
isDvtNodeRegistered返回值已检查,false 时触发confirmFailedi18n 提示(EN/ZH 均已添加),onNext()仍调用(tx 已确认,设计正确)。✓ - M1
applyKey成功路径才持久化 key + pop;失败路径同步清空两者,无效 key 不再使 Continue 按钮可用。✓
APPROVE
The prior commit bumped only aastar-frontend to ^0.38.0. For 0.x versions
`^0.35.0` means >=0.35.0 <0.36.0, so the backend's untouched ^0.35.0 no longer
matched the hoisted 0.38.0 — `npm ci` failed the lockfile sync check ("Missing:
@aastar/sdk@0.35.0"), taking every CI job down at the install step. Bump the
backend range (+ its lockfile stanza) to ^0.38.0; backend already type-checks
and builds against 0.38.0. `npm ci --dry-run` sync gate passes.
Claude-Session: https://claude.ai/code/session_01RajETCvboSvhadpqMbekNx
What
Guided operator onboarding for DVT signing-node registration — turns the manual
registerWithProofscript into a 5-step wizard underapp/operator/dvt-register/.Flow: connect → eligibility → key & PoP → register → done. Reuses the existing operator deploy-wizard scaffolding (
StepCard/WizardButton/WizardProgress/FormField/useTxStep) and the Track-C browser-signing path (operator's own EOA signs; no backend key).Highlights
operatorNode(operator)non-zero → jump to done showing the boundnodeId.crypto.getRandomValues) or imported in-browser, held only in in-memory wizard state — never persisted, never sent to the backend. Red warning + offline-backup prompt.nodeIdpreviewed from the PoP before any tx.lib/sdk/dvtOperator.ts): all chain/crypto calls funnel through it.SDK wiring
@aastar/sdk0.38.0 shippedbuildDvtPop+dvtOperatorActions(aastar-sdk#288, E2E LIVE PASS on Sepolia). Wiring = flipDVT_SDK_READY, add the import, fill theWIRE-HEREbodies. (Done in a follow-up commit on this branch.)Test
type-check+eslintgreen.Coordination: CC-17.
https://claude.ai/code/session_01RajETCvboSvhadpqMbekNx