Add more state objects.

This commit is contained in:
Michael Mikovsky
2026-05-31 14:47:25 -06:00
parent b2e2523860
commit 966f16008b
10 changed files with 200 additions and 15 deletions
Generated
+47
View File
@@ -269,6 +269,26 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "convert_case"
version = "0.10.0"
@@ -350,6 +370,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -578,6 +604,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
@@ -1757,6 +1794,15 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
@@ -1824,6 +1870,7 @@ name = "unshell"
version = "0.1.0"
dependencies = [
"chrono",
"const-random",
"crossbeam-channel",
"ratatui",
"rkyv",
+16 -5
View File
@@ -18,12 +18,14 @@ repository = "https://github.com/Astatin3/unshell"
include = ["LICENSE", "**/*.rs", "Cargo.toml"]
[workspace.dependencies]
rkyv = "0.8.16"
thiserror = "2.0.18"
chrono = "0.4.44"
static_init = "1.0.4"
rkyv = "0.8.16"
thiserror = "2.0.18"
chrono = "0.4.44"
static_init = "1.0.4"
portable-pty = "0.9.0"
crossbeam-channel = "0.5.15"
const-random = "0.1.18"
ratatui = "0.30.0"
@@ -44,7 +46,7 @@ edition.workspace = true
description = "Pure no_std implementation of the UnShell Protocol"
[features]
# default = ["interface_ratatui"]
default = ["counter_shuffle_feistel_lcg"]
log = []
log_debug = ["log", "dep:chrono"]
@@ -52,17 +54,26 @@ log_debug = ["log", "dep:chrono"]
interface = []
interface_ratatui = ["interface", "dep:ratatui"]
counter_shuffle_none = []
counter_shuffle_feistel = []
counter_shuffle_feistel_lcg = []
[dependencies]
rkyv = { workspace = true }
thiserror = { workspace = true, optional = true }
chrono = { workspace = true, optional = true }
static_init = { workspace = true }
const-random = { workspace = true }
ratatui = { workspace = true, optional = true }
[dev-dependencies]
crossbeam-channel.workspace = true
[build-dependencies]
[profile.minimize]
inherits = "release"
strip = true # Strip symbols from the binary
+67
View File
@@ -0,0 +1,67 @@
use crate::crypto::feistel_shuffle;
#[cfg(feature = "counter_shuffle_none")]
pub type Counter = NoShuffle;
#[cfg(feature = "counter_shuffle_feistel")]
pub type Counter = FeistelShuffle;
#[cfg(feature = "counter_shuffle_feistel_lcg")]
pub type Counter = FeistelLCGShuffle;
const NONCE16_1: u16 = const_random::const_random!(u16);
const NONCE16_2: u16 = const_random::const_random!(u16);
const NONCE32: u32 = const_random::const_random!(u32);
pub struct NoShuffle(u16);
/// Linear shuffle, no randomization, just a random starting point and step size
impl NoShuffle {
pub fn new() -> Self {
Self(NONCE16_1)
}
pub fn next(&mut self) -> u16 {
self.0 = self.0.wrapping_add(1);
self.0
}
}
/// Shuffle all 16 bit numbers, an actual shuffle
/// But this still stores local values in a linear format
pub struct FeistelShuffle(u16, u32);
impl FeistelShuffle {
pub fn new() -> Self {
Self(NONCE16_1, NONCE32)
}
pub fn next(&mut self) -> u16 {
self.0 = self.0.wrapping_add(NONCE16_2);
feistel_shuffle(self.0, self.1)
}
}
/// Linear recursive shuffle,
/// feeds back into itself and doesn't store the actual state.
/// Harder to decompile
pub struct FeistelLCGShuffle {
state: u16,
a: u16, // Multiplier (must be 1 mod 4)
c: u16, // Increment (must be odd)
}
impl FeistelLCGShuffle {
pub fn new() -> Self {
let seed = NONCE32;
let a = (((seed & 0x3FFF) as u16) << 2) | 1;
let c = ((seed >> 16) as u16) | 1;
Self { state: 0, a, c }
}
pub fn next(&mut self) -> u16 {
// 1. Advance state using LCG (Guarantees single cycle of 65536)
self.state = self.state.wrapping_mul(self.a).wrapping_add(self.c);
// 2. Apply Feistel shuffle to the state (Adds randomness)
feistel_shuffle(self.state, self.a as u32)
}
}
+21 -4
View File
@@ -1,10 +1,27 @@
use alloc::string::String;
mod hash;
mod ordering;
// TODO: Make this seed dependent on env var;
pub const GLOBAL_SEED: u32 = 0xDEAFBEEF;
// pub const GLOBAL_NONCE: u32 = {
// let time = match u128::from_str_radix(env!("BUILD_TIME"), 10) {
// Ok(i) => i,
// Err(_) => panic!("Failed to parse BUILD_TIME"),
// };
pub use hash::sha256;
pub use ordering::feistel_shuffle;
// GLOBAL_SEED ^ (time as u32)
// };
mod feistel;
#[allow(dead_code)]
mod feistel_state;
mod sha256;
pub use feistel::feistel_shuffle;
pub use feistel_state::{Counter, FeistelLCGShuffle, FeistelShuffle, NoShuffle};
pub use sha256::sha256;
#[cfg(test)]
mod tests;
#[macro_export]
macro_rules! hash_256 {
+40
View File
@@ -0,0 +1,40 @@
use crate::crypto::{FeistelLCGShuffle, FeistelShuffle, NoShuffle};
#[test]
fn test_linear_shuffle() {
let mut seen = [false; 65536];
let mut counter = NoShuffle::new();
for _ in 0..65535 {
let val = counter.next();
assert!(!seen[val as usize], "Collision detected");
seen[val as usize] = true;
}
}
#[test]
fn test_feistel_shuffle() {
let mut seen = [false; 65536];
let mut counter = FeistelShuffle::new();
for _ in 0..65535 {
let val = counter.next();
assert!(!seen[val as usize], "Collision detected");
seen[val as usize] = true;
}
}
#[test]
fn test_fristel_lcg_shuffle() {
let mut seen = [false; 65536];
let mut counter = FeistelLCGShuffle::new();
for _ in 0..65535 {
let val = counter.next();
assert!(!seen[val as usize], "Collision detected");
seen[val as usize] = true;
}
}
+3 -2
View File
@@ -16,10 +16,11 @@ impl Endpoint {
/// reuse an id before the previous route has closed. If every `u16` id is active
/// the function panics; that is a hard local resource exhaustion condition, not a
/// recoverable packet error.
///
/// TODO: Reevaluate this method of allocation checking. It can be quite slow
pub fn allocate_hook_id(&mut self) -> HookID {
for _ in 0..=HookID::MAX {
let candidate = self.last_hook;
self.last_hook = self.last_hook.wrapping_add(1);
let candidate = self.last_hook.next();
if !self.hooks.contains_key(&candidate) {
return candidate;
+6 -4
View File
@@ -5,15 +5,17 @@ pub use hooks::HookID;
use alloc::{boxed::Box, vec::Vec};
use crate::protocol::{ConnectionSet, HookMap, Leaf, Packet, Path, RouteMap};
use crate::{
crypto::Counter,
protocol::{ConnectionSet, HookMap, Leaf, Packet, Path, RouteMap},
};
pub struct Endpoint {
// This endpoint's identifier
pub id: u32,
// A counter that creates unique hook IDs.
// TODO: Randomize the hooks for more obfuscation
pub(crate) last_hook: u16,
pub(crate) last_hook: Counter,
// Absolute path for this node. Must be set by some leaf
pub path: Path,
@@ -36,7 +38,7 @@ impl Endpoint {
Self {
id,
// Init the hook at 0, which will increment
last_hook: 0,
last_hook: Counter::new(),
// Set the current path as an empty vec
path: Vec::new(),