Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
498d999
versioned + salted nonces added
hlgltvnnk Sep 25, 2025
080a077
nonce verifier added
hlgltvnnk Sep 26, 2025
04567c2
salt management methods added
hlgltvnnk Sep 26, 2025
f29f537
tests updated
hlgltvnnk Sep 26, 2025
e9ce10e
valid salts format upd: all previous salts stored in map
hlgltvnnk Sep 26, 2025
28a4618
nonce verifier removed, logic moved to state
hlgltvnnk Sep 29, 2025
9a34e35
nonce tests updated + invalidate salt method added
hlgltvnnk Sep 29, 2025
792717d
salt manager tests added
hlgltvnnk Sep 30, 2025
c5d43f1
fee manager tests added
hlgltvnnk Sep 30, 2025
06d2459
state versioning added + clippy lints fixed
hlgltvnnk Sep 30, 2025
efae3c5
refactoring
hlgltvnnk Oct 1, 2025
ecac381
changelog added + minor fixes
hlgltvnnk Oct 1, 2025
a5c2d45
minor edit
hlgltvnnk Oct 1, 2025
40a10ad
refactoring
hlgltvnnk Oct 1, 2025
0728277
minor edits
hlgltvnnk Oct 1, 2025
b294c34
changes according to review
hlgltvnnk Oct 6, 2025
cf75a19
minor edit
hlgltvnnk Oct 6, 2025
dde795b
changes according to review #2
hlgltvnnk Oct 6, 2025
8060aba
contract versioning addded
hlgltvnnk Oct 7, 2025
b7cee39
minor fixs + missing files
hlgltvnnk Oct 7, 2025
7493e1e
naming upd + minor fixes
hlgltvnnk Oct 7, 2025
3d200e5
state migration updated + refactored
hlgltvnnk Oct 7, 2025
56da699
refactoring
hlgltvnnk Oct 7, 2025
28d9174
changes according to review #3
hlgltvnnk Oct 7, 2025
1ae5bcb
refactoring
hlgltvnnk Oct 8, 2025
494abdf
salt derivation updated + naming upd
hlgltvnnk Oct 8, 2025
2961a7e
minor updates
hlgltvnnk Oct 8, 2025
bb28ed3
MaybeVersionedNonce removed
hlgltvnnk Oct 8, 2025
731224d
refactoring
hlgltvnnk Oct 8, 2025
e550ff1
missing file from prev commit
hlgltvnnk Oct 8, 2025
3acfda8
minor edits
hlgltvnnk Oct 8, 2025
5385e75
refactoring + garbage collector role added
hlgltvnnk Oct 9, 2025
6087055
clean nonces made payable
hlgltvnnk Oct 9, 2025
010f6ae
fix
hlgltvnnk Oct 9, 2025
f30b752
test fix
hlgltvnnk Oct 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.3.0]

### Added
- 4 bytes salts added to state for security purposes
- SaltManager role to manage salts rotation functionality
- Expirable nonces: contain expiration deadline in nanoseconds
- Salted nonces: contain salt part as validity identifier
- Cleanup functionality for nonces to preserve storage: all expired nonces or nonces with invalid salt can be cleared. Legacy nonces can't be cleared before a complete prohibition on its usage.

### Changed
- State versioning
- Nonce versioning
- Updated nonce format: The new version must contain the salt part + expiration date to determine if it is valid, but legacy nonces are still supported
- Optimized nonce storage by adding hashers into maps
- Intent simulation output returns all nonces committed by transaction

2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion core/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use near_sdk::{AccountIdRef, near};
use serde_with::serde_as;
use std::borrow::Cow;

use crate::Nonce;
use crate::{Nonce, Salt};

#[must_use = "make sure to `.emit()` this event"]
#[near(serializers = [json])]
Expand Down Expand Up @@ -63,3 +63,19 @@ impl NonceEvent {
Self { nonce }
}
}

#[must_use = "make sure to `.emit()` this event"]
#[near(serializers = [json])]
#[derive(Debug, Clone)]
pub struct RotateSaltEvent {
pub old_salt: Salt,
pub new_salt: Salt,
}

#[must_use = "make sure to `.emit()` this event"]
#[near(serializers = [json])]
#[derive(Debug, Clone)]
pub struct InvalidateSaltEvent {
pub current: Salt,
pub invalidated: Salt,
}
33 changes: 28 additions & 5 deletions core/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use self::{inspector::*, state::*};
use defuse_crypto::{Payload, SignedPayload};

use crate::{
DefuseError, ExpirableNonce, Result,
Deadline, DefuseError, ExpirableNonce, Nonce, Result, SaltedNonce, VersionedNonce,
intents::{DefuseIntents, ExecutableIntent},
payload::{DefusePayload, ExtractDefusePayload, multi::MultiPayload},
};
Expand Down Expand Up @@ -64,10 +64,6 @@ where

self.inspector.on_deadline(deadline);

if ExpirableNonce::maybe_from(nonce).is_some_and(|n| deadline > n.deadline) {
return Err(DefuseError::DeadlineGreaterThanNonce);
}

// make sure message is still valid
if deadline.has_expired() {
return Err(DefuseError::DeadlineExpired);
Expand All @@ -79,6 +75,7 @@ where
}

// commit nonce
self.verify_intent_nonce(nonce, deadline)?;
self.state.commit_nonce(signer_id.clone(), nonce)?;

intents.execute_intent(&signer_id, self, hash)?;
Expand All @@ -87,6 +84,32 @@ where
Ok(())
}

#[inline]
fn verify_intent_nonce(&self, nonce: Nonce, intent_deadline: Deadline) -> Result<()> {
match VersionedNonce::from(nonce) {
// NOTE: it is allowed to commit legacy nonces in this version
VersionedNonce::Legacy(_) => {}
VersionedNonce::V1(SaltedNonce {
salt,
nonce: ExpirableNonce { deadline, .. },
}) => {
if !self.state.is_valid_salt(salt) {
return Err(DefuseError::InvalidSalt);
}

if intent_deadline > deadline {
return Err(DefuseError::DeadlineGreaterThanNonce);
}

if deadline.has_expired() {
return Err(DefuseError::NonceExpired);
}
}
}

Ok(())
}

#[inline]
fn finalize(self) -> Result<Transfers> {
self.state
Expand Down
20 changes: 9 additions & 11 deletions core/src/engine/state/cached.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
DefuseError, Nonce, Nonces, Result,
DefuseError, Nonce, Nonces, Result, Salt,
amounts::Amounts,
fees::Pips,
intents::{
Expand Down Expand Up @@ -119,6 +119,10 @@ where
.is_some_and(|a| a.auth_by_predecessor_id_toggled);
was_enabled ^ toggled
}

fn is_valid_salt(&self, salt: Salt) -> bool {
self.view.is_valid_salt(salt)
}
}

impl<W> State for CachedState<W>
Expand Down Expand Up @@ -175,20 +179,14 @@ where
.commit_nonce(nonce)
}

fn cleanup_expired_nonces(
&mut self,
account_id: &AccountId,
nonces: impl IntoIterator<Item = Nonce>,
) -> Result<()> {
fn cleanup_nonce(&mut self, account_id: &AccountId, nonce: Nonce) -> Result<()> {
let account = self
.accounts
.get_mut(account_id)
.ok_or_else(|| DefuseError::AccountNotFound(account_id.clone()))?
.as_inner_unchecked_mut();

for n in nonces {
account.clear_expired_nonce(n);
}
account.cleanup_nonce(nonce);

Ok(())
}
Expand Down Expand Up @@ -417,7 +415,7 @@ impl CachedAccount {
}

#[inline]
pub fn clear_expired_nonce(&mut self, n: U256) -> bool {
self.nonces.clear_expired(n)
pub fn cleanup_nonce(&mut self, n: U256) -> bool {
self.nonces.cleanup(n)
}
}
15 changes: 8 additions & 7 deletions core/src/engine/state/deltas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
DefuseError, Nonce, Result,
DefuseError, Nonce, Result, Salt,
amounts::Amounts,
fees::Pips,
intents::{
Expand Down Expand Up @@ -96,6 +96,11 @@ where
fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountIdRef) -> bool {
self.state.is_auth_by_predecessor_id_enabled(account_id)
}

#[inline]
fn is_valid_salt(&self, salt: Salt) -> bool {
self.state.is_valid_salt(salt)
}
}

impl<S> State for Deltas<S>
Expand All @@ -118,12 +123,8 @@ where
}

#[inline]
fn cleanup_expired_nonces(
&mut self,
account_id: &AccountId,
nonces: impl IntoIterator<Item = Nonce>,
) -> Result<()> {
self.state.cleanup_expired_nonces(account_id, nonces)
fn cleanup_nonce(&mut self, account_id: &AccountId, nonce: Nonce) -> Result<()> {
self.state.cleanup_nonce(account_id, nonce)
}

fn internal_add_balance(
Expand Down
11 changes: 5 additions & 6 deletions core/src/engine/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub mod cached;
pub mod deltas;

use crate::{
Nonce, Result,
Nonce, Result, Salt,
fees::Pips,
intents::{
auth::AuthCall,
Expand Down Expand Up @@ -42,6 +42,9 @@ pub trait StateView {
/// Returns whether authentication by `PREDECESSOR_ID` is enabled.
fn is_auth_by_predecessor_id_enabled(&self, account_id: &AccountIdRef) -> bool;

/// Returns whether salt in nonce is valid
fn is_valid_salt(&self, salt: Salt) -> bool;

#[inline]
fn cached(self) -> CachedState<Self>
where
Expand All @@ -59,11 +62,7 @@ pub trait State: StateView {

fn commit_nonce(&mut self, account_id: AccountId, nonce: Nonce) -> Result<()>;

fn cleanup_expired_nonces(
&mut self,
account_id: &AccountId,
nonces: impl IntoIterator<Item = Nonce>,
) -> Result<()>;
fn cleanup_nonce(&mut self, account_id: &AccountId, nonce: Nonce) -> Result<()>;

fn internal_add_balance(
&mut self,
Expand Down
6 changes: 6 additions & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub enum DefuseError {
#[error("nonce was already expired")]
NonceExpired,

#[error("invalid nonce")]
InvalidNonce,

#[error("public key '{1}' already exists for account '{0}'")]
PublicKeyExists(AccountId, PublicKey),

Expand All @@ -66,4 +69,7 @@ pub enum DefuseError {

#[error("wrong verifying_contract")]
WrongVerifyingContract,

#[error("invalid salt")]
InvalidSalt,
}
8 changes: 7 additions & 1 deletion core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use derive_more::derive::From;
use near_sdk::{near, serde::Deserialize};

use crate::{
accounts::{AccountEvent, NonceEvent, PublicKeyEvent},
accounts::{AccountEvent, InvalidateSaltEvent, NonceEvent, PublicKeyEvent, RotateSaltEvent},
fees::{FeeChangedEvent, FeeCollectorChangedEvent},
intents::{
IntentEvent,
Expand Down Expand Up @@ -63,6 +63,12 @@ pub enum DefuseEvent<'a> {

#[event_version("0.3.0")]
SetAuthByPredecessorId(AccountEvent<'a, SetAuthByPredecessorId>),

#[event_version("0.4.0")]
RotateSalt(RotateSaltEvent),

#[event_version("0.4.0")]
InvalidateSalt(InvalidateSaltEvent),
}

pub trait DefuseIntentEmit<'a>: Into<DefuseEvent<'a>> {
Expand Down
Loading
Loading