feat(testkit): dapp-author testing tools — local-key Nido accounts + local auth simulation - #189
feat(testkit): dapp-author testing tools — local-key Nido accounts + local auth simulation#189willemneal wants to merge 2 commits into
Conversation
…local auth simulation Implements #188. Lets a dapp author create and exercise a Nido smart account from local keys — no WebAuthn passkey, no live network — and simulate authorization locally. TS — @nidohq/testkit (packages/testkit): - local signers for every verifier the account supports, each signing the auth digest with the bytes that verifier accepts: - secp256r1 → a local P-256 key building a WebAuthn assertion (the same webauthn-verifier, minus the passkey), via a ported buildSyntheticAssertion; - ed25519 → 64-byte signature; - ml-dsa-65 → post-quantum signature (@noble/post-quantum). - createLocalAccount: real deployer+salt C-address derivation + the account's perch PolicyDoc + a real doc_hash (perch canonical vendored, byte-identical). - simulateCheckAuth: mirrors do_check_auth + perch policy evaluation (function allowlist, arg predicates, expiry, N-of-N signer floor) → Kleene verdict with a readable trace. - reachableCalls / isNarrowing: perch reachable-call + attenuation analysis. - 12 vitest tests (all three signers round-trip; account derivation; the ci-publish allow/deny/expiry/is-self/zero-sig matrix; attenuation). Rust — nido-testkit (crates/testkit): - test_p256_key + build_contract_assertion: the contract-test twin of the TS secp256r1 path (deterministic local key + synthetic WebAuthn assertion), pure-crypto, no soroban dep. 2 tests. Honesty: only secp256r1 has a deployed verifier today. ed25519 and ML-DSA verifiers, and perch policies themselves, are modelled ahead of their on-chain contracts (ML-DSA groundwork = #143; perch = stellar-registry/perch) — the simulator IS the perch interpreter until integration lands. Roadmap: swap the TS simulation for soroban-env in the browser (wasmi) + rs-soroban-sdk#1657's local-storage cache for lazy testnet pulls. Consumed by examples/perch-authz-console (next). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Example dApp preview deployed! https://example-pr-189.mysoroban.pages.dev The |
|
Preview deployed! Account URLs use numeric preview suffixes, for example |
…st the first A call can fall under more than one rule of the same scope (e.g. two self-admin rules signed by different keys). The old first-match logic denied a call the first rule couldn't satisfy even when a later rule could — so a self-admin op signed only by the ML-DSA key was wrongly denied because admin-root (needing the admin key) matched first. Now it tries all scope-matching rules and authorizes if any does (matching OZ, where the caller nominates a rule via context_rule_ids), else returns the first deny. +2 tests: a self-admin call authorized by a non-first rule (the ML-DSA signer), and denied when no matching rule is satisfied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| let firstDeny: SimResult | null = null; | ||
| for (const [rule, idx] of matching) { | ||
| const res = evalRule(account, ctx, ledger, rule, idx, signedBy); | ||
| if (res.verdict === 'allow') return res; |
There was a problem hiding this comment.
Business Logic: Over-permissive rule selection in simulateCheckAuth
simulateCheckAuth now authorizes if any scope-matching rule passes (for (const [rule, idx] of matching) ... if (res.verdict === 'allow') return res), instead of selecting a single applicable rule. If multiple rules can match the same call (e.g., multiple self-admin rules), the simulator may return allow even when a dapp intended to model a specific rule selection via context_rule_ids, potentially causing developers to ship policies/UI flows that assume weaker signer requirements than what an on-chain __check_auth invocation with fixed context_rule_ids would enforce.
Make the simulator accept an explicit contextRuleId/context_rule_ids input (or a mode flag) and evaluate only that rule, keeping the current "try all" behavior as an opt-in helper for UX discovery rather than the default authorization decision.
Actions
- Reply
/almanax ask <question>to ask a follow-up question. - Reply
/almanax dismiss [<reason>]and it won't appear again in future scans. - Reply
/almanax resolve [<reason>]to mark the finding as resolved. - Reply
/almanax severity <level> [<reason>]to override the severity.

Implements #188. Lets a dapp author create and exercise a Nido smart account from local keys — no WebAuthn passkey, no live network — and simulate authorization locally.
TS —
@nidohq/testkit(packages/testkit)webauthn-verifier, minus the passkey), via a portedbuildSyntheticAssertion;@noble/post-quantum).createLocalAccount— real deployer+salt C-address derivation + the account's perchPolicyDoc+ a realdoc_hash(perch canonical vendored, byte-identical).simulateCheckAuth— mirrorsdo_check_auth+ perch policy evaluation (function allowlist, arg predicates, expiry, N-of-N signer floor) → Kleene verdict with a readable trace.reachableCalls/isNarrowing— perch reachable-call + attenuation analysis.Rust —
nido-testkit(crates/testkit)test_p256_key+build_contract_assertion— the contract-test twin of the TS secp256r1 path (deterministic local key + synthetic WebAuthn assertion), pure-crypto, no soroban dep. 2 tests.Honest scope
Only secp256r1 has a deployed verifier today. ed25519 and ML-DSA verifiers, and perch policies themselves, are modelled ahead of their on-chain contracts (ML-DSA groundwork = #143; perch = stellar-registry/perch) — the simulator is the perch interpreter until integration lands. This is what "simulation-first" is for.
Roadmap: swap the TS simulation for
soroban-envin the browser (wasmi) + rs-soroban-sdk#1657's local-storage cache for lazy testnet pulls — samesimulateCheckAuthcall, real VM, offline-first.Verification
packages/testkit:tsc --noEmitclean,tsc(dist) clean,vitest run— 12 passedcrates/testkit:cargo test -p nido-testkit— 2 passedNext
Consumed + demonstrated by
examples/perch-authz-console(separate PR): local-key wallet login, create a Nido account that supports perch, and a live policy visualizer + builder including the ML-DSA signer.🤖 Generated with Claude Code