Skip to content

Conversation

@hlgltvnnk
Copy link
Collaborator

@hlgltvnnk hlgltvnnk commented Nov 17, 2025

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

    • Legacy nonces are no longer supported and will be rejected as invalid.
  • Improvements

    • Nonce handling now uses explicit conversion and error propagation, ensuring only properly formatted, versioned nonces proceed.
  • Tests

    • Legacy-nonce tests removed or updated; test flows now assert invalid-nonce behavior and use payload-based nonce extraction.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Replaces 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

Cohort / File(s) Summary
VersionedNonce implementation
core/src/nonce/versioned.rs
Add impl TryFrom<Nonce> for VersionedNonce returning DefuseError on invalid/legacy nonces; strip version prefix and deserialize remainder; map failures to DefuseError::InvalidNonce.
Engine handling
core/src/engine/mod.rs
Replace maybe_from/Option early-return with try_into() and propagate errors; perform a single match focusing on VersionedNonce::V1(...) path.
Contract garbage collector
defuse/src/contract/garbage_collector.rs
Change conversion usage to try_from/try_into with guard (let Ok(versioned_nonce) = ... else { return false; }); legacy nonces no longer considered cleanable.
Tests — nonces & imports
tests/src/tests/defuse/accounts/nonces.rs
Remove Nonce import and legacy-nonce test cases; adjust assertions to expect invalid-nonce errors where applicable.
Tests — intents refactor & removal
tests/src/tests/defuse/intents/mod.rs, tests/src/tests/defuse/intents/legacy_nonce.rs
Remove legacy_nonce test file; in mod.rs remove legacy module, switch signing to payload-based flow with ExtractNonceExt, drop RNG param, and comment out webauthn test block/TODOs.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review TryFrom implementation and error mapping in core/src/nonce/versioned.rs.
  • Verify correct propagation of DefuseError::InvalidNonce at engine and garbage-collector call sites.
  • Confirm test removals/changes reflect intended policy (legacy nonces treated invalid) and ensure no stray references remain.
  • Check commented-out webauthn test area in tests/.../intents/mod.rs for TODO tracking.

Possibly related PRs

Suggested reviewers

  • fusede
  • BonelessImpl

Poem

🐰 I hopped through code at break of dawn,

try_from now finds what maybe had drawn.
Legacy hops are left behind,
DefuseError keeps them neatly fined.
🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'WIP: Feat/disable legacy nonces' clearly describes the main change—disabling legacy nonces—and is directly reflected throughout the changeset where legacy nonce support is removed and replaced with versioned nonce handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/disable-legacy-nonces

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 behavior

The note now contradicts the implementation: legacy/non-versioned nonces are rejected via TryFrom<Nonce> and treated as DefuseError::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: Rename maybe_from_test to match TryFrom semantics

The test now exercises VersionedNonce::try_from rather than maybe_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 semantics

The 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 of rng.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 test

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2f75c2c and d9001ab.

📒 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 prefix

Conversion 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 of strip_prefix plus deserialize_reader is 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_cleanable now returns false whenever VersionedNonce::try_from fails, 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_nonce now requires a VersionedNonce via nonce.try_into()?; any non-versioned or malformed nonce fails fast with DefuseError::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 extraction

Switching to sign_defuse_payload_default plus extract_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

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between d9001ab and 0a48825.

📒 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 DefuseSignerExt trait import provides the necessary signing functionality.


239-244: Nonce extraction mechanism verified—no issues found.

The implementation correctly maintains nonce uniqueness and proper extraction:

  1. Unique nonce generation: unique_nonce() combines three components for strong uniqueness: a per-account/contract salt, 15 cryptographically random bytes (via TestRng::from_entropy()), and a 120-second expiration deadline.

  2. Correct extraction: extract_nonce() deserializes the DefusePayload and retrieves the embedded nonce directly—straightforward and reliable.

  3. 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.

Comment on lines +295 to +355
// 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
// );
// }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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:

  1. Implementing the correct WebAuthn signature generation path with the new nonce format
  2. Re-enabling and updating this test
  3. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants