Skip to content

Commit f091807

Browse files
authored
Merge pull request #1 from SolarRepublic/undersaturated
dev: handle undersaturated dwb
2 parents be5d6e9 + e556e98 commit f091807

File tree

2 files changed

+53
-39
lines changed

2 files changed

+53
-39
lines changed

src/contract.rs

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore};
99
use secret_toolkit_crypto::{sha_256, ContractPrng};
1010

1111
use crate::batch;
12-
use crate::dwb::{random_in_range, DelayedWriteBuffer, ACCOUNT_TXS, DWB, DWB_LEN, TX_NODES};
12+
use crate::dwb::{random_in_range, DelayedWriteBuffer, DWB, DWB_LEN, DWB_MAX_TX_EVENTS, TX_NODES};
1313
use crate::msg::{
1414
AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer,
1515
ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success,
@@ -1910,56 +1910,57 @@ fn perform_transfer(
19101910
new_entry.add_tx_node(store, tx_id)?;
19111911
new_entry.add_amount(amount)?;
19121912

1913-
// if recipient is in the buffer (non-zero index), set this value to 1, otherwise 0, in constant-time
1914-
// casting to isize will never overflow, so long as dwb length is limited to a u16 value
1915-
let recipient_in_buffer = (((recipient_index as isize | -(recipient_index as isize)) >> 31) & 1) as usize;
19161913

1917-
if dwb.saturated() {
1918-
// randomly pick an entry to exclude in case the recipient is not in the buffer
1919-
let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize;
1920-
1921-
// index of entry to exclude from selection
1922-
let exclude_index = (recipient_index as usize * recipient_in_buffer) + (random_exclude_index * (1 - recipient_in_buffer));
1914+
// whether or not recipient is in the buffer (non-zero index)
1915+
// casting to i32 will never overflow, so long as dwb length is limited to a u16 value
1916+
let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32);
19231917

1924-
// randomly select any other entry to settle in constant-time (avoiding the reserved 0th position)
1925-
let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize;
1918+
// randomly pick an entry to exclude in case the recipient is not in the buffer
1919+
let random_exclude_index = random_in_range(rng, 1, DWB_LEN as u32)? as usize;
19261920

1927-
// check if we have any open slots in the linked list
1928-
let open_slots = (u16::MAX - dwb.entries[recipient_index].list_len()?) as i32;
1929-
let list_can_grow = (((open_slots | -open_slots) >> 31) & 1) as usize;
1921+
// index of entry to exclude from selection
1922+
let exclude_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, random_exclude_index);
19301923

1931-
// if we would overflow the list, just settle recipient
1932-
// TODO: see docs for attack analysis
1933-
let actual_settle_index = (random_settle_index as usize * list_can_grow) + (recipient_index * (1 - list_can_grow));
1924+
// randomly select any other entry to settle in constant-time (avoiding the reserved 0th position)
1925+
let random_settle_index = (((random_in_range(rng, 0, DWB_LEN as u32 - 2)? + exclude_index as u32) % (DWB_LEN as u32 - 1)) + 1) as usize;
19341926

1935-
// settle the entry
1936-
dwb.settle_entry(store, actual_settle_index)?;
19371927

1938-
// replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer
1939-
let replacement_entry = dwb.unique_random_entry(rng)?;
1940-
dwb.entries[actual_settle_index] = replacement_entry;
1928+
// whether or not the buffer is fully saturated yet
1929+
let if_undersaturated = constant_time_is_not_zero(dwb.empty_space_counter as i32);
19411930

1942-
// pick the index to where the recipient's entry should be written
1943-
let write_index = (recipient_index * recipient_in_buffer) + (actual_settle_index * (1 - recipient_in_buffer));
1931+
// find the next empty entry in the buffer
1932+
let next_empty_index = (DWB_LEN - dwb.empty_space_counter) as usize;
19441933

1945-
// either updates the existing recipient entry, or overwrites the random replacement entry in the settled index
1946-
dwb.entries[write_index] = new_entry;
1947-
} else {
1948-
// TODO: revisit contract warm up with other options, e.g saturating with random address from beginning
1934+
// if buffer is not yet saturated, settle the address at the next empty index
1935+
let bounded_settle_index = constant_time_if_else(if_undersaturated, next_empty_index, random_settle_index);
19491936

1950-
// find the next empty entry in the buffer
1951-
let next_index = (DWB_LEN - dwb.empty_space_counter) as usize;
19521937

1953-
// pick the index to where the recipient's entry should be written
1954-
let write_index = (recipient_index * recipient_in_buffer) + (next_index * (1 - recipient_in_buffer));
1938+
// check if we have any open slots in the linked list
1939+
let if_list_can_grow = constant_time_is_not_zero((DWB_MAX_TX_EVENTS - dwb.entries[recipient_index].list_len()?) as i32);
19551940

1956-
// either updates the existing recipient entry, or write the entry to the next index value
1957-
dwb.entries[write_index] = new_entry;
1941+
// if we would overflow the list, just settle recipient
1942+
// TODO: see docs for attack analysis
1943+
let actual_settle_index = constant_time_if_else(if_list_can_grow, bounded_settle_index, recipient_index);
19581944

1959-
// decrement empty space counter if receipient is not already in buffer
1960-
let empty_space_counter_delta = (1 - recipient_in_buffer) as u16;
1961-
dwb.empty_space_counter -= empty_space_counter_delta;
1962-
}
1945+
// settle the entry
1946+
dwb.settle_entry(store, actual_settle_index)?;
1947+
1948+
// replace it with a randomly generated address (that is not currently in the buffer) and 0 amount and nil events pointer
1949+
let replacement_entry = dwb.unique_random_entry(rng)?;
1950+
dwb.entries[actual_settle_index] = replacement_entry;
1951+
1952+
// pick the index to where the recipient's entry should be written
1953+
let write_index = constant_time_if_else(if_recipient_in_buffer, recipient_index, actual_settle_index);
1954+
1955+
// either updates the existing recipient entry, or overwrites the random replacement entry in the settled index
1956+
dwb.entries[write_index] = new_entry;
1957+
1958+
// decrement empty space counter if it is undersaturated and the recipient was not already in the buffer
1959+
dwb.empty_space_counter -= constant_time_if_else(
1960+
if_undersaturated,
1961+
constant_time_if_else(if_recipient_in_buffer, 0usize, 1usize),
1962+
0usize
1963+
) as u16;
19631964

19641965
DWB.save(store, &dwb)?;
19651966

@@ -1999,6 +2000,16 @@ fn is_valid_symbol(symbol: &str) -> bool {
19992000
len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_alphabetic())
20002001
}
20012002

2003+
#[inline]
2004+
fn constant_time_is_not_zero(value: i32) -> u32 {
2005+
return (((value | -value) >> 31) & 1) as u32;
2006+
}
2007+
2008+
#[inline]
2009+
fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize {
2010+
return (then * condition as usize) | (els * (1 - condition as usize));
2011+
}
2012+
20022013
// pub fn migrate(
20032014
// _deps: DepsMut,
20042015
// _env: Env,

src/dwb.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ pub const IMPOSSIBLE_ADDR: [u8; 20] = [
3838
// minimum allowable size: 3
3939
pub const DWB_LEN: u16 = 65;
4040

41+
// maximum number of tx events allowed in an entry's linked list
42+
pub const DWB_MAX_TX_EVENTS: u16 = u16::MAX;
43+
4144
#[derive(Serialize, Deserialize, Debug)]
4245
pub struct DelayedWriteBuffer {
4346
pub empty_space_counter: u16,

0 commit comments

Comments
 (0)