-
Notifications
You must be signed in to change notification settings - Fork 19
WIP: Feat/disable legacy nonces #152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughReplaces Option-based nonce parsing with a Result-based TryFrom → VersionedNonce conversion, updates call sites to handle conversion errors, narrows engine handling to the V1 variant, and removes/adjusts legacy-nonce tests and related test flows. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Caller as Engine / Contract
participant Nonce as Nonce bytes
participant Conv as VersionedNonce::try_from
participant Handler as V1 handler (salt/deadline checks)
Note over Caller,Nonce: Incoming request includes `Nonce`
Caller->>Conv: try_from(Nonce)
alt valid and versioned
Conv-->>Caller: Ok(VersionedNonce::V1)
Caller->>Handler: match V1 -> validate salt/deadline
alt checks pass
Handler-->>Caller: proceed with intent execution
else checks fail
Handler-->>Caller: return error (e.g., expired/invalid salt)
end
else invalid / legacy
Conv-->>Caller: Err(DefuseError::InvalidNonce)
Caller-->>Caller: short-circuit / return false for cleanup or propagate error
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
core/src/nonce/versioned.rs (2)
21-25: Update legacy-nonce comment to reflect new behaviorThe note now contradicts the implementation: legacy/non-versioned nonces are rejected via
TryFrom<Nonce>and treated asDefuseError::InvalidNonce, not “still usable”.-// NOTE: Legacy nonces can still be used at this time, but will be prohibited out in the near future. +// NOTE: Legacy/non-versioned nonces are treated as invalid; only versioned nonces +// with `VERSIONED_MAGIC_PREFIX` are accepted by the engine.
62-80: Renamemaybe_from_testto match TryFrom semanticsThe test now exercises
VersionedNonce::try_fromrather thanmaybe_from, but the function name still references the old API. Renaming will avoid confusion for future readers.- #[rstest] - fn maybe_from_test(random_bytes: Vec<u8>) { + #[rstest] + fn try_from_nonce_test(random_bytes: Vec<u8>) {tests/src/tests/defuse/accounts/nonces.rs (1)
42-60: Legacy nonce commit test matches new InvalidNonce semanticsThe new “can't commit legacy nonce” block correctly asserts that executing an intent with a non-versioned/random nonce now fails with
"invalid nonce", which aligns with the engine’s stricter verification.As a minor improvement, you could use a deterministic value that’s guaranteed not to start with
VERSIONED_MAGIC_PREFIX(e.g., a zeroed nonce) instead ofrng.random(), to remove even the tiny chance of accidentally generating a valid versioned nonce.- let deadline = Deadline::MAX; - let legacy_nonce = rng.random(); + let deadline = Deadline::MAX; + // Deterministic non-versioned nonce; guaranteed to fail TryFrom<Nonce>. + let legacy_nonce = Default::default();tests/src/tests/defuse/intents/mod.rs (1)
302-362: Consider using#[ignore]instead of fully commenting out WebAuthn testThe WebAuthn test is temporarily disabled with a large commented block. Using
#[ignore]on the test instead keeps the code compiled and closer to the final shape while still excluding it from regular runs.-// TODO: temporary disabled test until we have correct way to generate WebAuthn signatures -// #[tokio::test] -// #[rstest] -// async fn webauthn(#[values(false, true)] no_registration: bool) { +// TODO: temporary disabled test until we have correct way to generate WebAuthn signatures +#[tokio::test] +#[rstest] +#[ignore = "WebAuthn signatures generation not yet implemented for this test"] +async fn webauthn(#[values(false, true)] no_registration: bool) { /* existing body */ -// } +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
core/src/engine/mod.rs(1 hunks)core/src/nonce/versioned.rs(4 hunks)defuse/src/contract/garbage_collector.rs(1 hunks)tests/src/tests/defuse/accounts/nonces.rs(3 hunks)tests/src/tests/defuse/intents/legacy_nonce.rs(0 hunks)tests/src/tests/defuse/intents/mod.rs(4 hunks)
💤 Files with no reviewable changes (1)
- tests/src/tests/defuse/intents/legacy_nonce.rs
🧰 Additional context used
🧬 Code graph analysis (2)
tests/src/tests/defuse/intents/mod.rs (1)
core/src/payload/mod.rs (1)
nonce(68-70)
defuse/src/contract/garbage_collector.rs (1)
core/src/nonce/versioned.rs (1)
try_from(30-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
- GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
- GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
- GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
- GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
- GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
- GitHub Check: Security Audit - report
🔇 Additional comments (4)
core/src/nonce/versioned.rs (1)
27-36: TryFrom implementation correctly gates nonces by magic prefixConversion now strictly requires the versioned magic prefix and successful Borsh deserialization; any mismatch or decode error becomes
DefuseError::InvalidNonce, which matches the PR goal of disabling legacy nonces. The use ofstrip_prefixplusdeserialize_readeris succinct and sound for the current V1 layout.defuse/src/contract/garbage_collector.rs (1)
33-43: Confirm intent to leave legacy/non-versioned nonces uncleared
is_nonce_cleanablenow returnsfalsewheneverVersionedNonce::try_fromfails, so legacy or otherwise invalid nonces will never be cleaned, even if they exist in old state. This matches the comment but is a behavioral change from earlier, more permissive cleanup logic.Can you confirm there are no remaining production flows that rely on garbage-collecting legacy nonces (e.g., old used nonces that should eventually disappear), and that keeping them “stuck” as used is acceptable from a UX/state-size perspective?
core/src/engine/mod.rs (1)
88-106: Nonce verification now correctly rejects non-versioned/invalid nonces
verify_intent_noncenow requires aVersionedNoncevianonce.try_into()?; any non-versioned or malformed nonce fails fast withDefuseError::InvalidNonce, while the existing salt/deadline checks remain intact for V1. This matches the desired “versioned-only” policy.tests/src/tests/defuse/intents/mod.rs (1)
3-4: simulate_is_view_method now validates nonce via payload extractionSwitching to
sign_defuse_payload_defaultplusextract_nonce()makes the test exercise the same payload construction path as the engine and asserts directly against the nonce embedded in the signed payload. This is a solid way to keep the test aligned with the versioned-nonce flow.Also applies to: 220-252
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
tests/src/tests/defuse/intents/mod.rs(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/src/tests/defuse/intents/mod.rs (2)
tests/src/tests/defuse/env/builder.rs (1)
no_registration(80-83)core/src/payload/mod.rs (1)
nonce(68-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Security Audit - deny
- GitHub Check: Build Reproducible
- GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
- GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
- GitHub Check: Security Audit - report
🔇 Additional comments (3)
tests/src/tests/defuse/intents/mod.rs (3)
1-2: LGTM!The import changes correctly support the new payload-based nonce extraction pattern.
212-213: LGTM!The simplified signature correctly reflects the shift to payload-based signing. The
DefuseSignerExttrait import provides the necessary signing functionality.
239-244: Nonce extraction mechanism verified—no issues found.The implementation correctly maintains nonce uniqueness and proper extraction:
Unique nonce generation:
unique_nonce()combines three components for strong uniqueness: a per-account/contract salt, 15 cryptographically random bytes (viaTestRng::from_entropy()), and a 120-second expiration deadline.Correct extraction:
extract_nonce()deserializes theDefusePayloadand retrieves the embedded nonce directly—straightforward and reliable.Uniqueness guarantees: The salt ensures per-account/contract isolation, random bytes provide cryptographic uniqueness, and the deadline adds temporal scoping. This approach is equivalent to or stronger than random-only nonce generation and effectively prevents replay attacks.
The nonce is embedded in the signed payload during
sign_defuse_payload_default()and extracted immediately thereafter, with no intervening state changes—ensuring deterministic, safe behavior across the test suite.
| // TODO: temporary disabled test until we have correct way to generate WebAuthn signatures | ||
| // #[tokio::test] | ||
| // #[rstest] | ||
| // async fn webauthn(#[values(false, true)] no_registration: bool) { | ||
| // const SIGNER_ID: &AccountIdRef = | ||
| // AccountIdRef::new_or_panic("0x3602b546589a8fcafdce7fad64a46f91db0e4d50"); | ||
|
|
||
| // let env = Env::builder() | ||
| // .no_registration(no_registration) | ||
| // .build() | ||
| // .await; | ||
|
|
||
| // let (user, ft) = futures::join!( | ||
| // env.create_named_user("user1"), | ||
| // env.create_named_token("ft1") | ||
| // ); | ||
|
|
||
| // let ft_id = TokenId::from(Nep141TokenId::new(ft.clone())); | ||
|
|
||
| // env.initial_ft_storage_deposit(vec![user.id()], vec![&ft]) | ||
| // .await; | ||
|
|
||
| // // deposit | ||
| // env.defuse_ft_deposit_to(&ft, 2000, &SIGNER_ID.to_owned()) | ||
| // .await | ||
| // .unwrap(); | ||
|
|
||
| // env.defuse | ||
| // .execute_intents(env.defuse.id(), [serde_json::from_str(r#"{ | ||
| // "standard": "webauthn", | ||
| // "payload": "{\"signer_id\":\"0x3602b546589a8fcafdce7fad64a46f91db0e4d50\",\"verifying_contract\":\"defuse.test.near\",\"deadline\":\"2050-03-30T00:00:00Z\",\"nonce\":\"A3nsY1GMVjzyXL3mUzOOP3KT+5a0Ruy+QDNWPhchnxM=\",\"intents\":[{\"intent\":\"transfer\",\"receiver_id\":\"user1.test.near\",\"tokens\":{\"nep141:ft1.poa-factory.test.near\":\"1000\"}}]}", | ||
| // "public_key": "p256:2V8Np9vGqLiwVZ8qmMmpkxU7CTRqje4WtwFeLimSwuuyF1rddQK5fELiMgxUnYbVjbZHCNnGc6fAe4JeDcVxgj3Q", | ||
| // "signature": "p256:2wpTbs61923xQU9L4mqBGSdHSdv5mqMn3zRA2tFmDirm8t4mx1PYAL7Vhe9uta4WMbHoMMTBZ8KQSM7nWug3Nrc7", | ||
| // "client_data_json": "{\"type\":\"webauthn.get\",\"challenge\":\"DjS-6fxaPS3avW-4ls8dDYAynCmsAXWCF86cJBTkHbs\",\"origin\":\"https://defuse-widget-git-feat-passkeys-defuse-94bbc1b2.vercel.app\"}", | ||
| // "authenticator_data": "933cQogpBzE3RSAYSAkfWoNEcBd3X84PxE8iRrRVxMgdAAAAAA==" | ||
| // }"#).unwrap(), serde_json::from_str(r#"{ | ||
| // "standard": "webauthn", | ||
| // "payload": "{\"signer_id\":\"0x3602b546589a8fcafdce7fad64a46f91db0e4d50\",\"verifying_contract\":\"defuse.test.near\",\"deadline\":\"2050-03-30T00:00:00Z\",\"nonce\":\"B3nsY1GMVjzyXL3mUzOOP3KT+5a0Ruy+QDNWPhchnxM=\",\"intents\":[{\"intent\":\"transfer\",\"receiver_id\":\"user1.test.near\",\"tokens\":{\"nep141:ft1.poa-factory.test.near\":\"1000\"}}]}", | ||
| // "public_key": "p256:2V8Np9vGqLiwVZ8qmMmpkxU7CTRqje4WtwFeLimSwuuyF1rddQK5fELiMgxUnYbVjbZHCNnGc6fAe4JeDcVxgj3Q", | ||
| // "signature": "p256:5Zq1w2ntVi5EowuKPnaSyuM2XB3JsQZub5CXB1fHsP6MWMSV1RXEoqpgVn5kNK43ZiUoXGBKVvUSS3DszwWCWgG6", | ||
| // "client_data_json": "{\"type\":\"webauthn.get\",\"challenge\":\"6ULo-LNIjd8Gh1mdxzUdHzv2AuGDWMchOORdDnaLXHc\",\"origin\":\"https://defuse-widget-git-feat-passkeys-defuse-94bbc1b2.vercel.app\"}", | ||
| // "authenticator_data": "933cQogpBzE3RSAYSAkfWoNEcBd3X84PxE8iRrRVxMgdAAAAAA==" | ||
| // }"#).unwrap()]) | ||
| // .await | ||
| // .unwrap(); | ||
|
|
||
| // assert_eq!( | ||
| // env.defuse | ||
| // .mt_balance_of(user.id(), &ft_id.to_string()) | ||
| // .await | ||
| // .unwrap(), | ||
| // 2000 | ||
| // ); | ||
| // assert_eq!( | ||
| // env.defuse | ||
| // .mt_balance_of(&SIGNER_ID.to_owned(), &ft_id.to_string()) | ||
| // .await | ||
| // .unwrap(), | ||
| // 0 | ||
| // ); | ||
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Track WebAuthn test coverage restoration in an issue.
The removal of the WebAuthn test eliminates end-to-end coverage for passkey/biometric authentication flows. While understandable given the nonce format migration and the PR's WIP status, this represents a significant gap that must be addressed before final release.
Since the PR description indicates this should only be merged after systems are ready, please create a tracking issue for:
- Implementing the correct WebAuthn signature generation path with the new nonce format
- Re-enabling and updating this test
- Verifying WebAuthn flows work end-to-end with the new nonce system
Would you like me to help draft the issue description or generate a skeleton for the updated test implementation?
🤖 Prompt for AI Agents
In tests/src/tests/defuse/intents/mod.rs around lines 295 to 355, the WebAuthn
end-to-end test is temporarily disabled; create a tracking issue to restore
coverage and re-enable the test once nonce/signature generation is updated. Open
an issue that (1) describes implementing correct WebAuthn signature generation
for the new nonce format, (2) updates and re-enables this test with examples of
valid test vectors and harness changes required, and (3) includes an acceptance
checklist to verify full end-to-end WebAuthn flows against the new nonce system;
reference this file and the specific commented test, assign to the feature
owner, label with "webauthn", "testing", and "blocker", and include a note to
link the issue in the PR before merging.
This PR can be merged only after all system will be ready for the update + main part of the solvers will switch to new nonce version
Summary by CodeRabbit
Breaking Changes
Improvements
Tests