├── .github └── workflows │ └── deploy.yaml ├── .gitignore ├── Cargo.toml ├── readme.md ├── src ├── congruence_solver.rs ├── expr.rs ├── lib.rs ├── matrix.rs ├── numbers.rs ├── pages │ ├── linear_congruences.rs │ ├── mod.rs │ ├── obfuscate.rs │ └── perm_poly.rs ├── polynomial.rs ├── printer.rs ├── uniform_expr.rs └── vector.rs ├── test.sh └── www ├── index.html ├── linear_congruences.html ├── linear_congruences.js ├── linear_mba.html ├── linear_mba.js ├── mathjax.js ├── obfuscate.js ├── perm_poly.html ├── perm_poly.js ├── prism.css └── wasm.js /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build and deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Install Rust 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: stable 20 | 21 | - name: Install wasm-pack 22 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 23 | 24 | - name: Build Rust to WASM 25 | run: wasm-pack build --target web 26 | 27 | - name: Copy WASM files to www folder 28 | run: cp ./pkg/mba_wasm.js ./pkg/mba_wasm_bg.wasm ./www/ 29 | 30 | - name: Deploy to Github Pages 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./www/ 35 | publish_branch: pages -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /local -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mba-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | wasm-bindgen = "0.2" 11 | web-sys = { version = "0.3", features = ["Window", "Performance"] } 12 | js-sys = "0.3" 13 | num-traits = "0.2" 14 | rand = "0.8" 15 | getrandom = { version = "0.2", features = ["js"] } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mixed Boolean-Arithmetic Obfuscation 2 | The algorithm transforms expressions like `x+y` into things like this: 3 | ```c 4 | -38*(x & y) - 83*(x ^ y) - 64*~(x ^ (y ^ z)) - 41*~x - 43*~y - 23*y - 44*z - 20*(y & z) - 21*(x | z) - 107*(~x & z) - 108*(y | ~z) 5 | ``` 6 | These kind of expressions involving both normal arithmetic as well as boolean operations are known as mixed boolean-arithmetic expressions. 7 | This particular transformation is only valid when `x` and `y` (and `z`) are 8-bit integers and the usual rules of computer arithmetic apply (e.g. when adding/multiplying numbers and there is an overflow then the most significant bits that can not be represented are cut off). 8 | In particular this will not work when the numbers are floating point numbers. 9 | Rust itself will panic (at least in debug builds) when addition/multiplication overflows so in order to use this with rust you will have to use the [Wrapping](https://doc.rust-lang.org/std/num/struct.Wrapping.html) types. 10 | 11 | ### Usage 12 | There is a [web interface](https://plzin.github.io/mba-wasm/). 13 | 14 | Generating linear MBA expressions involves solving systems of linear congruences 15 | which can be done [here](https://plzin.github.io/mba-wasm/linear_congruences.html). 16 | This was mostly used during debugging but hopefully someone can find use for this. 17 | 18 | ### Original implementation 19 | The main implementation with more features can be found [here](https://github.com/plzin/mba). 20 | This is the WASM port that doesn't use the rug crate for arbitrary precision integers 21 | and solves linear systems mod n by [diagonalization](https://plzin.github.io/posts/linear-systems-mod-n). 22 | 23 | ### How it works 24 | If you want to understand the algorithm, check out my [blog post](https://plzin.github.io/posts/mba) about it. 25 | The algorithm is implemented in Rust and compiles to WebAssembly, that will be run in your browser. 26 | 27 | ### To Do 28 | Currently only linear MBA expressions are implemented. 29 | The permutation polynomial part is missing. -------------------------------------------------------------------------------- /src/congruence_solver.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use crate::matrix::Matrix; 3 | use crate::vector::Vector; 4 | use crate::numbers::UnsignedInt; 5 | 6 | pub struct AffineLattice { 7 | pub offset: Vector, 8 | pub basis: Vec>, 9 | } 10 | 11 | impl AffineLattice { 12 | pub fn empty() -> Self { 13 | Self { 14 | offset: Vector::empty(), 15 | basis: Vec::new(), 16 | } 17 | } 18 | 19 | pub fn is_empty(&self) -> bool { 20 | self.offset.is_empty() 21 | } 22 | } 23 | 24 | impl AffineLattice { 25 | pub fn to_tex(&self) -> String { 26 | let mut s = self.offset.to_tex(); 27 | for (i, b) in self.basis.iter().enumerate() { 28 | s += &std::format!("+a_{}{}", i + 1, b.to_tex()); 29 | } 30 | s 31 | } 32 | 33 | pub fn to_tex_brace(&self) -> String { 34 | if self.basis.is_empty() { 35 | self.to_tex() 36 | } else { 37 | format!("\\left({}\\right)", self.to_tex()) 38 | } 39 | } 40 | } 41 | 42 | 43 | /// Solves ax=b mod n where n = 2^8 for u8. 44 | /// Returns None if there is no solution. 45 | /// Otherwise returns all solutions in the form (c, d) 46 | /// where c+di are all solutions. 47 | pub fn solve_scalar_congruence( 48 | a: T, b: T 49 | ) -> Option<(T, T)> { 50 | // Handle the case that a is zero, so we don't have to think about it. 51 | if a == T::zero() { 52 | return (b == T::zero()).then_some((T::zero(), T::one())); 53 | } 54 | 55 | // We are basically going to use the extended euclidean algorithm on 56 | // the diophantine equation ax+ny=b where n is the number of values 57 | // (2^8 for u8). 58 | // But n doesn't fit into T, so we have to do a hack in the first step. 59 | // Usually we'd divide n by a but instead we divide n-a by a and add 1. 60 | // This makes the code structurally uglier, but otherwise I'm pretty 61 | // much just following the pseudo code on wikipedia. 62 | 63 | let (mut old_r, mut r) = (T::zero(), a); 64 | let (mut old_t, mut t) = (T::zero(), T::one()); 65 | let mut q = (T::zero() - a) / a + T::one(); 66 | 67 | loop { 68 | (old_r, r) = (r, old_r - q * r); 69 | (old_t, t) = (t, old_t - q * t); 70 | if r == T::zero() { 71 | break; 72 | } 73 | q = old_r / r; 74 | } 75 | 76 | // old_r is gcd(a, n). 77 | let gcd = old_r; 78 | 79 | // There is a solution iff gcd divides b, but we can also just check ax=b. 80 | // old_t is the Bezout coefficient: a*old_t=gcd(a, n) mod n. 81 | let x = b / gcd * old_t; 82 | if a * x != b { 83 | return None; 84 | } 85 | 86 | // The kernel is n / gcd which happens to be in t. 87 | // If the kernel is greater than n/2 we can take -t 88 | // which is smaller. 89 | let kern = std::cmp::min(t, T::zero() - t); 90 | 91 | Some((x, t)) 92 | } 93 | 94 | /// Solves a system of linear congruences Ax=b. 95 | pub fn solve_congruences( 96 | mut a: Matrix, b: &Vector 97 | ) -> AffineLattice { 98 | debug_assert!(a.rows == b.dim, "Invalid system of congruences"); 99 | 100 | // Diagonalize the system. 101 | let (s, t) = diagonalize(&mut a); 102 | 103 | // Transform the vector b. 104 | // We could already do this in diagonalize if we really wanted. 105 | let b = (&s * b); 106 | 107 | // If there is a non-zero entry in b at index >a.min_dim() 108 | // then the system has no solution, since the corresponding 109 | // row in a is zero, so we are solving 0=x. 110 | if b.iter().skip(a.min_dim()).any(|e| *e != T::zero()) { 111 | return AffineLattice::empty(); 112 | } 113 | 114 | // Some solution to the system. 115 | let mut offset = Vector::zero(a.cols); 116 | 117 | // The basis of the kernel. 118 | let mut basis = Vec::new(); 119 | 120 | // Solve the scalar linear congruences. 121 | for i in 0..a.min_dim() { 122 | let (x, kern) = match solve_scalar_congruence(a[(i, i)], b[i]) { 123 | // If there is no solution, 124 | // then the whole system does not have a solution. 125 | None => return AffineLattice::empty(), 126 | Some(s) => s, 127 | }; 128 | 129 | // The particular solution is an entry is 130 | // the particular solution of the whole system. 131 | offset[i] = x; 132 | 133 | // If the kernel is zero, then the vector is zero for sure. 134 | if kern != T::zero() { 135 | let mut v = Vector::zero(a.cols); 136 | v[i] = kern; 137 | basis.push(v); 138 | } 139 | } 140 | 141 | // If there are more variables then equations 142 | // then there are no restrictions on the variables 143 | // from index d.rows 144 | for i in a.rows..a.cols { 145 | let mut v = Vector::zero(a.cols); 146 | v[i] = T::one(); 147 | basis.push(v); 148 | } 149 | 150 | offset = (&t * &offset); 151 | for v in &mut basis { 152 | *v = (&t * &*v); 153 | } 154 | 155 | AffineLattice { 156 | offset, 157 | basis 158 | } 159 | } 160 | 161 | /// Computes a diagonal matrix D in-place 162 | /// and returns matrices (S, T), such that D=SAT. 163 | pub fn diagonalize( 164 | a: &mut Matrix 165 | ) -> (Matrix, Matrix) { 166 | // The matrices S and T are initialized to the identity. 167 | // S/T keeps track of the row/column operations. 168 | let mut s = Matrix::::id(a.rows); 169 | let mut t = Matrix::::id(a.cols); 170 | 171 | for i in 0..a.min_dim() { 172 | // 173 | // Eliminate row i and column i. 174 | // 175 | loop { 176 | // Is there a non-zero element in the column? 177 | let col_zero = a.col(i) 178 | .skip(i+1) 179 | .all(|e| *e == T::zero()); 180 | 181 | if (!col_zero) { 182 | // 183 | // Eliminate the column. 184 | // 185 | 186 | // Find a pivot in the column. 187 | let pivot = a.col(i) 188 | .enumerate() 189 | .skip(i) 190 | .filter(|e| *e.1 != T::zero()) 191 | .min_by_key(|e| e.1) 192 | .map(|e| e.0) 193 | .unwrap(); // We know there is a non-zero element. 194 | 195 | // Move the pivot to the beginning. 196 | a.swap_rows(i, pivot); 197 | s.swap_rows(i, pivot); 198 | 199 | // Try to eliminate every other entry in the column. 200 | for k in i+1..a.rows { 201 | if a[(k, i)] != T::zero() { 202 | let m = T::zero() - (a[(k, i)] / a[(i, i)]); 203 | a.row_multiply_add(i, k, m); 204 | s.row_multiply_add(i, k, m); 205 | } 206 | } 207 | 208 | // Keep eliminating the column. 209 | continue; 210 | } 211 | 212 | // If we get here, the column is zero. 213 | 214 | // Is there a non-zero element in the row? 215 | let row_zero = a.row(i) 216 | .iter() 217 | .skip(i+1) 218 | .all(|e| *e == T::zero()); 219 | 220 | // If the row is zero, then continue with the next row/column. 221 | if row_zero { 222 | break; 223 | } 224 | 225 | // 226 | // Eliminate the row. 227 | // 228 | 229 | // Find a pivot in the row. 230 | let pivot = a.row(i) 231 | .iter() 232 | .enumerate() 233 | .skip(i) 234 | .filter(|e| *e.1 != T::zero()) 235 | .min_by_key(|e| e.1) 236 | .map(|e| e.0) 237 | .unwrap(); // We know there is a non-zero element. 238 | 239 | // Move the pivot to the beginning. 240 | a.swap_columns(i, pivot); 241 | t.swap_columns(i, pivot); 242 | 243 | // Try to eliminate every other entry in the row. 244 | for k in i+1..a.cols { 245 | if a[(i, k)] != T::zero() { 246 | let m = T::zero() - (a[(i, k)] / a[(i, i)]); 247 | a.col_multiply_add(i, k, m); 248 | t.col_multiply_add(i, k, m); 249 | } 250 | } 251 | } 252 | } 253 | 254 | return (s, t); 255 | } 256 | 257 | /// Solves ax=b mod n. 258 | /// Returns None if there is no solution. 259 | /// Otherwise returns all solutions in the form (c, d) 260 | /// where c+di are all solutions. 261 | pub fn solve_scalar_congruence_mod( 262 | a: T, b: T, n: T 263 | ) -> Option<(T, T)> { 264 | assert!(!n.is_zero()); 265 | // Handle the case that a is zero, so we don't have to think about it. 266 | if a == T::zero() { 267 | return (b == T::zero()).then_some((T::zero(), T::one())); 268 | } 269 | 270 | let (mut old_r, mut r) = (a, n); 271 | let (mut old_t, mut t) = (T::zero(), T::one()); 272 | while !r.is_zero() { 273 | let q = old_r / r; 274 | (old_r, r) = (r, old_r - q * r); 275 | (old_t, t) = (t, old_t - q * t); 276 | } 277 | 278 | // old_r is gcd(a, n). 279 | let gcd = old_r; 280 | 281 | // There is a solution iff gcd divides b, but we can also just check ax=b. 282 | // old_t is the Bezout coefficient: a*old_t=gcd(a, n) mod n. 283 | let x = b / gcd * old_t; 284 | if a * x != b { 285 | return None; 286 | } 287 | 288 | // The kernel is n / gcd which happens to be in t. 289 | // If the kernel is greater than n/2 we can take -t 290 | // which is smaller. 291 | let kern = std::cmp::min(t, T::zero() - t); 292 | 293 | Some((x, t)) 294 | } -------------------------------------------------------------------------------- /src/expr.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::fmt::Write; 3 | use std::collections::BTreeSet; 4 | use crate::{numbers::{UnsignedInt, int_from_it}, printer::Printer}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub enum Expr { 8 | Const(T), 9 | Var(String), 10 | 11 | // Arithmetic 12 | Add(Rc>, Rc>), 13 | Sub(Rc>, Rc>), 14 | Mul(Rc>, Rc>), 15 | Div(Rc>, Rc>), 16 | Mod(Rc>, Rc>), 17 | Neg(Rc>), 18 | 19 | // Boolean 20 | And(Rc>, Rc>), 21 | Or(Rc>, Rc>), 22 | Xor(Rc>, Rc>), 23 | Shl(Rc>, Rc>), 24 | Shr(Rc>, Rc>), 25 | Not(Rc>), 26 | } 27 | 28 | impl Expr { 29 | /// Returns the zero constant. 30 | pub fn zero() -> Self { 31 | Self::Const(T::zero()) 32 | } 33 | 34 | /// Returns all variables in the expression. 35 | pub fn vars(&self) -> Vec { 36 | let mut v = BTreeSet::new(); 37 | self.vars_impl(&mut v); 38 | v.into_iter().collect() 39 | } 40 | 41 | fn vars_impl(&self, v: &mut BTreeSet) { 42 | match self { 43 | Expr::Const(_) => {}, 44 | Expr::Var(name) => drop(v.insert(name.clone())), 45 | Expr::Neg(e) | Expr::Not(e) => e.vars_impl(v), 46 | 47 | Expr::Add(l, r) | Expr::Sub(l, r) | Expr::Mul(l, r) 48 | | Expr::Div(l, r) | Expr::Mod(l, r) | Expr::And(l, r) 49 | | Expr::Or(l, r) | Expr::Xor(l, r) | Expr::Shl(l, r) 50 | | Expr::Shr(l, r) => { 51 | l.vars_impl(v); 52 | r.vars_impl(v); 53 | } 54 | } 55 | } 56 | 57 | /// Substitute and expression for a variable. 58 | pub fn substitute(&mut self, e: &mut Rc>, var: &str) { 59 | let mut visited = Vec::new(); 60 | match self { 61 | Expr::Const(_) => {}, 62 | Expr::Var(v) => if v == var { *self = e.as_ref().clone() }, 63 | Expr::Neg(i) | Expr::Not(i) => Self::substitute_impl(i, var, e, &mut visited), 64 | Expr::Add(l, r) | Expr::Sub(l, r) | Expr::Mul(l, r) 65 | | Expr::Div(l, r) | Expr::Mod(l, r) | Expr::And(l, r) 66 | | Expr::Or(l, r) | Expr::Xor(l, r) | Expr::Shl(l, r) 67 | | Expr::Shr(l, r) => { 68 | Self::substitute_impl(l, var, e, &mut visited); 69 | Self::substitute_impl(r, var, e, &mut visited); 70 | }, 71 | } 72 | } 73 | 74 | fn substitute_impl( 75 | this: &mut Rc>, var: &str, 76 | e: &mut Rc>, visited: &mut Vec<*const Expr> 77 | ) { 78 | let ptr = Rc::as_ptr(this); 79 | let recurse = if visited.contains(&ptr) { 80 | false 81 | } else { 82 | visited.push(ptr); 83 | true 84 | }; 85 | 86 | use Expr::*; 87 | // SAFETY: This is okay because we make sure with extra logic 88 | // that this is never encountered twice. 89 | match unsafe { &mut *(ptr as *mut _) } { 90 | Const(_) => {}, 91 | Var(v) => if v == var { *this = e.clone() }, 92 | Add(l, r) | Sub(l, r) | Mul(l, r) | Div(l, r) | Mod(l, r) 93 | | And(l, r) | Or(l, r) | Xor(l, r) | Shl(l, r) 94 | | Shr(l, r) => if recurse { 95 | Self::substitute_impl(l, var, e, visited); 96 | Self::substitute_impl(r, var, e, visited); 97 | }, 98 | Neg(i) | Not(i) => if recurse { 99 | Self::substitute_impl(i, var, e, visited) 100 | }, 101 | } 102 | } 103 | 104 | /// Returns the precedence of a binary operator. 105 | /// All operators are treated as being left associative. 106 | fn precedence(&self) -> usize { 107 | use Expr::*; 108 | match self { 109 | Or(_, _) => 1, 110 | Xor(_, _) => 2, 111 | And(_, _) => 3, 112 | Shl(_, _) | Shr(_, _) => 4, 113 | Add(_, _) | Sub(_, _) => 5, 114 | Mul(_, _) | Div(_, _) | Mod(_, _) => 6, 115 | Neg(_) | Not(_) => 255, 116 | Const(_) | Var(_) => 256, 117 | } 118 | } 119 | 120 | /// Parse an expression from a string. 121 | /// Can't parse shifts at the moment. 122 | /// Closing brackets are a bit broken. 123 | pub fn from_string(s: U) -> Result, String> { 124 | let mut s = s.to_string(); 125 | s.retain(|c| !c.is_whitespace()); 126 | let mut it = s.chars().peekable(); 127 | 128 | Self::parse(&mut it, 0) 129 | } 130 | 131 | // pre 0: parse as much as possible 132 | // ... 133 | // pre 15: parse as little as possible 134 | fn parse( 135 | it: &mut std::iter::Peekable, 136 | pre: usize 137 | ) -> Result { 138 | use Expr::*; 139 | 140 | let mut c = *it.peek() 141 | .ok_or_else(|| "Unexpected end of input".to_owned())?; 142 | 143 | let mut e = if c == '(' { 144 | it.next(); 145 | let e = Self::parse(it, 0)?; 146 | match it.next() { 147 | Some(')') => e, 148 | _ => return Err("Closing bracket missing".into()), 149 | } 150 | } else if c == '~' { 151 | it.next(); 152 | let e = Self::parse(it, 15)?; 153 | Not(Rc::new(e)) 154 | } else if c == '-' { 155 | it.next(); 156 | let e = Self::parse(it, 15)?; 157 | Neg(Rc::new(e)) 158 | } else if c.is_alphabetic() { 159 | it.next(); 160 | let mut var = String::from(c); 161 | loop { 162 | let Some(c) = it.peek() else { 163 | break 164 | }; 165 | 166 | if !c.is_alphanumeric() { 167 | break 168 | } 169 | 170 | var.push(*c); 171 | it.next(); 172 | } 173 | 174 | Var(var) 175 | } else if c.is_ascii_digit() { 176 | // This can't panic because we check that 177 | // the character is an ascii digit. 178 | let num = int_from_it(it).unwrap(); 179 | Const(num) 180 | } else { 181 | return Err("Unrecognized character".into()); 182 | }; 183 | 184 | loop { 185 | let Some(c) = it.peek().cloned() else { 186 | return Ok(e) 187 | }; 188 | 189 | let op_pre = match c { 190 | '|' => 1, 191 | '^' => 2, 192 | '&' => 3, 193 | '+' | '-' => 5, 194 | '*' | '/' | '%' => 6, 195 | ')' => return Ok(e), 196 | _ => return Err("Unknown operator".into()), 197 | }; 198 | 199 | if op_pre <= pre { 200 | return Ok(e); 201 | } 202 | 203 | // If the current operators precedence is higher than 204 | // the one whose subexpression we are currently parsing 205 | // then we need to finish this operator first. 206 | it.next(); 207 | let rhs = Rc::new(Self::parse(it, op_pre)?); 208 | let lhs = Rc::new(e); 209 | e = match c { 210 | '+' => Add(lhs, rhs), 211 | '-' => Sub(lhs, rhs), 212 | '*' => Mul(lhs, rhs), 213 | '/' => Div(lhs, rhs), 214 | '%' => Mod(lhs, rhs), 215 | '&' => And(lhs, rhs), 216 | '|' => Or(lhs, rhs), 217 | '^' => Xor(lhs, rhs), 218 | c => panic!("Unknown operator: {c}"), 219 | }; 220 | }; 221 | } 222 | 223 | /// Prints the expression while avoiding reprinting 224 | /// common subexpressions by assigning them to variables. 225 | /// This only works if the Rc's used in the expression 226 | /// are not shared with other expressions. 227 | pub fn print_as_fn(&self, printer: Printer) -> String { 228 | assert!(printer != Printer::Tex, 229 | "Tex printing is not supported for general expressions."); 230 | 231 | let mut input = self.vars(); 232 | // This shouldn't really be done here, 233 | // but I can't be bothered. 234 | input.sort_by(|l, r| { 235 | if l.starts_with("aux") { 236 | if r.starts_with("aux") { 237 | l.cmp(r) 238 | } else { 239 | std::cmp::Ordering::Greater 240 | } 241 | } else if r.starts_with("aux") { 242 | std::cmp::Ordering::Less 243 | } else { 244 | l.cmp(r) 245 | } 246 | }); 247 | 248 | // Stores a mapping of (sub)expressions to variables. 249 | let mut vars = Vec::new(); 250 | 251 | let l = self.print_simple_impl(&mut vars, printer); 252 | 253 | let mut s = String::new(); 254 | if printer == Printer::Default { 255 | for (_, var, init) in vars.iter().rev() { 256 | write!(&mut s, "{} = {}", var, init); 257 | } 258 | } else if printer == Printer::C { 259 | let ty = match std::mem::size_of::() { 260 | 1 => "uint8_t", 261 | 2 => "uint16_t", 262 | 4 => "uint32_t", 263 | 8 => "uint64_t", 264 | 16 => "uint128_t", 265 | _ => panic!("Unknown type."), 266 | }; 267 | 268 | write!(&mut s, "{} f(", ty); 269 | for v in &input { 270 | write!(&mut s, "{} {}, ", ty, v); 271 | } 272 | s.pop(); 273 | s.pop(); 274 | write!(&mut s, ") {{\n"); 275 | 276 | for (_, var, init) in vars.iter().rev() { 277 | write!(&mut s, "\t{} {} = {};\n", ty, var, init); 278 | } 279 | 280 | write!(&mut s, "\treturn {};\n}}", &l); 281 | } else if printer == Printer::Rust { 282 | let ty = match std::mem::size_of::() { 283 | 1 => "Wrapping", 284 | 2 => "Wrapping", 285 | 4 => "Wrapping", 286 | 8 => "Wrapping", 287 | 16 => "Wrapping", 288 | _ => panic!("Unknown type."), 289 | }; 290 | 291 | write!(&mut s, "fn f("); 292 | for v in &input { 293 | write!(&mut s, "{}: {}, ", v, ty); 294 | } 295 | s.pop(); 296 | s.pop(); 297 | write!(&mut s, ") -> {} {{\n", ty); 298 | 299 | for (_, var, init) in vars.iter().rev() { 300 | write!(&mut s, "\tlet {} = {};\n", var, init); 301 | } 302 | 303 | write!(&mut s, "\t{}\n}}", &l); 304 | } else { 305 | assert!(false, "Unsupported printer."); 306 | } 307 | 308 | s 309 | } 310 | 311 | fn print_simple_rc( 312 | e: &Rc, 313 | vars: &mut Vec<(*const Self, String, String)>, 314 | printer: Printer 315 | ) -> String { 316 | // If there is only one reference then just print it. 317 | if Rc::strong_count(e) == 1 { 318 | return e.print_simple_impl(vars, printer); 319 | } 320 | 321 | // We don't want to assign a variable to a variable 322 | // so there is this shortcut here. 323 | if let Expr::Var(v) = &**e { 324 | return format!("{}", *v); 325 | } 326 | 327 | let ptr = Rc::as_ptr(e); 328 | 329 | // If the expression already has a variable then just print the variable. 330 | let var = vars.iter().find(|t| t.0 == ptr); 331 | if let Some(v) = var { 332 | v.1.clone() 333 | } else { 334 | let v = format!("var{}", vars.len()); 335 | 336 | // Push everything. 337 | vars.push((ptr, v.clone(), String::new())); 338 | 339 | let idx = vars.len() - 1; 340 | 341 | // Get the initializer for the variable. 342 | vars[idx].2 = e.print_simple_impl(vars, printer); 343 | 344 | // Return just the variable name. 345 | v 346 | } 347 | } 348 | 349 | // Yes, this PERFORMANCE CRITICAL code could be more efficient... 350 | fn print_simple_impl( 351 | &self, 352 | vars: &mut Vec<(*const Self, String, String)>, 353 | printer: Printer 354 | ) -> String { 355 | // Print a binary operation. 356 | let bin_op = | 357 | op: &str, l: &Rc, r: &Rc, 358 | vars: &mut Vec<(*const Self, String, String)> 359 | | { 360 | let pred = self.precedence(); 361 | 362 | let l = if pred > l.precedence() && Rc::strong_count(l) == 1 { 363 | format!("({})", Self::print_simple_rc(l, vars, printer)) 364 | } else { 365 | format!("{}", Self::print_simple_rc(l, vars, printer)) 366 | }; 367 | 368 | let r = if pred > r.precedence() && Rc::strong_count(r) == 1 { 369 | format!("({})", Self::print_simple_rc(r, vars, printer)) 370 | } else { 371 | format!("{}", Self::print_simple_rc(r, vars, printer)) 372 | }; 373 | 374 | format!("{} {} {}", l, op, r) 375 | }; 376 | 377 | // Print a unary operation. 378 | let un_op = | 379 | op: &str, i: &Rc, 380 | vars: &mut Vec<(*const Self, String, String)> 381 | | { 382 | if self.precedence() > i.precedence() && Rc::strong_count(i) == 1 { 383 | format!("{}({})", op, Self::print_simple_rc(i, vars, printer)) 384 | } else { 385 | format!("{}{}", op, Self::print_simple_rc(i, vars, printer)) 386 | } 387 | }; 388 | 389 | use Expr::*; 390 | match self { 391 | Const(i) if printer == Printer::Rust => format!("Wrapping({})", i), 392 | Const(i) => format!("{}", i), 393 | Var(n) => format!("{}", n), 394 | Add(l, r) => bin_op("+", l, r, vars), 395 | Sub(l, r) => bin_op("-", l, r, vars), 396 | Mul(l, r) => bin_op("*", l, r, vars), 397 | Div(l, r) => bin_op("/", l, r, vars), 398 | Mod(l, r) => bin_op("%", l, r, vars), 399 | Neg(i) => un_op("-", i, vars), 400 | And(l, r) => bin_op("&", l, r, vars), 401 | Or(l, r) => bin_op("|", l, r, vars), 402 | Xor(l, r) => bin_op("^", l, r, vars), 403 | Shl(l, r) => bin_op("<<", l, r, vars), 404 | Shr(l, r) => bin_op(">>", l, r, vars), 405 | Not(i) if printer == Printer::Rust => un_op("!", i, vars), 406 | Not(i) => un_op("~", i, vars), 407 | } 408 | } 409 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | mod vector; 4 | mod matrix; 5 | mod numbers; 6 | mod polynomial; 7 | mod congruence_solver; 8 | mod expr; 9 | mod uniform_expr; 10 | mod printer; 11 | mod pages; 12 | 13 | use wasm_bindgen::prelude::*; 14 | 15 | #[wasm_bindgen] 16 | extern "C" { 17 | #[wasm_bindgen(js_namespace = console)] 18 | pub fn log(s: &str); 19 | } 20 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use std::marker::PhantomData; 3 | use std::ops::{Index, IndexMut, Mul}; 4 | use std::fmt::Write; 5 | use num_traits::{Num, NumAssign}; 6 | use crate::numbers::UnsignedInt; 7 | use crate::vector::Vector; 8 | 9 | #[derive(Clone)] 10 | pub struct Matrix { 11 | /// The number of rows. 12 | pub rows: usize, 13 | 14 | /// The number of column. 15 | pub cols: usize, 16 | 17 | /// Memory that holds the entries. 18 | pub(self) entries: Box<[T]>, 19 | } 20 | 21 | impl Matrix { 22 | /// Return an empty matrix. 23 | pub fn empty() -> Self { 24 | Self { 25 | rows: 0, 26 | cols: 0, 27 | entries: Box::new([]), 28 | } 29 | } 30 | 31 | /// Create a matrix from an array. 32 | pub fn from_array, const R: usize, const C: usize>( 33 | a: [[U; C]; R] 34 | ) -> Self { 35 | let entries = a.into_iter() 36 | .flatten() 37 | .map(|e| e.into()) 38 | .collect::>() 39 | .into_boxed_slice(); 40 | 41 | Self { 42 | rows: R, 43 | cols: C, 44 | entries, 45 | } 46 | } 47 | 48 | /// Returns the minimum of the two dimension. 49 | pub fn min_dim(&self) -> usize { 50 | std::cmp::min(self.rows, self.cols) 51 | } 52 | 53 | /// Returns a reference to an entry. 54 | pub fn entry(&self, r: usize, c: usize) -> &T { 55 | &self.entries[r * self.cols + c] 56 | } 57 | 58 | /// Returns a mutable reference to an entry. 59 | pub fn entry_mut(&mut self, r: usize, c: usize) -> &mut T { 60 | &mut self.entries[r * self.cols + c] 61 | } 62 | 63 | /// Returns a pointer to an entry. 64 | pub fn entry_ptr(&mut self, r: usize, c: usize) -> *mut T { 65 | self.entry_mut(r, c) as *mut T 66 | } 67 | 68 | /// Returns a row as a slice. 69 | pub fn row(&self, r: usize) -> &[T] { 70 | let i = r * self.cols; 71 | &self.entries[i..i+self.cols] 72 | } 73 | 74 | /// Returns a row as a mutable slice. 75 | pub fn row_mut(&mut self, r: usize) -> &mut [T] { 76 | let i = r * self.cols; 77 | &mut self.entries[i..i+self.cols] 78 | } 79 | 80 | /// Returns an iterator over the column. 81 | pub fn col(&self, c: usize) -> Column { 82 | Column::from_matrix(self, c) 83 | } 84 | 85 | /// Returns an iterator over mutable references to the elements in the column. 86 | pub fn col_mut(&mut self, c: usize) -> ColumnMut { 87 | ColumnMut::from_matrix(self, c) 88 | } 89 | 90 | /// Apply a function to each entry. 91 | pub fn map(&self, mut f: F) -> Matrix 92 | where F: FnMut(&T) -> U 93 | { 94 | let mut v = Vec::with_capacity(self.entries.len()); 95 | for e in self.entries.iter() { 96 | v.push(f(e)); 97 | } 98 | 99 | Matrix:: { 100 | rows: self.rows, 101 | cols: self.cols, 102 | entries: v.into_boxed_slice() 103 | } 104 | } 105 | 106 | /// Apply a function to each entry that can fail. 107 | /// If the function for the first time, then that resulting error 108 | /// and the location of the entry is returned. 109 | pub fn try_map(&self, mut f: F) -> Result, (usize, usize, E)> 110 | where F: FnMut(&T) -> Result 111 | { 112 | let mut v = Vec::with_capacity(self.entries.len()); 113 | for e in self.entries.iter() { 114 | match f(e) { 115 | Ok(e) => v.push(e), 116 | Err(e) => { 117 | let row = v.len() / self.rows; 118 | let col = v.len() % self.cols; 119 | return Err((row, col, e)); 120 | } 121 | } 122 | } 123 | 124 | Ok(Matrix:: { 125 | rows: self.rows, 126 | cols: self.cols, 127 | entries: v.into_boxed_slice() 128 | }) 129 | } 130 | } 131 | 132 | impl Matrix { 133 | /// Creates a new matrix whose entries are initialized with `val`. 134 | pub fn uniform(r: usize, c: usize, val: T) -> Self { 135 | Self { 136 | rows: r, 137 | cols: c, 138 | entries: vec![val; r*c].into_boxed_slice(), 139 | } 140 | } 141 | } 142 | 143 | impl Matrix { 144 | /// Returns a r×c zero matrix. 145 | pub fn zero(r: usize, c: usize) -> Self { 146 | if r == 0 || c == 0 { 147 | return Self::empty(); 148 | } 149 | 150 | Self { 151 | rows: r, 152 | cols: c, 153 | entries: vec![T::zero(); r*c].into_boxed_slice(), 154 | } 155 | } 156 | 157 | /// Returns an n×n identity matrix. 158 | pub fn id(n: usize) -> Self { 159 | let mut m = Self::zero(n, n); 160 | for i in 0..n { 161 | m[(i, i)] = T::one(); 162 | } 163 | m 164 | } 165 | 166 | /// Swap two rows. 167 | pub fn swap_rows(&mut self, i: usize, j: usize) { 168 | if i == j { return } 169 | 170 | unsafe { 171 | core::ptr::swap_nonoverlapping( 172 | self.entry_ptr(i, 0), 173 | self.entry_ptr(j, 0), self.cols 174 | ) 175 | } 176 | } 177 | 178 | /// Swap two columns. 179 | pub fn swap_columns(&mut self, i: usize, j: usize) { 180 | if i == j { return } 181 | 182 | for k in 0..self.rows { 183 | unsafe { 184 | core::ptr::swap_nonoverlapping( 185 | self.entry_ptr(k, i), self.entry_ptr(k, j), 1 186 | ); 187 | } 188 | } 189 | } 190 | 191 | /// Add a scaled row to another row. N = c * M. 192 | pub fn row_multiply_add(&mut self, n: usize, m: usize, c: T) { 193 | for i in 0..self.cols { 194 | let s = self[(n, i)] * c; 195 | self[(m, i)] += s; 196 | } 197 | } 198 | 199 | /// Add a scaled column to another column. N = c * M. 200 | pub fn col_multiply_add(&mut self, n: usize, m: usize, c: T) { 201 | for i in 0..self.rows { 202 | let s = self[(i, n)] * c; 203 | self[(i, m)] += s; 204 | } 205 | } 206 | } 207 | 208 | impl Matrix { 209 | /// Convert the matrix into a latex renderable string. 210 | pub fn to_tex(&self) -> String { 211 | let mut s = "\\left[\\begin{array}{}".to_owned(); 212 | for r in 0..self.rows { 213 | for e in self.row(r).iter().cloned() { 214 | if (e.print_negative()) { 215 | write!(&mut s, "-{} & ", T::zero() - e); 216 | } else { 217 | write!(&mut s, "{} & ", e); 218 | } 219 | } 220 | 221 | // Remove the last " & ". 222 | s.truncate(s.len() - 3); 223 | s += "\\\\"; 224 | } 225 | 226 | s += "\\end{array}\\right]"; 227 | s 228 | } 229 | } 230 | 231 | impl Index<(usize, usize)> for Matrix { 232 | type Output = T; 233 | 234 | fn index(&self, (r, c): (usize, usize)) -> &Self::Output { 235 | self.entry(r, c) 236 | } 237 | } 238 | 239 | impl IndexMut<(usize, usize)> for Matrix { 240 | fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut Self::Output { 241 | self.entry_mut(r, c) 242 | } 243 | } 244 | 245 | impl Mul for &Matrix { 246 | type Output = Matrix; 247 | fn mul(self, rhs: Self) -> Self::Output { 248 | debug_assert!(self.cols == rhs.rows, 249 | "Can't multiply matrices because of incompatible dimensions"); 250 | 251 | let mut m = Self::Output::zero(self.rows, rhs.cols); 252 | 253 | for i in 0..m.rows { 254 | for j in 0..m.cols { 255 | m[(i, j)] = self.row(i).iter() 256 | .zip(rhs.col(j)) 257 | .map(|(l, r)| *l * *r) 258 | .fold(T::zero(), T::add); 259 | } 260 | } 261 | 262 | m 263 | } 264 | } 265 | 266 | impl Mul<&Vector> for &Matrix { 267 | type Output = Vector; 268 | fn mul(self, rhs: &Vector) -> Self::Output { 269 | debug_assert!(self.cols == rhs.dim, 270 | "Can't multiply matrix/vector because of incompatible dimensions"); 271 | 272 | let mut m = Vector::::zero(self.rows); 273 | for i in 0..m.dim { 274 | m[i] = self.row(i).iter() 275 | .zip(rhs.iter()) 276 | .map(|(l, r)| *l * *r) 277 | .fold(T::zero(), T::add); 278 | } 279 | 280 | m 281 | } 282 | } 283 | 284 | impl PartialEq for Matrix { 285 | fn eq(&self, other: &Self) -> bool { 286 | self.rows == other.rows && self.cols == other.cols && 287 | self.entries.iter() 288 | .zip(other.entries.iter()) 289 | .all(|(e, f)| *e == *f) 290 | } 291 | } 292 | 293 | pub struct Column<'a, T> { 294 | ptr: *const T, 295 | end: *const T, 296 | off: usize, 297 | marker: PhantomData<&'a T>, 298 | } 299 | 300 | impl<'a, T> Column<'a, T> { 301 | pub fn from_matrix(mat: &'a Matrix, c: usize) -> Self { 302 | debug_assert!(c < mat.cols); 303 | unsafe { 304 | Self { 305 | ptr: mat.entries.as_ptr().add(c), 306 | end: mat.entries.as_ptr().add(mat.entries.len()), 307 | off: mat.cols, 308 | marker: PhantomData, 309 | } 310 | } 311 | } 312 | } 313 | 314 | impl<'a, T> Iterator for Column<'a, T> { 315 | type Item = &'a T; 316 | fn next(&mut self) -> Option { 317 | if self.ptr < self.end { 318 | unsafe { 319 | let e = &*self.ptr; 320 | self.ptr = self.ptr.add(self.off); 321 | Some(e) 322 | } 323 | } else { 324 | None 325 | } 326 | } 327 | } 328 | 329 | pub struct ColumnMut<'a, T> { 330 | ptr: *mut T, 331 | end: *mut T, 332 | off: usize, 333 | marker: PhantomData<&'a mut T>, 334 | } 335 | 336 | impl<'a, T> ColumnMut<'a, T> { 337 | pub fn from_matrix(mat: &'a mut Matrix, c: usize) -> Self { 338 | debug_assert!(c < mat.cols); 339 | unsafe { 340 | Self { 341 | ptr: mat.entries.as_mut_ptr().add(c), 342 | end: mat.entries.as_mut_ptr().add(mat.entries.len()), 343 | off: mat.cols, 344 | marker: PhantomData, 345 | } 346 | } 347 | } 348 | } 349 | 350 | impl<'a, T> Iterator for ColumnMut<'a, T> { 351 | type Item = &'a mut T; 352 | fn next(&mut self) -> Option { 353 | if self.ptr < self.end { 354 | unsafe { 355 | let e = &mut *self.ptr; 356 | self.ptr = self.ptr.add(self.off); 357 | Some(e) 358 | } 359 | } else { 360 | None 361 | } 362 | } 363 | } -------------------------------------------------------------------------------- /src/numbers.rs: -------------------------------------------------------------------------------- 1 | //! [Rings](https://en.wikipedia.org/wiki/Ring_(mathematics)) with additional structure. 2 | 3 | use std::ops::{ 4 | IndexMut, Index, BitAnd, BitOr, BitXor, Not, 5 | Shl, ShlAssign, Add, Sub, Mul, Div, Rem, 6 | AddAssign, DivAssign, RemAssign, MulAssign, SubAssign 7 | }; 8 | use std::fmt::{self, Formatter, Display}; 9 | use num_traits::{Num, NumAssign, Unsigned, Signed, Zero, One}; 10 | 11 | /// The integers mod n. 12 | /// Representatives in the range 0..n are stored. 13 | pub trait UnsignedInt: NumAssign + Copy + Ord + Unsigned + Display { 14 | /// Should the number be printed as a negative number. 15 | fn print_negative(self) -> bool { 16 | false 17 | } 18 | 19 | /// Fast conversion from u8. 20 | fn from_u8(v: u8) -> Self; 21 | } 22 | 23 | /// Parses an integer in base ten from the iterator. 24 | pub(crate) fn int_from_it( 25 | it: &mut std::iter::Peekable 26 | ) -> Option { 27 | let neg = *it.peek()? == '-'; 28 | if neg { 29 | it.next(); 30 | } 31 | 32 | // Is the character an ascii digit? 33 | if !it.peek().map_or(false, |c| c.is_ascii_digit()) { 34 | return None; 35 | } 36 | 37 | let ten = T::from_u8(10); 38 | 39 | // Parse the number. 40 | let mut n = T::zero(); 41 | loop { 42 | // Is this still a digit? 43 | let Some(d) = it.peek().and_then(|c| c.to_digit(10)) else { 44 | break 45 | }; 46 | 47 | n *= ten; 48 | n += T::from_u8(d as u8); 49 | it.next(); 50 | } 51 | 52 | if neg { 53 | n = T::zero() - n; 54 | } 55 | 56 | Some(n) 57 | } 58 | 59 | /// N-bit integers basically. 60 | pub trait UniformNum: UnsignedInt 61 | + BitAnd 62 | + BitOr 63 | + BitXor 64 | + Shl 65 | + ShlAssign 66 | + Not {} 67 | 68 | impl UniformNum for std::num::Wrapping {} 69 | impl UniformNum for std::num::Wrapping {} 70 | impl UniformNum for std::num::Wrapping {} 71 | impl UniformNum for std::num::Wrapping {} 72 | impl UniformNum for std::num::Wrapping {} 73 | 74 | macro_rules! impl_uint { 75 | ($impl_ty:ty) => { 76 | impl UnsignedInt for std::num::Wrapping<$impl_ty> { 77 | fn print_negative(self) -> bool { 78 | self.0 > (1 << (<$impl_ty>::BITS - 1)) 79 | } 80 | 81 | fn from_u8(v: u8) -> Self { 82 | std::num::Wrapping(v as $impl_ty) 83 | } 84 | } 85 | } 86 | } 87 | 88 | impl_uint!(u8); 89 | impl_uint!(u16); 90 | impl_uint!(u32); 91 | impl_uint!(u64); 92 | impl_uint!(u128); 93 | impl_uint!(usize); -------------------------------------------------------------------------------- /src/pages/linear_congruences.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use std::fmt::Display; 3 | use std::num::Wrapping; 4 | use crate::vector::Vector; 5 | use crate::matrix::Matrix; 6 | use super::{Width, bold, underbrace}; 7 | use crate::congruence_solver::{ 8 | AffineLattice, diagonalize, solve_scalar_congruence 9 | }; 10 | use crate::numbers::UnsignedInt; 11 | 12 | /// Stores the intermediate results during the computation of the solution. 13 | #[wasm_bindgen] 14 | pub struct SolveTrace { 15 | /// The diagonalization of the matrix. 16 | diag: String, 17 | 18 | /// The resulting diagonal system. 19 | scalar_system: String, 20 | 21 | /// Linear congruences and solutions. 22 | linear_solutions: String, 23 | 24 | /// Vector form of the solution. 25 | vector_solution: String, 26 | 27 | /// The final solution. 28 | final_solution: String, 29 | } 30 | 31 | #[wasm_bindgen] 32 | impl SolveTrace { 33 | #[wasm_bindgen(getter)] 34 | pub fn diag(&self) -> String { 35 | self.diag.clone() 36 | } 37 | 38 | #[wasm_bindgen(getter)] 39 | pub fn scalar_system(&self) -> String { 40 | self.scalar_system.clone() 41 | } 42 | 43 | #[wasm_bindgen(getter)] 44 | pub fn linear_solutions(&self) -> String { 45 | self.linear_solutions.clone() 46 | } 47 | 48 | #[wasm_bindgen(getter)] 49 | pub fn vector_solution(&self) -> String { 50 | self.vector_solution.clone() 51 | } 52 | 53 | #[wasm_bindgen(getter)] 54 | pub fn final_solution(&self) -> String { 55 | self.final_solution.clone() 56 | } 57 | } 58 | 59 | fn solve_congruences_impl( 60 | a: Matrix<&str>, b: Vector<&str> 61 | ) -> Result { 62 | let a = a.try_map(|&e| T::from_str_radix(e, 10)) 63 | .map_err(|(r, c, _)| format!("Failed to parse entry ({}, {}).", r+1, c+1))?; 64 | 65 | let b = b.try_map(|&e| T::from_str_radix(e, 10)) 66 | .map_err(|(r, _)| format!("Failed to parse entry ({}, {}).", r+1, a.cols+1))?; 67 | 68 | let mut d = a.clone(); 69 | let (s, t) = diagonalize(&mut d); 70 | assert!(&(&s * &a) * &t == d); 71 | 72 | let diag = format!("{}={}{}{}", 73 | underbrace(d.to_tex(), bold("D")), 74 | underbrace(s.to_tex(), bold("S")), 75 | underbrace(a.to_tex(), bold("A")), 76 | underbrace(t.to_tex(), bold("T")) 77 | ); 78 | 79 | // Transform the vector b. 80 | // We could already do this in diagonalize if we really wanted. 81 | let b_new = (&s * &b); 82 | 83 | let scalar_system = format!("{}\\mathbf{{x'}}={}{}={}", 84 | underbrace(d.to_tex(), bold("D")), 85 | underbrace(s.to_tex(), bold("S")), 86 | underbrace(b.to_tex(), bold("b")), 87 | b_new.to_tex() 88 | ); 89 | 90 | let b = b_new; 91 | 92 | let min_dim = d.min_dim(); 93 | 94 | if let Some(i) = b.iter() 95 | .skip(min_dim) 96 | .position(|e| *e != T::zero()) { 97 | let i = min_dim + i; 98 | let linear_solutions = format!( 99 | "\\text{{Row {}: }} 0={}\\implies \\text{{No solution}}", 100 | i + 1, b[i] 101 | ); 102 | return Ok(SolveTrace { 103 | diag, 104 | scalar_system, 105 | linear_solutions, 106 | vector_solution: String::new(), 107 | final_solution: String::new() 108 | }); 109 | } 110 | 111 | // Some solution to the system. 112 | let mut offset = Vector::zero(d.cols); 113 | 114 | // The basis of the kernel. 115 | let mut basis = Vec::new(); 116 | 117 | let mut linear_solutions = "\\begin{align}".to_owned(); 118 | 119 | // Variable index for basis. 120 | let mut j = 1; 121 | 122 | // Solve the scalar linear congruences. 123 | for i in 0..d.min_dim() { 124 | linear_solutions += &format!( 125 | "{}x'_{{{}}}&={} &\\implies ", d[(i, i)], i + 1, b[i]); 126 | let (x, kern) = match solve_scalar_congruence(d[(i, i)], b[i]) { 127 | // If there is no solution, 128 | // then the whole system does not have a solution. 129 | None => { 130 | linear_solutions += "\\text{No solution!}&\\end{align}"; 131 | return Ok(SolveTrace { 132 | diag, 133 | scalar_system, 134 | linear_solutions, 135 | vector_solution: String::new(), 136 | final_solution: String::new(), 137 | }); 138 | }, 139 | Some(s) => s, 140 | }; 141 | 142 | if kern == T::zero() { 143 | linear_solutions += &format!("x'_{{{}}}&={}\\\\", i + 1, x); 144 | } else { 145 | linear_solutions += &format!( 146 | "x'_{{{}}}&={}+{}a_{{{}}}\\\\", i + 1, x, kern, j 147 | ); 148 | j += 1; 149 | } 150 | 151 | // The particular solution is an entry is 152 | // the particular solution of the whole system. 153 | offset[i] = x; 154 | 155 | // If the kernel is zero, then the vector is zero for sure. 156 | if kern != T::zero() { 157 | let mut v = Vector::zero(d.cols); 158 | v[i] = kern; 159 | basis.push(v); 160 | } 161 | } 162 | 163 | // If there are more variables then equations 164 | // then there are no restrictions on the variables 165 | // from index d.rows 166 | for i in d.rows..d.cols { 167 | let mut v = Vector::zero(d.cols); 168 | v[i] = T::one(); 169 | basis.push(v); 170 | 171 | linear_solutions += &format!( 172 | "&&x'_{{{}}}&=a_{{{}}}\\\\", i + 1, j 173 | ); 174 | j += 1; 175 | } 176 | 177 | linear_solutions += "\\end{align}"; 178 | 179 | let mut solution = AffineLattice { 180 | offset, 181 | basis, 182 | }; 183 | 184 | let x_old = solution.to_tex_brace(); 185 | let vector_solution = format!("\\mathbf{{x'}}={}", x_old); 186 | 187 | solution.offset = (&t * &solution.offset); 188 | for v in &mut solution.basis { 189 | *v = (&t * &*v); 190 | } 191 | 192 | let final_solution = format!("\\mathbf{{x}}={}{}={}", 193 | underbrace(t.to_tex(), bold("T")), 194 | underbrace(x_old, bold("x'")), 195 | solution.to_tex() 196 | ); 197 | 198 | Ok(SolveTrace { 199 | diag, 200 | scalar_system, 201 | linear_solutions, 202 | vector_solution, 203 | final_solution, 204 | }) 205 | } 206 | 207 | #[wasm_bindgen] 208 | pub fn solve_congruences(matrix_str: String, bit: Width) -> Result { 209 | // The number of rows is the number of lines. 210 | let rows = matrix_str.lines().count(); 211 | if rows == 0 { 212 | return Err("Empty matrix.".into()); 213 | } 214 | 215 | // Get the number of columns from the first line. 216 | let cols = matrix_str.lines() 217 | .next() 218 | .unwrap() 219 | .split_ascii_whitespace() 220 | .count() - 1; 221 | if cols == 0 { 222 | return Err("Empty matrix.".into()); 223 | } 224 | 225 | let mut a = Matrix::<&str>::uniform(rows, cols, ""); 226 | let mut b = Vector::<&str>::uniform(rows, ""); 227 | 228 | for (i, l) in matrix_str.lines().enumerate() { 229 | // Is the number of elements in this row the same as in the first. 230 | let mut ok = false; 231 | for (j, e) in l.split_ascii_whitespace().enumerate() { 232 | if (j < cols) { 233 | a[(i, j)] = e; 234 | } else if (j == cols) { 235 | b[i] = e; 236 | ok = true; 237 | } else { 238 | ok = false; 239 | break; 240 | } 241 | } 242 | 243 | if !ok { 244 | return Err(std::format!("Row {} has a different number of \ 245 | entries than the first row.", i + 1)); 246 | } 247 | } 248 | 249 | match bit { 250 | Width::U8 => solve_congruences_impl::>(a, b), 251 | Width::U16 => solve_congruences_impl::>(a, b), 252 | Width::U32 => solve_congruences_impl::>(a, b), 253 | Width::U64 => solve_congruences_impl::>(a, b), 254 | Width::U128 => solve_congruences_impl::>(a, b), 255 | } 256 | } -------------------------------------------------------------------------------- /src/pages/mod.rs: -------------------------------------------------------------------------------- 1 | //! API for webpages. 2 | 3 | mod obfuscate; 4 | mod linear_congruences; 5 | mod perm_poly; 6 | 7 | use wasm_bindgen::prelude::*; 8 | 9 | #[wasm_bindgen] 10 | #[derive(Clone, Copy, Debug)] 11 | pub enum Width { 12 | U8, 13 | U16, 14 | U32, 15 | U64, 16 | U128, 17 | } 18 | 19 | pub fn underbrace, U: AsRef>(inner: T, label: U) -> String { 20 | format!("\\underbrace{{{}}}_{{{}}}", inner.as_ref(), label.as_ref()) 21 | } 22 | 23 | pub fn bold>(inner: T) -> String { 24 | format!("\\mathbf{{{}}}", inner.as_ref()) 25 | } -------------------------------------------------------------------------------- /src/pages/obfuscate.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::fmt::{self, Display, Formatter, Write}; 3 | use std::num::Wrapping; 4 | use std::rc::Rc; 5 | use rand::distributions::{Standard, Distribution}; 6 | use wasm_bindgen::prelude::*; 7 | use super::Width; 8 | use crate::congruence_solver::solve_congruences; 9 | use crate::matrix::Matrix; 10 | use crate::vector::Vector; 11 | use crate::printer::Printer; 12 | use crate::expr::Expr; 13 | use crate::uniform_expr::{LUExpr, UExpr, Valuation}; 14 | use crate::numbers::{UnsignedInt, UniformNum}; 15 | 16 | #[wasm_bindgen] 17 | #[derive(Debug)] 18 | pub struct ObfuscationConfig { 19 | /// The expression to obfuscate. 20 | #[wasm_bindgen(skip)] 21 | pub expr: String, 22 | 23 | /// The integer width. 24 | pub width: Width, 25 | 26 | /// How to print the result. 27 | pub printer: Printer, 28 | 29 | /// The number of auxiliary variables to use. 30 | pub aux_vars: usize, 31 | 32 | /// The depth of the rewrite expressions. 33 | /// Ultimately, we should probably just generate a random truth table 34 | /// and find a simple expression for it with `egg`. 35 | pub rewrite_depth: u8, 36 | 37 | /// The number of rewrite expressions to use. 38 | pub rewrite_count: usize, 39 | } 40 | 41 | #[wasm_bindgen] 42 | impl ObfuscationConfig { 43 | #[wasm_bindgen(constructor)] 44 | pub fn new() -> Self { 45 | Self { 46 | expr: String::new(), 47 | width: Width::U8, 48 | printer: Printer::C, 49 | aux_vars: 0, 50 | rewrite_depth: 3, 51 | rewrite_count: 24, 52 | } 53 | } 54 | 55 | #[wasm_bindgen(setter)] 56 | pub fn set_expr(&mut self, expr: String) { 57 | self.expr = expr; 58 | } 59 | } 60 | 61 | #[wasm_bindgen] 62 | pub fn obfuscate(cfg: &ObfuscationConfig) -> Result { 63 | match cfg.width { 64 | Width::U8 => obfuscate_impl::>(cfg), 65 | Width::U16 => obfuscate_impl::>(cfg), 66 | Width::U32 => obfuscate_impl::>(cfg), 67 | Width::U64 => obfuscate_impl::>(cfg), 68 | Width::U128 => obfuscate_impl::>(cfg), 69 | } 70 | } 71 | 72 | fn obfuscate_impl( 73 | cfg: &ObfuscationConfig 74 | ) -> Result 75 | where Standard: Distribution 76 | { 77 | crate::log(&format!("Obfuscating with config: {:?}", cfg)); 78 | let mut e = Rc::new(Expr::::from_string(&cfg.expr)?); 79 | 80 | let mut vars = e.vars(); 81 | for i in 0..cfg.aux_vars { 82 | vars.push(format!("aux{}", i)); 83 | } 84 | 85 | if vars.is_empty() { 86 | return Err("No variables to obfuscate with. Add auxiliary variables.".to_owned()); 87 | } 88 | 89 | let mut v = Vec::new(); 90 | obfuscate_expr(&mut e, &mut v, &vars, cfg); 91 | Ok(e.print_as_fn(cfg.printer)) 92 | } 93 | 94 | /// Tries to convert the expression to a uniform expression. 95 | /// When part of the expression isn't a uniform expression, 96 | /// it generates a variable and remembers what expression to 97 | /// substitute for that variable. 98 | fn expr_to_uexpr( 99 | e: &Rc>, subs: &mut Vec<(String, Rc>)> 100 | ) -> UExpr { 101 | if Rc::strong_count(e) > 1 { 102 | let var = format!("_sub_{}", subs.len()); 103 | subs.push((var.clone(), e.clone())); 104 | return UExpr::Var(var); 105 | } 106 | 107 | match e.as_ref() { 108 | Expr::Var(v) => UExpr::Var(v.clone()), 109 | Expr::And(l, r) => UExpr::and(expr_to_uexpr(l, subs), expr_to_uexpr(r, subs)), 110 | Expr::Or(l, r) => UExpr::or(expr_to_uexpr(l, subs), expr_to_uexpr(r, subs)), 111 | Expr::Xor(l, r) => UExpr::xor(expr_to_uexpr(l, subs), expr_to_uexpr(r, subs)), 112 | Expr::Not(i) => UExpr::not(expr_to_uexpr(i, subs)), 113 | // Otherwise generate a new variable and add the substitution. 114 | _ => { 115 | let var = format!("_sub_{}", subs.len()); 116 | subs.push((var.clone(), e.clone())); 117 | UExpr::Var(var) 118 | } 119 | } 120 | } 121 | 122 | /// Tries to convert an expression 123 | fn parse_term( 124 | e: &Rc>, subs: &mut Vec<(String, Rc>)> 125 | ) -> (T, UExpr) { 126 | if let Expr::Mul(l, r) = e.as_ref() { 127 | if let Expr::Const(i) = l.as_ref() { 128 | return (*i, expr_to_uexpr(r, subs)); 129 | } else if let Expr::Const(i) = r.as_ref() { 130 | return (*i, expr_to_uexpr(l, subs)); 131 | } 132 | } else if let Expr::Const(c) = e.as_ref() { 133 | return (T::zero() - *c, UExpr::Ones); 134 | } 135 | 136 | (T::one(), expr_to_uexpr(e, subs)) 137 | } 138 | 139 | fn expr_to_luexpr( 140 | e: &Rc>, 141 | lu: &mut LUExpr, 142 | subs: &mut Vec<(String, Rc>)>, 143 | sign: bool 144 | ) { 145 | // If this is an add the left and right hand side 146 | // can contribute to the linear combination. 147 | match e.as_ref() { 148 | Expr::Add(l, r) => { 149 | expr_to_luexpr(l, lu, subs, sign); 150 | expr_to_luexpr(r, lu, subs, sign); 151 | }, 152 | 153 | Expr::Sub(l, r) => { 154 | expr_to_luexpr(l, lu, subs, sign); 155 | expr_to_luexpr(r, lu, subs, !sign); 156 | }, 157 | 158 | Expr::Neg(i) => { 159 | // Theoretically we could allow another whole 160 | // LUExpr in here but hopefully not too important. 161 | 162 | // Flipped because of the Neg. 163 | let f = if sign { T::one() } else { T::zero() - T::one() }; 164 | lu.0.push((f, expr_to_uexpr(i, subs))); 165 | }, 166 | 167 | // Otherwise parse the term from this expression. 168 | _ => { 169 | let (mut f, u) = parse_term(e, subs); 170 | if sign { 171 | f = T::zero() - f; 172 | } 173 | lu.0.push((f, u)); 174 | }, 175 | } 176 | } 177 | 178 | fn obfuscate_expr( 179 | er: &mut Rc>, 180 | visited: &mut Vec<*const Expr>, 181 | vars: &[String], 182 | cfg: &ObfuscationConfig 183 | ) 184 | where Standard: Distribution 185 | { 186 | let ptr = Rc::as_ptr(er); 187 | if Rc::strong_count(er) > 1 { 188 | if visited.contains(&ptr) { 189 | return; 190 | } 191 | visited.push(ptr); 192 | } 193 | 194 | let e = unsafe { &mut *(ptr as *mut _) }; 195 | 196 | match e { 197 | Expr::Mul(l, r) => { 198 | obfuscate_expr(l, visited, vars, cfg); 199 | obfuscate_expr(r, visited, vars, cfg); 200 | }, 201 | Expr::Div(l, r) | Expr::Mod(l, r) => { 202 | obfuscate_expr(l, visited, vars, cfg); 203 | obfuscate_expr(r, visited, vars, cfg); 204 | }, 205 | Expr::Shl(l, r) | Expr::Shr(l, r) => { 206 | obfuscate_expr(l, visited, vars, cfg); 207 | obfuscate_expr(r, visited, vars, cfg); 208 | }, 209 | _ => { 210 | // Try to find the largest subexpression that is linear MBA 211 | // and obfuscate it on its own. 212 | let mut lu = LUExpr(Vec::new()); 213 | 214 | // Substitutions in the LUExpr. 215 | let mut subs: Vec<(String, Rc>)> = Vec::new(); 216 | 217 | expr_to_luexpr(er, &mut lu, &mut subs, false); 218 | *e = rewrite_random(&lu, vars, cfg).to_expr(); 219 | for (var, sub) in &mut subs { 220 | // Obfuscate the substituted expressions. 221 | obfuscate_expr(sub, visited, vars, cfg); 222 | 223 | // Substitute them for the variables. 224 | e.substitute(sub, var); 225 | } 226 | } 227 | } 228 | } 229 | 230 | const REWRITE_TRIES: usize = 128; 231 | 232 | fn rewrite_random( 233 | e: &LUExpr, vars: &[String], cfg: &ObfuscationConfig 234 | ) -> LUExpr 235 | where Standard: Distribution 236 | { 237 | let mut vars: Vec<_> = vars.iter().cloned().collect(); 238 | for v in e.vars() { 239 | if !vars.contains(&v) { 240 | vars.push(v); 241 | } 242 | } 243 | for _ in 0..REWRITE_TRIES { 244 | let mut ops = Vec::new(); 245 | for _ in 0..cfg.rewrite_count { 246 | ops.push(LUExpr::from_uexpr( 247 | random_bool_expr(&vars, cfg.rewrite_depth) 248 | )); 249 | } 250 | 251 | if let Some(r) = rewrite(e, &ops, true) { 252 | return r; 253 | } 254 | } 255 | 256 | panic!("Failed to rewrite uniform expression."); 257 | } 258 | 259 | /// Note that this never generates `Ones` or any expression containing it, 260 | /// as those can be easily simplified to one that does not contain it. 261 | fn random_bool_expr>(vars: &[T], max_depth: u8) -> UExpr { 262 | assert!(!vars.is_empty(), "There needs to be at least one variable for the random expression."); 263 | 264 | let rand_var = || UExpr::Var(vars[rand::random::() % vars.len()].as_ref().to_owned()); 265 | 266 | if max_depth == 0 { 267 | return rand_var(); 268 | } 269 | 270 | // Generate one of the four variants uniformly at random. 271 | let d = max_depth - 1; 272 | match rand::random::() % 5 { 273 | 0 => rand_var(), 274 | 1 => UExpr::Not(random_bool_expr(vars, d).into()), 275 | 2 => UExpr::And(random_bool_expr(vars, d).into(), random_bool_expr(vars, d).into()), 276 | 3 => UExpr::Or(random_bool_expr(vars, d).into(), random_bool_expr(vars, d).into()), 277 | 4 => UExpr::Xor(random_bool_expr(vars, d).into(), random_bool_expr(vars, d).into()), 278 | _ => panic!("If you get here, mathematics is broken."), 279 | } 280 | } 281 | 282 | #[wasm_bindgen] 283 | pub fn obfuscate_linear(req: ObfLinReq) -> Result { 284 | match req.bits { 285 | Width::U8 => obfuscate_linear_impl::>(req), 286 | Width::U16 => obfuscate_linear_impl::>(req), 287 | Width::U32 => obfuscate_linear_impl::>(req), 288 | Width::U64 => obfuscate_linear_impl::>(req), 289 | Width::U128 => obfuscate_linear_impl::>(req), 290 | } 291 | } 292 | 293 | #[wasm_bindgen] 294 | pub fn normalize_op(expr: String, bits: Width) -> String { 295 | match bits { 296 | Width::U8 => LUExpr::>::from_string(expr) 297 | .map_or(String::new(), |s| s.to_string()), 298 | Width::U16 => LUExpr::>::from_string(expr) 299 | .map_or(String::new(), |s| s.to_string()), 300 | Width::U32 => LUExpr::>::from_string(expr) 301 | .map_or(String::new(), |s| s.to_string()), 302 | Width::U64 => LUExpr::>::from_string(expr) 303 | .map_or(String::new(), |s| s.to_string()), 304 | Width::U128 => LUExpr::>::from_string(expr) 305 | .map_or(String::new(), |s| s.to_string()), 306 | } 307 | } 308 | 309 | fn obfuscate_linear_impl( 310 | req: ObfLinReq 311 | ) -> Result 312 | where 313 | T: UniformNum + std::fmt::Display, 314 | Standard: Distribution 315 | { 316 | let expr = LUExpr::::from_string(req.expr).ok_or( 317 | "Input is not a linear combination of uniform expressions".to_owned() 318 | )?; 319 | 320 | let ops: Vec<_> = req.ops.into_iter() 321 | .map(|s| LUExpr::::from_string(s).unwrap()) 322 | .collect(); 323 | 324 | rewrite(&expr, &ops, req.randomize) 325 | .map(|e| req.printer.print_luexpr(&e)) 326 | .ok_or("Operations can't be used to rewrite the input".to_owned()) 327 | } 328 | 329 | fn rewrite( 330 | expr: &LUExpr, ops: &[LUExpr], randomize: bool 331 | ) -> Option> 332 | where 333 | T: UniformNum + std::fmt::Display, 334 | Standard: Distribution 335 | { 336 | // Find all variables. 337 | let mut v = BTreeSet::new(); 338 | expr.vars_impl(&mut v); 339 | for op in ops { 340 | op.vars_impl(&mut v); 341 | } 342 | 343 | let v: Vec<_> = v.into_iter().collect(); 344 | 345 | let mut val = Valuation::zero(v.clone()); 346 | 347 | let rows = 1usize << v.len(); 348 | let cols = ops.len(); 349 | 350 | let mut a = Matrix::zero(rows, cols); 351 | let mut b = Vector::zero(rows); 352 | 353 | // Initialize the matrix. 354 | for i in 0..rows { 355 | let row = a.row_mut(i); 356 | 357 | // Initialize the valuation. 358 | for (j, c) in v.iter().enumerate() { 359 | if (i >> j) & 1 == 0 { 360 | val[c] = T::zero(); 361 | } else { 362 | val[c] = T::zero() - T::one(); 363 | } 364 | } 365 | 366 | // Write the values of the operations into this row of the matrix. 367 | for (j, e) in ops.iter().enumerate() { 368 | row[j] = e.eval(&val); 369 | } 370 | 371 | // Write the desired result into the vector. 372 | b[i] = expr.eval(&val); 373 | } 374 | 375 | // Solve the system. 376 | let l = solve_congruences(a, &b); 377 | 378 | // Does it have solutions? 379 | if l.is_empty() { 380 | return None; 381 | } 382 | 383 | // Sample a point from the lattice. 384 | let mut solution = l.offset; 385 | if randomize { 386 | for b in l.basis { 387 | solution += &(b * rand::random()); 388 | } 389 | } 390 | 391 | // Put it in an LUExpr. 392 | // Currently, this simplifies the inner LUExprs into 393 | // sums of UExprs, such that the result is an LUExpr. 394 | // Once there is a more general Expr class, we need not do this. 395 | let mut v = Vec::new(); 396 | for (c, o) in solution.iter().zip(ops.iter()) { 397 | for (d, e) in &o.0 { 398 | // Is the UExpr already in the linear combination? 399 | match v.iter_mut().find(|(_, f)| f == e) { 400 | Some((f, _)) => *f += *c * *d, 401 | None => v.push((*c * *d, e.clone())), 402 | } 403 | } 404 | } 405 | 406 | // Remove terms where the coefficient is zero. 407 | v.retain(|(f, u)| !f.is_zero()); 408 | 409 | Some(LUExpr(v)) 410 | } 411 | 412 | /// Obfuscation settings. 413 | #[wasm_bindgen] 414 | #[derive(Debug)] 415 | pub struct ObfLinReq { 416 | /// The expression to obfuscate. 417 | #[wasm_bindgen(skip)] 418 | pub expr: String, 419 | 420 | /// The operations used for rewriting. 421 | /// There is currently an issue with this because we verify the ops 422 | /// with a certain bitness but the obfuscation may happen with another one. 423 | /// This is only really a problem with big constants though, so not that 424 | /// likely to happen to anyone. 425 | #[wasm_bindgen(skip)] 426 | pub ops: Vec, 427 | 428 | /// The integer width. 429 | pub bits: Width, 430 | 431 | /// Should the solution be randomized. 432 | pub randomize: bool, 433 | 434 | /// How to print the result. 435 | pub printer: Printer, 436 | } 437 | 438 | #[wasm_bindgen] 439 | impl ObfLinReq { 440 | #[wasm_bindgen(constructor)] 441 | pub fn new() -> Self { 442 | Self { 443 | expr: String::new(), 444 | ops: Vec::new(), 445 | bits: Width::U8, 446 | randomize: true, 447 | printer: Printer::C, 448 | } 449 | } 450 | 451 | #[wasm_bindgen(setter)] 452 | pub fn set_expr(&mut self, expr: String) { 453 | self.expr = expr; 454 | } 455 | 456 | #[wasm_bindgen] 457 | pub fn add_op(&mut self, op: String) { 458 | self.ops.push(op); 459 | } 460 | } -------------------------------------------------------------------------------- /src/pages/perm_poly.rs: -------------------------------------------------------------------------------- 1 | use std::num::Wrapping; 2 | use std::ops::ShlAssign; 3 | 4 | use num_traits::{Num, NumAssign}; 5 | use rand::Rng; 6 | use rand::distributions::{Standard, Distribution, Uniform}; 7 | use wasm_bindgen::prelude::*; 8 | 9 | use crate::congruence_solver; 10 | use crate::vector::Vector; 11 | use crate::matrix::Matrix; 12 | use crate::polynomial::Polynomial; 13 | use crate::numbers::UniformNum; 14 | 15 | use super::Width; 16 | 17 | #[wasm_bindgen] 18 | pub fn invert_poly( 19 | poly: String, bits: Width, alg: String 20 | ) -> Result { 21 | match bits { 22 | Width::U8 => invert_poly_impl::>(poly, alg), 23 | Width::U16 => invert_poly_impl::>(poly, alg), 24 | Width::U32 => invert_poly_impl::>(poly, alg), 25 | Width::U64 => invert_poly_impl::>(poly, alg), 26 | Width::U128 => invert_poly_impl::>(poly, alg), 27 | } 28 | } 29 | 30 | #[wasm_bindgen] 31 | pub fn rand_poly(bits: Width) -> String { 32 | match bits { 33 | Width::U8 => rand_poly_impl::>(), 34 | Width::U16 => rand_poly_impl::>(), 35 | Width::U32 => rand_poly_impl::>(), 36 | Width::U64 => rand_poly_impl::>(), 37 | Width::U128 => rand_poly_impl::>(), 38 | } 39 | } 40 | 41 | fn invert_poly_impl( 42 | poly: String, alg: String 43 | ) -> Result { 44 | // Parse the polynomial and make sure it's a permutation. 45 | let p = parse_poly::(poly)?; 46 | if !is_perm_poly(&p) { 47 | return Err("The input is not a permutation polynomial".into()); 48 | } 49 | 50 | // Find the generators of the "zero ideal". 51 | let zi = ZeroIdeal::::init(); 52 | 53 | // Simplify the polynomial. 54 | let p = p.simplified(&zi); 55 | 56 | // Select the algorithm. 57 | let alg = match alg.as_str() { 58 | "Newton" => invert_newton, 59 | "Fermat" => invert_fermat, 60 | "Lagrange" => invert_lagrange, 61 | _ => return Err("Invalid algorithm.".into()), 62 | }; 63 | 64 | // Set up timing. 65 | let perf = web_sys::window().unwrap().performance().unwrap(); 66 | let now = perf.now(); 67 | 68 | // Compute the inverse. 69 | let q = alg(&p, &zi); 70 | 71 | // Log the execution time. 72 | let dur = perf.now() - now; 73 | crate::log(&format!("Inverting took {} ms", dur as u64)); 74 | 75 | // Check that we did indeed find the inverse. 76 | if !compose(&p, &q, &zi).simplified(&zi).is_id() { 77 | crate::log("Inverse is wrong!"); 78 | } 79 | 80 | // Return the inverse's tex. 81 | Ok(q.to_tex()) 82 | } 83 | 84 | /// Invert using p as a generator. 85 | fn invert_fermat( 86 | p: &Polynomial, zi: &ZeroIdeal 87 | ) -> Polynomial { 88 | // p^(2^i-1) 89 | let mut f = p.clone(); 90 | for i in 0..zi.n { 91 | // p^(2^i) 92 | let g = compose(&f, p, zi).simplified(zi); 93 | if g.is_id() { 94 | // This will incorrectly say ord(X)=2, but whatever. 95 | crate::log(&format!("log(ord(p)) = {}", i + 1)); 96 | return f; 97 | } 98 | 99 | f = compose(&f, &g, zi).simplified(zi); 100 | } 101 | 102 | assert!(false, "Failed to invert {}", p); 103 | Polynomial::zero() 104 | } 105 | 106 | /// Invert using Newton's method. 107 | fn invert_newton( 108 | p: &Polynomial, zi: &ZeroIdeal 109 | ) -> Polynomial { 110 | // Initialize g with the initial guess Q(X)=X. 111 | let mut q = Polynomial::from_coeffs(&[T::zero(), T::one()]); 112 | 113 | let mut it = 0; 114 | 115 | // Do the Newton iterations. 116 | loop { 117 | assert!(it <= zi.n * 2, "Failed to compute the inverse\ 118 | in a reasonable number of iterations."); 119 | 120 | // Compute the composition. 121 | let mut comp = compose(p, &q, zi).simplified(zi); 122 | 123 | // Do we already have p(q(x)) = x? 124 | if comp.is_id() { 125 | crate::log(&format!("Inverted in {} iterations", it)); 126 | return q; 127 | } 128 | 129 | // Subtract X. 130 | // This is the quantity we want to make 0. 131 | comp.coeffs[1] -= T::one(); 132 | 133 | // Update the guess. 134 | let qd = q.derivative(); 135 | q -= &(&qd * &comp); 136 | q.simplify(zi); 137 | 138 | it += 1; 139 | } 140 | } 141 | 142 | /// Invert using interpolation. 143 | fn invert_lagrange( 144 | p: &Polynomial, zi: &ZeroIdeal 145 | ) -> Polynomial { 146 | // Construct a system of linear congruences. 147 | let rows = zi.gen.last().unwrap().len(); 148 | let cols = zi.gen.last().unwrap().len(); 149 | 150 | // Construct the Vandermonde matrix. 151 | let mut a = Matrix::::zero(rows, cols); 152 | let mut i = T::zero(); 153 | for r in 0..rows { 154 | let mut j = T::one(); 155 | let x = p.eval(i); 156 | for c in 0..cols { 157 | a[(r, c)] = j; 158 | j *= x; 159 | } 160 | 161 | i += T::one(); 162 | } 163 | 164 | // Construct the vector of values of the polynomial. 165 | let mut b = Vector::::zero(rows); 166 | let mut i = T::zero(); 167 | for r in 0..rows { 168 | b[r] = i; 169 | i += T::one(); 170 | } 171 | 172 | let l = congruence_solver::solve_congruences(a, &b); 173 | //crate::log(&format!("The kernel has dimension {}.", l.basis.len())); 174 | for b in &l.basis { 175 | let k = Polynomial::from_coeffs(b.entries()); 176 | //assert!(k.simplified(zi).is_zero(), 177 | // "Polynomial in the kernel is non-null."); 178 | if !k.clone().simplified(zi).is_zero() { 179 | crate::log(&format!("Polynomial in kernel is not null: {}", k)); 180 | } 181 | } 182 | 183 | Polynomial::from_coeffs(l.offset.entries()).simplified(zi) 184 | } 185 | 186 | /// Computes the composition of two polynomials. 187 | fn compose( 188 | p: &Polynomial, 189 | q: &Polynomial, 190 | zi: &ZeroIdeal 191 | ) -> Polynomial { 192 | // We are using Horner's method to evaluate the polynomial `p` at `q(x)`. 193 | 194 | // Iterate over the coefficients in reverse order. 195 | let mut iter = p.coeffs.iter().rev(); 196 | 197 | // The last coefficient is the initial value. 198 | let mut r = Polynomial::constant(iter.next().map_or(T::zero(), |c| *c)); 199 | 200 | for c in iter { 201 | r *= q; 202 | r += *c; 203 | r.reduce(zi); 204 | } 205 | 206 | r 207 | } 208 | 209 | /// Used internally as a function to Iterator::fold. 210 | fn parity(acc: bool, i: &T) -> bool { 211 | match *i & T::one() != T::zero() { 212 | true => !acc, 213 | false => acc, 214 | } 215 | } 216 | 217 | /// Is this a permutation polynomial? 218 | fn is_perm_poly(f: &Polynomial) -> bool { 219 | f.coeffs.get(1).map_or(false, |i| *i & T::one() != T::zero()) 220 | && f.coeffs.iter().skip(2).step_by(2).fold(true, parity) 221 | && f.coeffs.iter().skip(3).step_by(2).fold(true, parity) 222 | } 223 | 224 | fn rand_poly_impl() -> String 225 | where 226 | T: UniformNum + std::fmt::Display, 227 | Standard: Distribution, 228 | { 229 | let mut rng = rand::thread_rng(); 230 | let zi = ZeroIdeal::::init(); 231 | // This is the smallest degree possible that can represent any permutation 232 | // that has a polynomial representation. 233 | let degree = zi.gen.last().unwrap().len() - 1; 234 | 235 | // Create the polynomial. 236 | let mut p = Polynomial { 237 | coeffs: vec![T::zero(); degree + 1] 238 | }; 239 | 240 | // Initialize the coefficients with random values. 241 | for c in &mut p.coeffs { 242 | *c = rng.gen(); 243 | } 244 | 245 | // a_1 has to be odd. 246 | if p.coeffs[1] & T::one() == T::zero() { 247 | p.coeffs[1] += T::one(); 248 | } 249 | 250 | // a_2 + a_4 + ... has to be even. 251 | if p.coeffs.iter().skip(2).step_by(2).fold(false, parity) { 252 | let dist = Uniform::from(1..=degree/2); 253 | let i = dist.sample(&mut rng); 254 | p.coeffs[2*i] += T::one(); 255 | } 256 | 257 | // a_3 + a_5 + ... has to be even. 258 | if p.coeffs.iter().skip(3).step_by(2).fold(false, parity) { 259 | let dist = Uniform::from(1..=(degree-1)/2); 260 | let i = dist.sample(&mut rng); 261 | p.coeffs[2*i+1] += T::one(); 262 | } 263 | 264 | p.simplify(&zi); 265 | p.to_string() 266 | } 267 | 268 | 269 | /// Parse a polynomial. 270 | /// Either as a space-separated list of coefficients a_d ... a_0, 271 | /// or as a polynomial expression 4x^2 + 3x + 2. 272 | fn parse_poly( 273 | mut poly: String 274 | ) -> Result, String> { 275 | if !poly.is_ascii() { 276 | return Err("Non-ascii input.".into()); 277 | } 278 | 279 | poly.make_ascii_lowercase(); 280 | 281 | // If the polynomial contains x then it is an actual expression. 282 | // Otherwise, it is a list of coefficients. 283 | let mut coeffs = Vec::with_capacity(8); 284 | if poly.contains('x') { 285 | poly.retain(|c| c != ' '); 286 | let p = poly.as_bytes(); 287 | let mut i = 0; 288 | let mut last_i = usize::MAX; 289 | while i < p.len() { 290 | if i == last_i { 291 | return Err("Got stuck while parsing polynomial. This is a bug.".into()); 292 | } 293 | last_i = i; 294 | 295 | // Parse the sign. 296 | let sign = match p[i] { 297 | b'+' => { i += 1; false }, 298 | b'-' => { i += 1; true }, 299 | _ => false, 300 | }; 301 | 302 | // Parse the coefficient. 303 | let mut c = T::one(); 304 | if p[i].is_ascii_digit() { 305 | let start = i; 306 | while i < p.len() && p[i].is_ascii_digit() { 307 | i += 1; 308 | } 309 | 310 | c = ::from_str_radix(&poly[start..i], 10) 311 | .map_err(|_| 312 | "Failed to parse coefficient.".to_owned() 313 | )?; 314 | 315 | if i < p.len() && p[i] == b'*' { 316 | i += 1; 317 | } 318 | } 319 | 320 | if sign { 321 | c = T::zero() - c; 322 | } 323 | 324 | // Parse the exponent. 325 | let mut e = 0; 326 | 327 | // Skip past the `x`. 328 | if i < p.len() && p[i] == b'x' { 329 | i += 1; 330 | e = 1; 331 | 332 | // If there is an exponent, parse it. 333 | if i < p.len() && p[i] == b'^' { 334 | i += 1; 335 | if !p[i].is_ascii_digit() { 336 | return Err("Failed to parse exponent.".into()); 337 | } 338 | e = 0; 339 | while i < p.len() { 340 | if !p[i].is_ascii_digit() { 341 | break; 342 | } 343 | 344 | e *= 10; 345 | e += (p[i] - b'0') as usize; 346 | i += 1; 347 | } 348 | } 349 | } 350 | 351 | if e >= coeffs.len() { 352 | coeffs.resize(e + 1, T::zero()); 353 | } 354 | 355 | coeffs[e] = c; 356 | } 357 | } else { 358 | for c in poly.split_ascii_whitespace() { 359 | let c = ::from_str_radix(c, 10) 360 | .map_err(|_| "Failed to parse coefficient.".to_owned())?; 361 | coeffs.push(c); 362 | } 363 | coeffs.reverse(); 364 | } 365 | 366 | let p = Polynomial { coeffs }; 367 | 368 | Ok(p.truncated()) 369 | } 370 | 371 | /// The ideal of all polynomial expressions that evaluate to 0. 372 | pub struct ZeroIdeal { 373 | /// Mod 2^n. 374 | n: usize, 375 | 376 | /// The generators of the ideal. 377 | gen: Vec>, 378 | } 379 | 380 | impl ZeroIdeal { 381 | pub fn init() -> Self { 382 | let n = std::mem::size_of::() * 8; 383 | 384 | let mut gen = Vec::new(); 385 | 386 | // div stores how often 2 divides i!. 387 | // It is successively updated. 388 | let mut div = 0usize; 389 | for i in (2usize..).step_by(2) { 390 | div += i.trailing_zeros() as usize; 391 | 392 | // If the exponent would be negative 393 | // then add the last generator and stop. 394 | if n <= div { 395 | let mut p = Polynomial::::one(); 396 | 397 | let mut j = T::zero(); 398 | for _ in 0..i { 399 | // Multiply the current polynomial by (x-j). 400 | p.mul_lin(j); 401 | j += T::one(); 402 | } 403 | 404 | p.truncate(); 405 | 406 | gen.push(p); 407 | break; 408 | } 409 | 410 | // Compute the exponent. 411 | let e = n - div; 412 | 413 | // Let's build the polynomial. 414 | let mut p = Polynomial::::one(); 415 | 416 | let mut j = T::zero(); 417 | for _ in 0..i { 418 | // Multiply the current polynomial by (x-j). 419 | p.mul_lin(j); 420 | j += T::one(); 421 | } 422 | 423 | p <<= e; 424 | p.truncate(); 425 | 426 | gen.push(p); 427 | } 428 | 429 | Self { n, gen } 430 | } 431 | } 432 | 433 | impl Polynomial { 434 | /// Returns a simplified polynomial. 435 | pub fn simplified(mut self, zi: &ZeroIdeal) -> Self { 436 | self.simplify(zi); 437 | self 438 | } 439 | 440 | /// Simplifies a polynomial by adding a polynomial in the zero ideal. 441 | pub fn simplify(&mut self, zi: &ZeroIdeal) { 442 | let mut coeff = self.len() - 1; 443 | 444 | for gen in zi.gen.iter().rev() { 445 | let gen_len = gen.len() - 1; 446 | 447 | while coeff >= gen_len { 448 | let m = self.coeffs[coeff] / gen.coeffs[gen_len]; 449 | if m != T::zero() { 450 | let iter = (&mut self.coeffs[coeff-gen_len..=coeff]) 451 | .iter_mut().zip(gen.coeffs.iter()); 452 | 453 | for (p, g) in iter { 454 | *p -= m * *g; 455 | } 456 | } 457 | coeff -= 1; 458 | } 459 | } 460 | 461 | self.truncate(); 462 | } 463 | 464 | /// Reduce the degree of the polynomial as much as possible 465 | /// using the generator of the highest degree. 466 | pub fn reduce(&mut self, zi: &ZeroIdeal) { 467 | let gen = zi.gen.last().unwrap(); 468 | let gen_len = gen.len() - 1; 469 | while self.len() >= gen.len() { 470 | let c = self.coeffs.pop().unwrap(); 471 | for i in 0..gen_len { 472 | let j = self.len() - gen_len + i; 473 | self.coeffs[j] -= c * gen.coeffs[i]; 474 | } 475 | } 476 | 477 | self.truncate(); 478 | } 479 | } -------------------------------------------------------------------------------- /src/polynomial.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Sub, Mul, MulAssign, SubAssign, ShlAssign}; 2 | use std::fmt::{self, Write, Display, Formatter}; 3 | use num_traits::{Num, NumAssign}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Polynomial { 7 | pub coeffs: Vec, 8 | } 9 | 10 | impl Polynomial { 11 | pub fn zero() -> Self { 12 | Self { coeffs: Vec::new() } 13 | } 14 | 15 | pub fn constant(c: T) -> Self { 16 | Self { coeffs: vec![c] } 17 | } 18 | 19 | pub fn from_coeffs + Clone>(v: &[U]) -> Self { 20 | Self { 21 | coeffs: v.iter().map(|e| e.clone().into()).collect() 22 | } 23 | } 24 | 25 | /// The number of coefficients. 26 | pub fn len(&self) -> usize { 27 | self.coeffs.len() 28 | } 29 | 30 | // 31 | // Most functions below only work when the polynomial is truncated. 32 | // 33 | 34 | /// Returns the degree of the polynomial. 35 | /// The degree of 0 is defined to be -1. 36 | pub fn degree(&self) -> isize { 37 | self.coeffs.len() as isize - 1 38 | } 39 | 40 | /// Is this the zero polynomial? 41 | pub fn is_zero(&self) -> bool { 42 | self.len() == 0 43 | } 44 | } 45 | 46 | impl Polynomial { 47 | /// The constant 1 function. 48 | pub fn one() -> Self { 49 | Self::constant(T::one()) 50 | } 51 | 52 | /// Is this the identity polynomial P(X)=X? 53 | pub fn is_id(&self) -> bool { 54 | self.coeffs == &[T::zero(), T::one()] 55 | } 56 | 57 | /// Removes leading zero coefficients. 58 | pub fn truncate(&mut self) { 59 | let num_zero = self.coeffs 60 | .iter() 61 | .rev() 62 | .take_while(|c| **c == T::zero()) 63 | .count(); 64 | 65 | self.coeffs.truncate(self.len() - num_zero); 66 | } 67 | 68 | /// Returns the truncated polynomial. 69 | pub fn truncated(mut self) -> Self { 70 | self.truncate(); 71 | self 72 | } 73 | 74 | /// Evaluate the polynomial at x. 75 | pub fn eval(&self, x: T) -> T { 76 | // This is Horner's method. 77 | 78 | // Iterate over the coefficients in reverse order. 79 | let mut iter = self.coeffs.iter().rev().cloned(); 80 | 81 | // The last coefficient is the initial value. 82 | let mut r = iter.next().unwrap_or(T::zero()); 83 | 84 | for c in iter { 85 | r *= x; 86 | r += c; 87 | } 88 | 89 | r 90 | } 91 | 92 | /// Multiplies the polynomial by (X-a). 93 | pub fn mul_lin(&mut self, a: T) { 94 | // p(x) * (x-a) = p(x) * x - p(x) * a 95 | 96 | // Shift every coefficient to the left 97 | // which corresponds to a multiplication by x. 98 | self.coeffs.insert(0, T::zero()); 99 | 100 | // Now subtract `a` times the original polynomial. 101 | for i in 0..self.coeffs.len() - 1 { 102 | let m = a * self.coeffs[i+1]; 103 | self.coeffs[i] -= m; 104 | } 105 | } 106 | 107 | /// Computes the formal derivative of the polynomial. 108 | pub fn derivative(&self) -> Self { 109 | if self.len() == 0 { 110 | return Self::zero(); 111 | } 112 | 113 | let mut coeffs = Vec::with_capacity(self.len() - 1); 114 | let mut d = T::one(); 115 | for c in self.coeffs[1..].iter() { 116 | coeffs.push(*c * d); 117 | d += T::one(); 118 | } 119 | 120 | Self { coeffs } 121 | } 122 | } 123 | 124 | impl Polynomial { 125 | pub fn to_tex(&self) -> String { 126 | let mut s = String::new(); 127 | 128 | // Iterator over the non-zero coefficients. 129 | let mut iter = self.coeffs 130 | .iter() 131 | .enumerate() 132 | .rev() 133 | .filter(|(e, c)| **c != T::zero()); 134 | 135 | let write_term = |s: &mut String, e, c| { 136 | if e == 0 { 137 | write!(s, "{}", c); 138 | } else { 139 | if c != T::one() { 140 | write!(s, "{}", c); 141 | } 142 | 143 | write!(s, "X"); 144 | 145 | if e != 1 { 146 | write!(s, "^{{{}}}", e); 147 | } 148 | } 149 | }; 150 | 151 | match iter.next() { 152 | None => { write!(s, "0"); }, 153 | Some((e, c)) => write_term(&mut s, e, *c), 154 | }; 155 | 156 | for (e, c) in iter { 157 | s.push_str("+"); 158 | write_term(&mut s, e, *c); 159 | } 160 | 161 | s 162 | } 163 | } 164 | 165 | impl Display for Polynomial { 166 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 167 | // Iterator over the non-zero coefficients. 168 | let mut iter = self.coeffs 169 | .iter() 170 | .enumerate() 171 | .rev() 172 | .filter(|(e, c)| **c != T::zero()); 173 | 174 | match iter.next() { 175 | None => write!(f, "0")?, 176 | Some((e, c)) => if e == 0 { 177 | write!(f, "{}", c)? 178 | } else { 179 | write!(f, "{}x^{}", c, e)? 180 | }, 181 | }; 182 | 183 | for (e, c) in iter { 184 | if e == 0 { 185 | write!(f, " + {}", c)?; 186 | } else { 187 | write!(f, " + {}x^{}", c, e)?; 188 | } 189 | } 190 | 191 | Ok(()) 192 | } 193 | } 194 | 195 | impl Add for &Polynomial { 196 | type Output = Polynomial; 197 | fn add(self, rhs: Self) -> Self::Output { 198 | // Order the polynomials by degree. 199 | let (min, max) = match self.len() >= rhs.len() { 200 | true => (rhs, self), 201 | false => (self, rhs), 202 | }; 203 | 204 | let mut coeffs = Vec::with_capacity(max.len()); 205 | 206 | // Add up all coefficients that exist in both. 207 | self.coeffs.iter() 208 | .zip(rhs.coeffs.iter()) 209 | .for_each(|(l, r)| coeffs.push(*l + *r)); 210 | 211 | // Push the remaining coefficients. 212 | for c in &max.coeffs[min.len()..] { 213 | coeffs.push(*c); 214 | } 215 | 216 | Polynomial { coeffs } 217 | } 218 | } 219 | 220 | impl AddAssign<&Polynomial> for Polynomial { 221 | fn add_assign(&mut self, rhs: &Self) { 222 | // Add the coefficients that exist in both. 223 | self.coeffs.iter_mut() 224 | .zip(rhs.coeffs.iter()) 225 | .for_each(|(l, r)| *l += *r); 226 | 227 | // Push the remaining coefficients should rhs have more. 228 | for c in &rhs.coeffs[self.len()..] { 229 | self.coeffs.push(*c); 230 | } 231 | } 232 | } 233 | 234 | impl AddAssign for Polynomial { 235 | fn add_assign(&mut self, rhs: T) { 236 | if self.coeffs.is_empty() { 237 | self.coeffs.push(rhs); 238 | } else { 239 | self.coeffs[0] += rhs; 240 | } 241 | } 242 | } 243 | 244 | impl Sub for &Polynomial { 245 | type Output = Polynomial; 246 | fn sub(self, rhs: Self) -> Self::Output { 247 | // Order the polynomials by degree. 248 | let (min, max) = match self.len() >= rhs.len() { 249 | true => (rhs, self), 250 | false => (self, rhs), 251 | }; 252 | 253 | let mut coeffs = Vec::with_capacity(max.len()); 254 | 255 | // Add up all coefficients that exist in both. 256 | self.coeffs.iter() 257 | .zip(rhs.coeffs.iter()) 258 | .for_each(|(l, r)| coeffs.push(*l - *r)); 259 | 260 | // Push the remaining coefficients. 261 | for c in &max.coeffs[min.len()..] { 262 | coeffs.push(*c); 263 | } 264 | 265 | Polynomial { coeffs } 266 | } 267 | } 268 | 269 | impl SubAssign<&Polynomial> for Polynomial { 270 | fn sub_assign(&mut self, rhs: &Self) { 271 | // Add the coefficients that exist in both. 272 | self.coeffs.iter_mut() 273 | .zip(rhs.coeffs.iter()) 274 | .for_each(|(l, r)| *l -= *r); 275 | 276 | // Push the remaining coefficients should rhs have more. 277 | for c in &rhs.coeffs[self.len()..] { 278 | self.coeffs.push(T::zero() - *c); 279 | } 280 | } 281 | } 282 | 283 | impl Mul for &Polynomial { 284 | type Output = Polynomial; 285 | fn mul(self, rhs: Self) -> Self::Output { 286 | let mut coeffs = vec![T::zero(); self.len() + rhs.len() - 1]; 287 | for (i, c) in rhs.coeffs.iter().enumerate() { 288 | for (j, d) in self.coeffs.iter().enumerate() { 289 | coeffs[i + j] += *c * *d; 290 | } 291 | } 292 | 293 | Polynomial { coeffs } 294 | } 295 | } 296 | 297 | impl MulAssign<&Polynomial> for Polynomial { 298 | fn mul_assign(&mut self, rhs: &Polynomial) { 299 | *self = &*self * rhs; 300 | } 301 | } 302 | 303 | impl> ShlAssign for Polynomial { 304 | fn shl_assign(&mut self, m: usize) { 305 | for c in &mut self.coeffs { 306 | *c <<= m; 307 | } 308 | } 309 | } -------------------------------------------------------------------------------- /src/printer.rs: -------------------------------------------------------------------------------- 1 | //! Prints the expressions in different formats. 2 | //! The implementation is a bit disgusting. 3 | 4 | use std::fmt::{self, Display, Write, Formatter}; 5 | use crate::uniform_expr::{UExpr, LUExpr}; 6 | use crate::pages::Width; 7 | use crate::numbers::UniformNum; 8 | 9 | use wasm_bindgen::prelude::*; 10 | use num_traits::{Zero, One}; 11 | 12 | /// Determines how the result will be printed. 13 | #[wasm_bindgen] 14 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 15 | pub enum Printer { 16 | /// Some default. 17 | Default, 18 | 19 | /// Print a C function. 20 | C, 21 | 22 | /// Print a rust function. 23 | Rust, 24 | 25 | /// Tex expression. 26 | Tex, 27 | } 28 | 29 | impl Printer { 30 | /// Abbreviation to turn a UExpr into something 31 | /// that is `Display`ed correctly. 32 | fn u(self, e: &'_ UExpr) -> UExprPrinter<'_> { 33 | UExprPrinter { p: self, e } 34 | } 35 | 36 | pub fn print_uexpr(self, e: &UExpr) -> String { 37 | self.u(e).to_string() 38 | } 39 | 40 | pub fn print_luexpr(self, e: &LUExpr) -> String { 41 | let mut s = String::with_capacity(e.0.len() * 8); 42 | match self { 43 | Printer::C | Printer::Default => { 44 | let ty = match std::mem::size_of::() { 45 | 1 => "uint8_t", 46 | 2 => "uint16_t", 47 | 4 => "uint32_t", 48 | 8 => "uint64_t", 49 | 16 => "uint128_t", 50 | s => panic!("Unsupported size: {s}"), 51 | }; 52 | 53 | let vars = e.vars(); 54 | write!(&mut s, "{} f(", ty); 55 | for v in &vars { 56 | write!(&mut s, "{} {}, ", ty, v); 57 | } 58 | 59 | // Remove the last ', '. 60 | s.pop(); 61 | s.pop(); 62 | s += ") {\n\treturn "; 63 | self.print_luexpr_impl(&mut s, e, ""); 64 | s += ";\n}" 65 | }, 66 | Printer::Rust => { 67 | let (ty, const_suffix) = match std::mem::size_of::() { 68 | 1 => ("Wrapping", "u8"), 69 | 2 => ("Wrapping", "u16"), 70 | 4 => ("Wrapping", "u32"), 71 | 8 => ("Wrapping", "u64"), 72 | 16 => ("Wrapping", "u128"), 73 | _ => panic!("Unsupported size: {s}"), 74 | }; 75 | 76 | let vars = e.vars(); 77 | s += "fn f("; 78 | for v in &vars { 79 | write!(&mut s, "{}: {},", v, ty); 80 | } 81 | 82 | // Remove the last ', '. 83 | s.pop(); 84 | s.pop(); 85 | s += ") -> "; 86 | s += ty; 87 | s += " {\n\t"; 88 | self.print_luexpr_impl(&mut s, e, const_suffix); 89 | s += "\n}" 90 | }, 91 | Printer::Tex => self.print_luexpr_impl(&mut s, e, ""), 92 | } 93 | 94 | return s; 95 | } 96 | 97 | fn print_luexpr_impl( 98 | self, 99 | s: &mut String, 100 | e: &LUExpr, 101 | const_suffix: &str, 102 | ) { 103 | let mut iter = e.0.iter() 104 | .filter(|(i, _)| *i != T::zero()) 105 | .map(|(i, e)| (*i, e)); 106 | 107 | let (mut i, e) = match iter.next() { 108 | Some(t) => t, 109 | None => return *s += "0", 110 | }; 111 | 112 | let fmt_term = |s: &mut String, mut i: T, e: &UExpr| { 113 | let unary = e.is_unary(); 114 | let e = self.u(e); 115 | if i == T::one() { 116 | if unary { 117 | write!(s, "{}", e); 118 | } else { 119 | write!(s, "({})", e); 120 | } 121 | } else { 122 | if self == Self::Rust { 123 | if unary { 124 | write!(s, "Wrapping({}{})*{}", i, const_suffix, e); 125 | } else { 126 | write!(s, "Wrapping({}{})*({})", i, const_suffix, e); 127 | } 128 | return; 129 | } 130 | 131 | let op = match self { 132 | Self::Default | Self::C | Self::Rust => "*", 133 | Self::Tex => "\\cdot ", 134 | }; 135 | 136 | if unary { 137 | write!(s, "{}{}{}", i, op, e); 138 | } else { 139 | write!(s, "{}{}({})", i, op, e); 140 | } 141 | } 142 | }; 143 | 144 | if i.print_negative() { 145 | write!(s, "-"); 146 | i = T::zero() - i 147 | } 148 | fmt_term(s, i, e); 149 | 150 | for (i, e) in iter { 151 | let j = if i.print_negative() { 152 | write!(s, " - "); 153 | T::zero() - i 154 | } else { 155 | write!(s, " + "); 156 | i 157 | }; 158 | fmt_term(s, j, e); 159 | } 160 | } 161 | } 162 | 163 | struct UExprPrinter<'a> { 164 | p: Printer, 165 | e: &'a UExpr, 166 | } 167 | 168 | impl<'a> UExprPrinter<'a> { 169 | fn u<'b>(&self, e: &'b UExpr) -> UExprPrinter<'b> { 170 | UExprPrinter { p: self.p, e } 171 | } 172 | 173 | fn write_safe( 174 | &self, l: &UExpr, r: &UExpr, op: &str, f: &mut Formatter<'_> 175 | ) -> fmt::Result { 176 | let l = self.u(l); 177 | let r = self.u(r); 178 | if l.e.is_unary() { 179 | write!(f, "{} {}", l, op)?; 180 | } else { 181 | write!(f, "({}) {}", l, op)?; 182 | } 183 | 184 | if r.e.is_unary() { 185 | write!(f, " {}", r) 186 | } else { 187 | write!(f, " ({})", r) 188 | } 189 | } 190 | } 191 | 192 | impl<'a> Display for UExprPrinter<'a> { 193 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 194 | use UExpr::*; 195 | use Printer::*; 196 | match self.e { 197 | Ones => f.write_str("-1"), 198 | Var(c) => f.write_str(c), 199 | Not(i) => { 200 | let i = self.u(i); 201 | match self.p { 202 | Default | C if i.e.is_unary() => write!(f, "~{}", i), 203 | Default | C => write!(f, "~({})", i), 204 | Rust if i.e.is_unary() => write!(f, "!{}", i), 205 | Rust => write!(f, "!({})", i), 206 | Tex => write!(f, "\\overline{{{}}}", i), 207 | } 208 | }, 209 | And(l, r) => { 210 | match self.p { 211 | Default | C | Rust => self.write_safe(l, r, "&", f), 212 | Tex => self.write_safe(l, r, "\\land", f), 213 | } 214 | }, 215 | Or(l, r) => { 216 | match self.p { 217 | Default | C | Rust => self.write_safe(l, r, "|", f), 218 | Tex => self.write_safe(l, r, "\\lor", f), 219 | } 220 | }, 221 | Xor(l, r) => { 222 | match self.p { 223 | Default | C | Rust => self.write_safe(l, r, "^", f), 224 | Tex => self.write_safe(l, r, "\\oplus", f), 225 | } 226 | } 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /src/uniform_expr.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::fmt::{self, Formatter, Display}; 3 | use std::ops::{Index, IndexMut}; 4 | use std::rc::Rc; 5 | use num_traits::Num; 6 | 7 | use crate::expr::Expr; 8 | use crate::numbers::{UnsignedInt, UniformNum, int_from_it}; 9 | 10 | /// LUExpr is short for "Linear combination of Uniform Expressions" 11 | /// These are the expressions for which rewrite rules can be efficiently 12 | /// generated. 13 | #[derive(Clone, Debug)] 14 | pub struct LUExpr(pub Vec<(T, UExpr)>); 15 | 16 | impl LUExpr { 17 | /// Creates an expression that equals a constant. 18 | pub fn constant(c: T) -> Self { 19 | Self(vec![(T::zero() - c, UExpr::Ones)]) 20 | } 21 | 22 | /// Create a uniform expression. 23 | pub fn from_uexpr(u: UExpr) -> Self { 24 | Self(vec![(T::one(), u)]) 25 | } 26 | 27 | /// Creates an expression that equals a variable. 28 | pub fn var(name: String) -> Self { 29 | Self(vec![(T::one(), UExpr::Var(name))]) 30 | } 31 | 32 | /// Returns all variables in the expression. 33 | /// This will include duplicates. 34 | pub fn vars(&self) -> Vec { 35 | let mut v = BTreeSet::new(); 36 | self.vars_impl(&mut v); 37 | v.into_iter().collect() 38 | } 39 | 40 | pub(crate) fn vars_impl(&self, v: &mut BTreeSet) { 41 | for (_, e) in &self.0 { 42 | e.vars_impl(v); 43 | } 44 | } 45 | 46 | /// Evaluate an expression with a valuation for the occurring variables. 47 | pub fn eval(&self, v: &Valuation) -> T { 48 | self.0.iter() 49 | .map(|(i, e)| *i * e.eval(v)) 50 | .fold(T::zero(), T::add) 51 | } 52 | 53 | /// Parse a string to an expression. 54 | /// Note that this function is extremely limited 55 | /// and expects very specific syntax. 56 | /// It is used for convenience when testing things and 57 | /// not really meant to be used by something outside this crate. 58 | pub(crate) fn from_string(s: String) -> Option { 59 | let mut s = s.to_string(); 60 | s.retain(|c| !c.is_whitespace()); 61 | let mut it = s.chars().peekable(); 62 | 63 | // This stores the current linear combination. 64 | let mut v = Vec::new(); 65 | 66 | let mut neg = false; 67 | 68 | // Loop over the string/the summands. 69 | loop { 70 | // Are there still characters left? 71 | // If not then we're done. 72 | let mut c = match it.peek() { 73 | None => return Some(Self(v)), 74 | Some(c) => *c, 75 | }; 76 | 77 | if c == '-' { 78 | neg = true; 79 | it.next(); 80 | c = *it.peek()?; 81 | } 82 | 83 | // If this is a digit then we expect num*UExpr. 84 | if c.is_ascii_digit() { 85 | // Parse the number. 86 | let mut num = int_from_it(&mut it)?; 87 | 88 | // If the number is negative then negate it. 89 | if neg { 90 | num = T::zero() - num; 91 | } 92 | 93 | // Is it the expected '*'? 94 | match it.peek() { 95 | Some('*') => { 96 | it.next(); 97 | 98 | // Parse the UExpr. 99 | let e = UExpr::parse(&mut it, 0)?; 100 | 101 | // Push it. 102 | v.push((num, e)); 103 | }, 104 | 105 | // If this is a different character then we push -num*(-1). 106 | _ => v.push((T::zero() - num, UExpr::Ones)), 107 | } 108 | } else { 109 | // We don't have a factor so just parse the UExpr. 110 | let e = UExpr::parse(&mut it, 0)?; 111 | 112 | let sign = match neg { 113 | false => T::one(), 114 | true => T::zero() - T::one(), 115 | }; 116 | 117 | // Push sign*e. 118 | v.push((sign, e)); 119 | } 120 | 121 | // If the next character is not a plus then we are done. 122 | match it.peek() { 123 | // Next part of the linear combination. 124 | Some('+') => { neg = false; it.next() }, // Skip the +. 125 | Some('-') => { neg = true; it.next() }, 126 | 127 | // We consumed the whole input so we're good. 128 | None => return Some(Self(v)), 129 | 130 | // There is something left but we can't parse it. 131 | _ => return None, 132 | }; 133 | } 134 | } 135 | 136 | pub fn to_expr(&self) -> Expr { 137 | let mut it = self.0.iter() 138 | .filter(|(f, u)| !f.is_zero()); 139 | 140 | // If the linear combination is empty, 141 | // then it always evaluates to 0. 142 | let Some((f, u)) = it.next() else { 143 | return Expr::zero() 144 | }; 145 | 146 | // Lambda to convert the `coefficient * uexpr` into an expr. 147 | let term = |f: T, u: &UExpr| { 148 | if f.is_one() { 149 | u.to_expr() 150 | } else { 151 | Expr::Mul(Rc::new(Expr::Const(f)), Rc::new(u.to_expr())) 152 | } 153 | }; 154 | 155 | // Iterate over the linear combination and update e. 156 | let mut e = term(*f, u); 157 | for (f, u) in it { 158 | e = Expr::Add(Rc::new(e), Rc::new(term(*f, u))); 159 | } 160 | 161 | e 162 | } 163 | } 164 | 165 | impl From for LUExpr { 166 | fn from(e: UExpr) -> Self { 167 | Self(vec![(T::one(), e)]) 168 | } 169 | } 170 | 171 | impl Display for LUExpr { 172 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 173 | let mut iter = self.0.iter() 174 | .filter(|(i, _)| *i != T::zero()) 175 | .map(|(i, e)| (*i, e)); 176 | let (mut i, e) = match iter.next() { 177 | Some(t) => t, 178 | None => return write!(f, "0"), 179 | }; 180 | 181 | let fmt_term = |f: &mut Formatter<'_>, i: T, e: &UExpr| { 182 | if i == T::one() { 183 | write!(f, "{}", e) 184 | } else { 185 | if e.is_unary() { 186 | write!(f, "{}*{}", i, e) 187 | } else { 188 | write!(f, "{}*({})", i, e) 189 | } 190 | } 191 | }; 192 | 193 | if i.print_negative() { 194 | write!(f, "-")?; 195 | i = T::zero() - i 196 | } 197 | fmt_term(f, i, e)?; 198 | 199 | for (i, e) in iter { 200 | let j = if i.print_negative() { 201 | write!(f, " - ")?; 202 | T::zero() - i 203 | } else { 204 | write!(f, " + ")?; 205 | i 206 | }; 207 | fmt_term(f, j, e); 208 | } 209 | 210 | Ok(()) 211 | } 212 | } 213 | 214 | /// Represents an expression that is uniform on all bits. 215 | /// Note that the variant 'Ones' does not equal 1, but a 1 in every bit, 216 | /// which is -1 in two's complement. 217 | #[derive(Clone, Debug, PartialEq, Eq)] 218 | pub enum UExpr { 219 | Ones, 220 | Var(String), 221 | Not(Box), 222 | And(Box, Box), 223 | Or(Box, Box), 224 | Xor(Box, Box), 225 | } 226 | 227 | impl UExpr { 228 | pub fn var(c: String) -> Self { 229 | Self::Var(c) 230 | } 231 | 232 | pub fn not(e: Self) -> Self { 233 | Self::Not(e.into()) 234 | } 235 | 236 | pub fn and(l: Self, r: Self) -> Self { 237 | Self::And(l.into(), r.into()) 238 | } 239 | 240 | pub fn or(l: Self, r: Self) -> Self { 241 | Self::Or(l.into(), r.into()) 242 | } 243 | 244 | pub fn xor(l: Self, r: Self) -> Self { 245 | Self::Xor(l.into(), r.into()) 246 | } 247 | 248 | /// Is the top-most operator unary. 249 | pub fn is_unary(&self) -> bool { 250 | use UExpr::*; 251 | match self { 252 | Ones | Var(_) | Not(_) => true, 253 | _ => false, 254 | } 255 | } 256 | 257 | /// Returns all variables in the expression. 258 | /// This will include duplicates. 259 | pub fn vars(&self) -> Vec { 260 | let mut v = BTreeSet::new(); 261 | self.vars_impl(&mut v); 262 | v.into_iter().collect() 263 | } 264 | 265 | // Should really be `pub(self)`. 266 | pub(crate) fn vars_impl(&self, v: &mut BTreeSet) { 267 | use UExpr::*; 268 | match self { 269 | Ones => {}, 270 | Var(c) => if !v.contains(c) { v.insert(c.clone()); }, 271 | Not(e) => e.vars_impl(v), 272 | And(e1, e2) => { e1.vars_impl(v); e2.vars_impl(v) }, 273 | Or(e1, e2) => { e1.vars_impl(v); e2.vars_impl(v) }, 274 | Xor(e1, e2) => { e1.vars_impl(v); e2.vars_impl(v) }, 275 | } 276 | } 277 | 278 | /// Evaluate an expression with a valuation for the occurring variables. 279 | pub fn eval(&self, v: &Valuation) -> T { 280 | use UExpr::*; 281 | match self { 282 | Ones => T::zero() - T::one(), // -1 283 | Var(c) => v[c], 284 | Not(e) => !e.eval(v), 285 | And(e1, e2) => e1.eval(v) & e2.eval(v), 286 | Or(e1, e2) => e1.eval(v) | e2.eval(v), 287 | Xor(e1, e2) => e1.eval(v) ^ e2.eval(v), 288 | } 289 | } 290 | 291 | /// Rename a variable. 292 | pub fn rename_var(&mut self, old: &str, new: &str) { 293 | use UExpr::*; 294 | match self { 295 | Ones => (), 296 | Var(v) => if v == old { v.clear(); v.push_str(new) }, 297 | Not(e) => e.rename_var(old, new), 298 | And(l, r) => { l.rename_var(old, new); r.rename_var(old, new) }, 299 | Or(l, r) => { l.rename_var(old, new); r.rename_var(old, new) }, 300 | Xor(l, r) => { l.rename_var(old, new); r.rename_var(old, new) }, 301 | } 302 | } 303 | 304 | pub(crate) fn write_safe( 305 | e1: &Self, e2: &Self, op: &str, f: &mut std::fmt::Formatter<'_> 306 | ) -> std::fmt::Result { 307 | if e1.is_unary() { 308 | write!(f, "{} {}", e1, op)?; 309 | } else { 310 | write!(f, "({}) {}", e1, op)?; 311 | } 312 | 313 | if e2.is_unary() { 314 | write!(f, " {}", e2) 315 | } else { 316 | write!(f, " ({})", e2) 317 | } 318 | } 319 | 320 | /// Parse a string to an expression. 321 | pub(crate) fn from_string(s: T) -> Option { 322 | let mut s = s.to_string(); 323 | s.retain(|c| !c.is_whitespace()); 324 | let mut it = s.chars().peekable(); 325 | 326 | Self::parse(&mut it, 0) 327 | .filter(|_| it.next().is_none()) 328 | } 329 | 330 | pub(self) fn parse( 331 | it: &mut std::iter::Peekable, 332 | pre: usize 333 | ) -> Option { 334 | use UExpr::*; 335 | 336 | let c = *it.peek()?; 337 | 338 | let mut e = if c == '(' { 339 | it.next(); 340 | let e = Self::parse(it, 0)?; 341 | match it.next() { 342 | Some(')') => e, 343 | _ => return None, 344 | } 345 | } else if c == '~' || c == '!' { 346 | it.next(); 347 | let e = Self::parse(it, 15)?; 348 | Not(Box::new(e)) 349 | } else if c.is_alphabetic() { 350 | it.next(); 351 | let mut var = String::from(c); 352 | loop { 353 | let Some(c) = it.peek() else { 354 | break 355 | }; 356 | 357 | if !c.is_alphanumeric() { 358 | break 359 | } 360 | 361 | var.push(*c); 362 | it.next(); 363 | } 364 | 365 | Var(var) 366 | } else if c == '-' { 367 | it.next(); 368 | // Parse a -1. 369 | match it.next() { 370 | Some('1') => Ones, 371 | _ => return None, 372 | } 373 | } else { 374 | return None; 375 | }; 376 | 377 | loop { 378 | let c = match it.peek() { 379 | None => return Some(e), 380 | Some(c) => *c, 381 | }; 382 | 383 | let op_pre = match c { 384 | '|' => 1, 385 | '^' => 2, 386 | '&' => 3, 387 | _ => return Some(e), 388 | }; 389 | 390 | if op_pre <= pre { 391 | return Some(e); 392 | } 393 | 394 | // If the current operators precedence is higher than 395 | // the one whose subexpressions we are currently parsing 396 | // then we need to finish this operator first. 397 | it.next(); 398 | let rhs = Box::new(Self::parse(it, op_pre)?); 399 | let lhs = Box::new(e); 400 | e = match c { 401 | '&' => And(lhs, rhs), 402 | '|' => Or(lhs, rhs), 403 | '^' => Xor(lhs, rhs), 404 | _ => return None, 405 | }; 406 | } 407 | } 408 | 409 | pub fn to_expr(&self) -> Expr { 410 | match self { 411 | UExpr::Ones => Expr::Const(T::zero() - T::one()), 412 | UExpr::Var(v) => Expr::Var(v.clone()), 413 | UExpr::Not(e) => Expr::Not(Rc::new(e.to_expr::())), 414 | UExpr::And(l, r) => Expr::And(Rc::new(l.to_expr::()), Rc::new(r.to_expr::())), 415 | UExpr::Or(l, r) => Expr::Or(Rc::new(l.to_expr::()), Rc::new(r.to_expr::())), 416 | UExpr::Xor(l, r) => Expr::Xor(Rc::new(l.to_expr::()), Rc::new(r.to_expr::())), 417 | } 418 | } 419 | } 420 | 421 | impl std::fmt::Display for UExpr { 422 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 423 | use UExpr::*; 424 | match self { 425 | Ones => write!(f, "-1"), 426 | Var(c) => write!(f, "{}", c), 427 | And(e1, e2) => Self::write_safe(e1, e2, "&", f), 428 | Or(e1, e2) => Self::write_safe(e1, e2, "|", f), 429 | Xor(e1, e2) => Self::write_safe(e1, e2, "^", f), 430 | Not(e) => 431 | if e.is_unary() { 432 | write!(f, "~{}", e) 433 | } else { 434 | write!(f, "~({})", e) 435 | }, 436 | } 437 | } 438 | } 439 | 440 | /// Stores values that should be substituted into variables. 441 | #[derive(Debug)] 442 | pub struct Valuation { 443 | /// The key value pairs are stored as a Vector 444 | /// because I doubt a hashmap/tree would be faster 445 | /// when there are so few variables. 446 | vals: Vec<(String, T)>, 447 | } 448 | 449 | impl Valuation { 450 | /// Initializes a valuation from a list of variables 451 | /// each of which will be Initialized to 0. 452 | pub fn zero(vars: Vec) -> Self { 453 | let vals = vars.into_iter() 454 | .map(|c| (c, T::zero())) 455 | .collect(); 456 | 457 | Self { vals } 458 | } 459 | } 460 | 461 | impl Index<&str> for Valuation { 462 | type Output = T; 463 | fn index(&self, index: &str) -> &Self::Output { 464 | &self.vals.iter() 465 | .find(|(name, _)| *name == index) 466 | .unwrap().1 467 | } 468 | } 469 | 470 | impl IndexMut<&str> for Valuation { 471 | fn index_mut(&mut self, index: &str) -> &mut Self::Output { 472 | &mut self.vals.iter_mut() 473 | .find(|(name, _)| *name == index) 474 | .unwrap().1 475 | } 476 | } -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use std::ops::{Index, IndexMut, AddAssign, Mul}; 3 | use std::fmt::Write; 4 | 5 | use num_traits::Num; 6 | 7 | use crate::numbers::UnsignedInt; 8 | 9 | #[derive(Clone)] 10 | pub struct Vector { 11 | /// The number of entries in the vector. 12 | pub dim: usize, 13 | 14 | /// Memory that holds the entries. 15 | pub(self) entries: Box<[T]>, 16 | } 17 | 18 | impl Vector { 19 | /// Returns an empty vector. 20 | pub fn empty() -> Self { 21 | Self { 22 | dim: 0, 23 | entries: Box::new([]), 24 | } 25 | } 26 | 27 | /// Is the vector empty. 28 | pub fn is_empty(&self) -> bool { 29 | self.dim == 0 30 | } 31 | 32 | /// Creates a vector from a slice. 33 | pub fn from_slice(s: &[U]) -> Self 34 | where T: From, 35 | { 36 | Self { 37 | dim: s.len(), 38 | entries: s.iter() 39 | .map(|e| T::from(e.clone())) 40 | .collect::>() 41 | .into_boxed_slice() 42 | } 43 | } 44 | 45 | /// Returns a slice of the entries. 46 | pub fn entries(&self) -> &[T] { 47 | &self.entries 48 | } 49 | 50 | /// Returns a mutable slice of the entries. 51 | pub fn entries_mut(&mut self) -> &mut [T] { 52 | &mut self.entries 53 | } 54 | 55 | /// Returns an iterator over the elements. 56 | pub fn iter(&self) -> std::slice::Iter<'_, T> { 57 | self.entries().iter() 58 | } 59 | 60 | /// Returns an iterator over mutable references to the elements. 61 | pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> { 62 | self.entries_mut().iter_mut() 63 | } 64 | 65 | /// Returns an reference to an entry. 66 | pub fn entry(&self, i: usize) -> &T { 67 | &self.entries[i] 68 | } 69 | 70 | /// Returns a mutable reference to an entry. 71 | pub fn entry_mut(&mut self, i: usize) -> &mut T { 72 | &mut self.entries[i] 73 | } 74 | 75 | /// Apply a function to each entry. 76 | pub fn map(&self, mut f: F) -> Vector 77 | where F: FnMut(&T) -> U 78 | { 79 | let mut v = Vec::with_capacity(self.entries.len()); 80 | for e in self.entries.iter() { 81 | v.push(f(e)); 82 | } 83 | 84 | Vector:: { 85 | dim: self.dim, 86 | entries: v.into_boxed_slice() 87 | } 88 | } 89 | 90 | /// Apply a function to each entry that can fail. 91 | /// If the function fails for the first time, then that resulting error 92 | /// and the location of the entry is returned. 93 | pub fn try_map(&self, mut f: F) -> Result, (usize, E)> 94 | where F: FnMut(&T) -> Result 95 | { 96 | let mut v = Vec::with_capacity(self.entries.len()); 97 | for e in self.entries.iter() { 98 | match f(e) { 99 | Ok(e) => v.push(e), 100 | Err(e) => { 101 | return Err((v.len(), e)); 102 | } 103 | } 104 | } 105 | 106 | Ok(Vector:: { 107 | dim: self.dim, 108 | entries: v.into_boxed_slice() 109 | }) 110 | } 111 | } 112 | 113 | impl Vector { 114 | /// Creates a new vector whose entries are initialized with `val`. 115 | pub fn uniform(dim: usize, val: T) -> Self { 116 | Self { 117 | dim, 118 | entries: vec![val; dim].into_boxed_slice(), 119 | } 120 | } 121 | } 122 | 123 | impl Vector { 124 | /// Returns a zero vector. 125 | pub fn zero(dim: usize) -> Self { 126 | Self { 127 | dim, 128 | entries: vec![T::zero(); dim].into_boxed_slice(), 129 | } 130 | } 131 | 132 | /// Is this the zero vector? 133 | pub fn is_zero(&self) -> bool { 134 | self.iter().all(|e| *e == T::zero()) 135 | } 136 | } 137 | 138 | impl Vector { 139 | pub fn to_tex(&self) -> String { 140 | let mut s = "\\left[\\begin{array}{}".to_owned(); 141 | for e in self.iter().cloned() { 142 | if (e.print_negative()) { 143 | write!(&mut s, "-{}\\\\", T::zero() - e); 144 | } else { 145 | write!(&mut s, "{}\\\\", e); 146 | } 147 | } 148 | s += "\\end{array}\\right]"; 149 | s 150 | } 151 | } 152 | 153 | impl Index for Vector { 154 | type Output = T; 155 | 156 | fn index(&self, index: usize) -> &Self::Output { 157 | self.entry(index) 158 | } 159 | } 160 | 161 | impl IndexMut for Vector { 162 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 163 | self.entry_mut(index) 164 | } 165 | } 166 | 167 | impl Mul for Vector { 168 | type Output = Vector; 169 | fn mul(mut self, rhs: T) -> Self::Output { 170 | for e in self.iter_mut() { 171 | *e = *e * rhs; 172 | } 173 | self 174 | } 175 | } 176 | 177 | impl AddAssign<&Vector> for Vector { 178 | fn add_assign(&mut self, rhs: &Vector) { 179 | debug_assert!(self.dim == rhs.dim); 180 | for (e, f) in self.iter_mut().zip(rhs.iter()) { 181 | *e = *e + *f; 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir local 3 | ln www/* local/ 4 | ln pkg/mba_wasm.js pkg/mba_wasm_bg.wasm local 5 | python -m http.server -d local 6 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mixed Boolean-Arithmetic 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

Mixed Boolean-Arithmetic Obfuscation

26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | 63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 | 75 |
76 |
77 |
78 | 79 |
80 |
81 |
82 |
83 |

84 | 87 |

88 |
89 |
90 |
91 |
92 |
93 |

94 | 97 |

98 |
99 |
100 | Check out my series of blog posts. 101 |
102 | You can find the code for this page (including the algorithm) here. 103 |
104 | My initial (non-WASM) implementation with more features can be found here. 105 |
106 | The current implementation only obfuscates linear MBA subexpressions and leaves the non-linear parts as is, 107 | which can be deobfuscated relatively easily. 108 | I am working on a non-linear version that works like the constant obfuscation at the end of the first blog post. 109 |
110 |
111 |
112 |
113 |

114 | 117 |

118 |
119 |
120 | Some other things that are used during the obfuscation process: 121 |
122 | Linear MBA: 123 | Implements only linear MBA but allows you to choose the rewrite operations. 124 |
125 | Systems of Linear Equations mod n: 126 | Linear MBA rewriting at its core is solving such a linear system. 127 |
128 | Permutation Polynomials: 129 | This lets you generate and invert binary permutation polynomials. 130 | They are not used in the obfuscation process (for now?) but the origin paper that 131 | introduced MBA mentions them as a way to generate non-linear MBA. 132 |
133 |
134 |
135 |
136 | 137 | -------------------------------------------------------------------------------- /www/linear_congruences.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Linear Congruence solver 6 | 7 | 8 | 9 | 10 | 11 | 12 |

System of Linear Congruences Solver

13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 | Bits 22 | 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /www/linear_congruences.js: -------------------------------------------------------------------------------- 1 | import './mathjax.js' 2 | import { solve_congruences, Width } from './wasm.js'; 3 | 4 | // Global tex render options. 5 | let tex_options = { scale: 1.2 } 6 | 7 | // Get a bunch of DOM elements we need. 8 | const mat = document.getElementById('matrix') 9 | const mod = document.getElementById('modulus') 10 | const b = document.getElementById('solve-btn') 11 | const error = document.getElementById('err') 12 | const system = document.getElementById('system') 13 | const math = document.getElementById('math') 14 | 15 | // Changing the matrix text field updates the tex'd system. 16 | const show_eqs = () => { 17 | MathJax.reset() 18 | let eqs = '\\begin{align}' 19 | for (let line of mat.value.split('\n')) { 20 | line = line.trim() 21 | if (line === '') 22 | continue; 23 | 24 | let elements = line.split(' ') 25 | let rhs = '' 26 | if (elements.length > 1) { 27 | rhs = elements.pop() 28 | } 29 | for (let i = 0; i < elements.length; ++i) { 30 | eqs += elements[i] 31 | eqs += `x_{${i+1}}&+` 32 | } 33 | 34 | eqs = eqs.slice(0, -1) 35 | eqs += `\\equiv ${rhs}&\\pmod{2^{${mod.options[mod.selectedIndex].label}}}\\\\` 36 | } 37 | eqs += '\\end{align}' 38 | 39 | system.replaceChildren() 40 | system.appendChild(MathJax.tex2chtml(eqs, tex_options)) 41 | 42 | MathJax.set_css('mjx-system-styles') 43 | } 44 | 45 | // Draw the equations once. 46 | show_eqs() 47 | 48 | // Update when the matrix or the modulus changes. 49 | mat.oninput = mod.oninput = show_eqs 50 | 51 | // Clicking the button solves the system. 52 | b.onclick = () => { 53 | try { 54 | error.textContent = '' 55 | 56 | let add_text = (str) => { 57 | math.appendChild(document.createTextNode(str)) 58 | } 59 | let add_math = (str) => { 60 | math.appendChild(MathJax.tex2chtml(str, tex_options)) 61 | } 62 | 63 | let s = solve_congruences(mat.value, Width[mod.value]) 64 | 65 | MathJax.reset() 66 | math.replaceChildren() 67 | add_text('The diagonalization of the matrix A is') 68 | add_math(s.diag) 69 | 70 | add_text('The new system is') 71 | add_math(s.scalar_system) 72 | 73 | add_text('which results in the single variable linear congruences') 74 | add_math(s.linear_solutions) 75 | 76 | if (s.vector_solution.length === 0) { 77 | add_text('So overall the system has no solution.') 78 | } else { 79 | add_text('The vector form is') 80 | add_math(s.vector_solution) 81 | 82 | add_text('The final solution to the original system is') 83 | add_math(s.final_solution) 84 | } 85 | 86 | MathJax.set_css('mjx-solution-styles') 87 | } catch (err) { 88 | if (typeof err === "string") { 89 | error.textContent = err 90 | } else { 91 | console.log(err) 92 | error.textContent = 'Error. Check console.' 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /www/linear_mba.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Linear Mixed Boolean-Arithmetic 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Mixed Boolean-Arithmetic Obfuscation

25 | 26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 |
50 |
51 |
    52 |
  • Rewrite operations:
  • 53 |
  • 54 |
    55 | 56 | 57 |
    58 |
    Invalid operation
    59 |
  • 60 |
61 |
62 |
63 |
64 | 65 | 66 |
67 | 78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |

86 | 89 |

90 |
91 |
92 |
93 |
94 |
95 |

96 | 99 |

100 |
101 |
102 |
103 |
104 |
105 |

106 | 109 |

110 |
111 |
112 | Check out my series of blog posts. 113 |
114 | You can find the code for this page (including the algorithm) here. 115 |
116 |
117 |
118 |
119 | 120 | -------------------------------------------------------------------------------- /www/linear_mba.js: -------------------------------------------------------------------------------- 1 | import { obfuscate_linear, normalize_op, ObfLinReq, Width, Printer } from './wasm.js'; 2 | import './mathjax.js' 3 | 4 | const btn = document.getElementById('obfuscate-btn') 5 | const input = document.getElementById('input') 6 | const output = document.getElementById('output') 7 | const input_error = document.getElementById('input-error') 8 | const ops = document.getElementById('ops') 9 | const op_input = document.getElementById('op-input') 10 | const op_add = document.getElementById('op-add') 11 | const op_add_item = document.getElementById('op-add-item') 12 | const randomize = document.getElementById('random') 13 | const output_type = document.getElementById('output-type') 14 | const output_types = document.getElementsByName('output-type') 15 | 16 | // Highlights inline code. 17 | function hi_in(code) { 18 | return `${Prism.highlight(code, Prism.languages.c, 'c')}` 19 | } 20 | 21 | // Part of the popover config supplying default values. 22 | const popover_config = { 23 | trigger: 'hover', 24 | delay: { show: 500, hide: 100 }, 25 | html: true, 26 | customClass: 'custom-popover', 27 | } 28 | 29 | // Popover for the input box. 30 | new bootstrap.Popover(input, { 31 | ...popover_config, 32 | title: 'Expression that will be obfuscated', 33 | content: 34 | ` 35 | Currently, the expression has to be a linear combination of boolean expressions. 36 | Check 'What operations are allowed?' at the bottom of the page. 37 |
38 | E.g. ${hi_in('3*(x & ~y) + 4*(x | y) - 2*~x')}. 39 |
40 | This limitation will be removed in the future and more general expression will be allowed. 41 | More commonly, you will probably want to use things like ${hi_in('x + y')}, ${hi_in('x - y')}. 42 |
43 | Constants (${hi_in('1312')}) are also allowed. 44 | ` 45 | }) 46 | 47 | // Popover for the rewrite operation input. 48 | new bootstrap.Popover(op_input, { 49 | ...popover_config, 50 | title: 'Operations used during rewriting', 51 | content: 52 | ` 53 | These are the operations that are allowed to appear in the resulting linear combination. 54 | Only linear combinations of boolean operations are allowed. 55 | ` 56 | }) 57 | 58 | // Popover for the 'random solution' button. 59 | new bootstrap.Popover(document.getElementById('randomize-div'), { 60 | ...popover_config, 61 | title: `Generate a random output`, 62 | content: 63 | ` 64 | Rewriting the input using the operations involves solving a 'System of Linear Congruences', 65 | which is very similar to 'Systems of Linear Equations' that are known from Linear Algebra. 66 | In the same way the solution also is a particular solution plus any vector in the kernel. 67 | If randomize solution is disabled, the particular solution that the algorithm returns is used. 68 | Since the algorithm is deterministic it will always be the same. 69 | Note that changing the order of the rewrite operations can change the solution. 70 | To get a canonical solution, we could define some sort of norm and try to find the smallest 71 | solution according to that norm, but this is future work. 72 | ` 73 | }) 74 | 75 | // 'What is Mixed Boolean-Arithmetic?' 76 | document.getElementById('acc-col-1').children[0].innerHTML = 77 | ` 78 | Mixed Boolean-Arithmetic (MBA) is a name for expressions which contain both the usual arithmetic operations 79 | (${hi_in('+')}, ${hi_in('-')}, ${hi_in('*')}, ${hi_in('/')}) 80 | as well as boolean operations (${hi_in('&')}, ${hi_in('|')}, ${hi_in('^')}, ${hi_in('~')}). 81 | A simple example is the expression ${hi_in('2 * (x & y) + (x ^ y)')} that computes ${hi_in('x + y')}. 82 | This particular one works for integers of any number of bits but generally they can be specific to 83 | a certain size, such as the following one, that also computes ${hi_in('x + y')} but only for 8-bit integers: 84 |
${Prism.highlight('-38*(x & y) - 83*(x ^ y) - 64*~(x ^ (y ^ z))\n - 41*~x - 43*~y - 23*y - 44*z - 20*(y & z)\n - 21*(x | z) - 107*(~x & z) - 108*(y | ~z)', Prism.languages.c, 'c')}
85 | ` 86 | 87 | // 'What operations are allowed?' 88 | document.getElementById('acc-col-2').children[0].innerHTML = 89 | ` 90 | Currently, this site can only generate Linear MBA expressions, which are linear combinations of boolean operations, 91 | such as ${hi_in('24*(x & y) - 22*(x | y) - 105*(x ^ y) + 128*~x + 128*~y')}. 92 | The allowed boolean operations are ${hi_in('x & y')} (and), ${hi_in('x | y')} (or), ${hi_in('x ^ y')} (xor), ${hi_in('~x')} (equivalently ${hi_in('!x')}) (not) and ${hi_in('-1')}. 93 | ${hi_in('-1')} is the mnemonic for the constant 1 function on each bit, which (when interpreted in two's complement) has the value -1. 94 | Note that ${hi_in('!')} is an alias for the logical NOT and the same as ${hi_in('~')} here, whereas this is not the case in C. 95 | Of course the operations can also be nested: ${hi_in('x & (y | !z)')}. 96 | Constants ${hi_in('1312')} are also allowed as part of the linear combination and are represented internally as ${hi_in('-1312*(-1)')}. 97 |

98 | The rewrite operations are usually just the boolean operations that appear in the output linear combination, 99 | but they can be linear combinations themselves, as using those is equivalent to restricting the coefficients 100 | of the output. 101 |

102 | Additionally, the input expression has to be a Linear MBA expression as well, but this will be relaxed in the future. 103 | The idea is to obfuscate parts of a general expression that are Linear MBA and substitute those in, so often it will 104 | just be something like ${hi_in('x+y')}. 105 | The rewrite operations are usually the boolean operations that appear in the linear combination, 106 | but can also be linear combinations of boolean operations as using those is equivalent to restricting 107 | the coefficients in the output linear combination. 108 | ` 109 | 110 | // Setup handling for the output type dropdown. 111 | for (const li of output_types) { 112 | li.onclick = (e) => { 113 | for (const li of output_types) { 114 | li.classList.remove('active') 115 | } 116 | e.target.classList.add('active') 117 | output_type.textContent = e.target.textContent 118 | } 119 | } 120 | 121 | // Remove an operation from the op list. 122 | const remove_op = (e) => { 123 | e.target.parentNode.remove() 124 | } 125 | 126 | // Add an operation to the list of operations used for rewriting. 127 | const add_op = () => { 128 | // Normalize the operation and make sure it is valid. 129 | const bits = Width[document.querySelector('input[name=bitness]:checked').value] 130 | const s = normalize_op(op_input.value, bits) 131 | 132 | // If it isn't, indicate that. 133 | if (s === '') { 134 | op_input.classList.add('is-invalid') 135 | op_input.parentElement.classList.add('is-invalid') 136 | return 137 | } 138 | 139 | // Remove potential prior indication. 140 | op_input.classList.remove('is-invalid') 141 | op_input.parentElement.classList.remove('is-invalid') 142 | 143 | // Create a new list item. 144 | const list_item = document.createElement('li') 145 | list_item.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-start') 146 | 147 | const op_text = document.createElement('div') 148 | op_text.classList.add('ms-2', 'me-auto') 149 | op_text.textContent = s 150 | op_text.setAttribute('name', 'op-value') 151 | list_item.appendChild(op_text) 152 | 153 | const remove_btn = document.createElement('button') 154 | remove_btn.classList.add('btn-close') 155 | remove_btn.type = 'button' 156 | remove_btn.onclick = remove_op 157 | list_item.appendChild(remove_btn) 158 | 159 | // Insert it before the last item, which is the one 160 | // that contains the textfield and button. 161 | ops.insertBefore(list_item, op_add_item) 162 | } 163 | 164 | op_add.onclick = add_op 165 | op_input.onkeydown = (e) => { 166 | if (e.key == "Enter") 167 | add_op() 168 | } 169 | 170 | // Do the obfuscation. 171 | btn.onclick = () => { 172 | let req = new ObfLinReq() 173 | req.expr = input.value 174 | req.randomize = randomize.checked 175 | 176 | const printer = Printer[output_type.innerText.trim()] 177 | req.printer = printer 178 | 179 | // Collect the rewrite ops. 180 | for (const e of document.getElementsByName('op-value')) { 181 | req.add_op(e.innerText) 182 | } 183 | 184 | // Get the number of bits we are obfuscating for. 185 | const bits = Width[document.querySelector('input[name=bitness]:checked').value] 186 | req.bits = bits 187 | 188 | try { 189 | // Do the rewriting. 190 | const s = obfuscate_linear(req) 191 | input.classList.remove('is-invalid') 192 | input_error.textContent = '' 193 | 194 | // Display the result. 195 | output.replaceChildren() 196 | if (printer == Printer.C) { 197 | const code = document.createElement('pre') 198 | code.classList.add('language-c') 199 | code.innerHTML = Prism.highlight(s, Prism.languages.c, 'c') 200 | output.appendChild(code) 201 | 202 | // Very hacky and requires the code to contain commas only for the arguments. 203 | const args = s.split(',').map(() => '0').join(', ') 204 | const ce_code = encodeURIComponent(`#include \n#include \n\n${s}\n\nint main() {\n\tstd::cout << ${bits == Width.U8 ? '(uint32_t)' : ''}f(${args}) << "\\n";\n}`) 205 | const ce_btn = document.createElement('button') 206 | ce_btn.textContent = 'Open in Compiler Explorer' 207 | ce_btn.classList.add('btn', 'btn-secondary') 208 | ce_btn.onclick = () => { 209 | window.open(`https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:'${ce_code}'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:executor,i:(argsPanelShown:'1',compilationPanelShown:'0',compiler:clang_trunk,compilerOutShown:'0',execArgs:'',execStdin:'',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'',source:1,stdinPanelShown:'1',tree:'1',wrap:'1'),l:'5',n:'0',o:'Executor+x86-64+clang+(trunk)+(C%2B%2B,+Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4`) 210 | } 211 | output.appendChild(ce_btn) 212 | } else if (printer == Printer.Rust) { 213 | const code = document.createElement('pre') 214 | code.classList.add('language-rust') 215 | code.innerHTML = Prism.highlight(s, Prism.languages.rust, 'rust') 216 | output.appendChild(code) 217 | 218 | const pg_btn = document.createElement('button') 219 | pg_btn.textContent = 'Open in Rust Playground' 220 | pg_btn.classList.add('btn', 'btn-secondary') 221 | 222 | // Very hacky and requires the code to contain commas only for the arguments. 223 | const args = s.split(',').map(() => 'Wrapping(0)').join(', ') 224 | const pg_code = encodeURIComponent(`use std::num::Wrapping;\n\nfn main() {\n\tprintln!("{}", f(${args}));\n}\n\n${s}`) 225 | pg_btn.onclick = () => { 226 | window.open(`https://play.rust-lang.org/?version=stable&mode=release&edition=2021&code=${pg_code}`) 227 | } 228 | output.appendChild(pg_btn) 229 | } else if (printer == Printer.Tex) { 230 | MathJax.reset() 231 | output.appendChild(MathJax.tex2chtml(s, { scale: 1.3 })) 232 | MathJax.set_css('mathjax-styles') 233 | } else { 234 | output.textContent = s 235 | } 236 | } catch (err) { 237 | input.classList.add('is-invalid') 238 | output.textContent = '' 239 | if (typeof err === 'string') { 240 | input_error.textContent = err 241 | } else { 242 | console.log(err) 243 | input_error.textContent = 'Unknown error. Check console.' 244 | } 245 | } 246 | } -------------------------------------------------------------------------------- /www/mathjax.js: -------------------------------------------------------------------------------- 1 | // This module synchronously loads mathjax on import with custom settings. 2 | 3 | // This is using an XMLHttpRequest to fetch the js and execute it with window.eval. 4 | // Since synchronous XMLHttpRequests in the main thread are deprecated, this is a bit hacky. 5 | // We use a asynchronous request and in the listener we resolve a promise which we wait for. 6 | await new Promise((resolve) => { 7 | window.MathJax = { 8 | startup: { 9 | typeset: false, 10 | }, 11 | options: { 12 | enableMenu: false, 13 | }, 14 | chtml: { 15 | fontURL: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2', 16 | }, 17 | } 18 | 19 | const req = new XMLHttpRequest() 20 | req.open('GET', 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js') 21 | req.addEventListener('load', () => { 22 | // This stops styles for the (disabled) context menu from being loaded. 23 | // const code = req.responseText.replace(',y.CssStyles.addInfoStyles(this.document.document),y.CssStyles.addMenuStyles(this.document.document)', '') 24 | const code = req.responseText 25 | // Run the java script. 26 | window.eval(code) 27 | 28 | // Associate a function with MathJax that resets all state. 29 | window.MathJax.reset = () => { 30 | window.MathJax.typesetClear() 31 | window.MathJax.texReset() 32 | window.MathJax.startup.output.clearCache() 33 | } 34 | 35 | // Associate a function with MathJax that sets the css. 36 | window.MathJax.set_css = (id) => { 37 | let sheet = document.getElementById(id) 38 | if (sheet) 39 | sheet.remove() 40 | let css = window.MathJax.chtmlStylesheet() 41 | css.id = id 42 | // Remove some annoying css that gives me warnings. 43 | // css.innerText = css.innerText.replace( 44 | // '\n_::-webkit-full-page-media, _:future, :root mjx-container {\n will-change: opacity;\n}', 45 | // '') 46 | document.head.appendChild(css) 47 | } 48 | resolve() 49 | }) 50 | req.send() 51 | }) -------------------------------------------------------------------------------- /www/obfuscate.js: -------------------------------------------------------------------------------- 1 | import { obfuscate, ObfuscationConfig, Width, Printer } from './wasm.js'; 2 | 3 | const btn = document.getElementById('obfuscate-btn') 4 | const input = document.getElementById('input') 5 | const output = document.getElementById('output') 6 | const input_error = document.getElementById('input-error') 7 | const output_type = document.getElementById('output-type') 8 | const output_types = document.getElementsByName('output-type') 9 | const aux_vars = document.getElementById('aux-vars') 10 | const rewrite_ops = document.getElementById('rewrite-ops') 11 | const rewrite_depth = document.getElementById('rewrite-depth') 12 | 13 | // Highlights inline code. 14 | function hi_in(code) { 15 | return `${Prism.highlight(code, Prism.languages.c, 'c')}` 16 | } 17 | 18 | // Part of the popover config supplying default values. 19 | const popover_config = { 20 | trigger: 'hover', 21 | delay: { show: 500, hide: 100 }, 22 | html: true, 23 | customClass: 'custom-popover', 24 | } 25 | 26 | // Popover for the input box. 27 | new bootstrap.Popover(input, { 28 | ...popover_config, 29 | title: 'The expression that will be obfuscated', 30 | content: 31 | ` 32 | This expression can be any mixed boolean-arithmetic expression, 33 | e.g. ${hi_in('x + y')}, ${hi_in('x & ~y')}, also constants ${hi_in('1234')}. 34 | You can also use non-linear expressions such as ${hi_in('x * y + z')}, 35 | although the current implementation will just obfuscate the linear subexpressions 36 | and leave the non-linear operations as is. 37 | ` 38 | }) 39 | 40 | // 'What is Mixed Boolean-Arithmetic?' 41 | document.getElementById('acc-col-1').children[0].innerHTML = 42 | ` 43 | Mixed Boolean-Arithmetic (MBA) is a name for expressions which contain both the usual arithmetic operations 44 | (${hi_in('+')}, ${hi_in('-')}, ${hi_in('*')}, ${hi_in('/')}) 45 | as well as boolean operations (${hi_in('&')}, ${hi_in('|')}, ${hi_in('^')}, ${hi_in('~')}). 46 | A simple example is the expression ${hi_in('2 * (x & y) + (x ^ y)')} that computes ${hi_in('x + y')}. 47 | This particular one works for integers of any number of bits but generally they can be specific to 48 | a certain size, such as the following one, that also computes ${hi_in('x + y')} but only for 8-bit integers: 49 |
${Prism.highlight('-38*(x & y) - 83*(x ^ y) - 64*~(x ^ (y ^ z))\n - 41*~x - 43*~y - 23*y - 44*z - 20*(y & z)\n - 21*(x | z) - 107*(~x & z) - 108*(y | ~z)', Prism.languages.c, 'c')}
50 | ` 51 | 52 | // Setup handling for the output type dropdown. 53 | for (const li of output_types) { 54 | li.onclick = (e) => { 55 | for (const li of output_types) { 56 | li.classList.remove('active') 57 | } 58 | e.target.classList.add('active') 59 | output_type.textContent = e.target.textContent 60 | } 61 | } 62 | 63 | // Do the obfuscation. 64 | btn.onclick = () => { 65 | const printer = Printer[output_type.innerText.trim()] 66 | const width = Width[document.querySelector('input[name=bitness]:checked').value] 67 | 68 | let cfg = new ObfuscationConfig() 69 | cfg.expr = input.value 70 | cfg.printer = printer 71 | cfg.width = width 72 | cfg.aux_vars = Number(aux_vars.value) 73 | cfg.rewrite_count = Number(rewrite_ops.value) 74 | cfg.rewrite_depth = Number(rewrite_depth.value) 75 | 76 | try { 77 | // Do the rewriting. 78 | const s = postprocess_code(obfuscate(cfg)) 79 | input.classList.remove('is-invalid') 80 | input_error.textContent = '' 81 | 82 | // Display the result. 83 | output.replaceChildren() 84 | if (printer == Printer.C) { 85 | const code = document.createElement('pre') 86 | code.classList.add('language-c') 87 | code.innerHTML = Prism.highlight(s, Prism.languages.c, 'c') 88 | output.appendChild(code) 89 | 90 | // Very hacky and requires the code to contain commas only for the arguments. 91 | const args = s.split(',').map(() => '0').join(', ') 92 | const ce_code = encodeURIComponent(`#include \n#include \n\n${s}\n\nint main() {\n\tstd::cout << ${width == Width.U8 ? '(uint32_t)' : ''}f(${args}) << "\\n";\n}`) 93 | const ce_btn = document.createElement('button') 94 | ce_btn.textContent = 'Open in Compiler Explorer' 95 | ce_btn.classList.add('btn', 'btn-secondary') 96 | ce_btn.onclick = () => { 97 | window.open(`https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:'${ce_code}'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:executor,i:(argsPanelShown:'1',compilationPanelShown:'0',compiler:clang_trunk,compilerOutShown:'0',execArgs:'',execStdin:'',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'',source:1,stdinPanelShown:'1',tree:'1',wrap:'1'),l:'5',n:'0',o:'Executor+x86-64+clang+(trunk)+(C%2B%2B,+Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4`) 98 | } 99 | output.appendChild(ce_btn) 100 | } else if (printer == Printer.Rust) { 101 | const code = document.createElement('pre') 102 | code.classList.add('language-rust') 103 | code.innerHTML = Prism.highlight(s, Prism.languages.rust, 'rust') 104 | output.appendChild(code) 105 | 106 | const pg_btn = document.createElement('button') 107 | pg_btn.textContent = 'Open in Rust Playground' 108 | pg_btn.classList.add('btn', 'btn-secondary') 109 | 110 | // Very hacky and requires the code to contain commas only for the arguments. 111 | const args = s.split(',').map(() => 'Wrapping(0)').join(', ') 112 | const pg_code = encodeURIComponent(`use std::num::Wrapping;\n\nfn main() {\n\tprintln!("{}", f(${args}));\n}\n\n${s}`) 113 | pg_btn.onclick = () => { 114 | window.open(`https://play.rust-lang.org/?version=stable&mode=release&edition=2021&code=${pg_code}`) 115 | } 116 | output.appendChild(pg_btn) 117 | } 118 | else { 119 | output.textContent = s 120 | } 121 | } catch (err) { 122 | input.classList.add('is-invalid') 123 | output.textContent = '' 124 | if (typeof err === 'string') { 125 | input_error.textContent = err 126 | } else { 127 | console.log(err) 128 | input_error.textContent = 'Unknown error. Check console.' 129 | } 130 | } 131 | } 132 | 133 | // Hide this ugly code down here. 134 | function postprocess_code(code) { 135 | let s = '' 136 | let lines = code.split('\n') 137 | //s += lines[0] 138 | //s += '\n' 139 | //lines = lines.slice(1) 140 | for (let l of lines) { 141 | let tabs = 0 142 | for (; tabs < l.length && l[tabs] == '\t'; tabs++) {} 143 | l = l.substring(tabs) 144 | 145 | inner: for (let j = 0; ; j++) { 146 | let max = 68 - 4 * tabs; 147 | if (j == 0) { 148 | s += '\t'.repeat(tabs) 149 | } else { 150 | s += '\t'.repeat(tabs+1) 151 | max -= 4; 152 | } 153 | 154 | if (l.length >= max) { 155 | for (let i = max; i >= 0; i--) { 156 | if (l[i] == ' ') { 157 | s += l.substring(0, i) 158 | s += '\n' 159 | l = l.substring(i+1) 160 | continue inner 161 | } 162 | } 163 | } 164 | 165 | // If we never found a space then just put the whole string into the line anyways. 166 | s += l 167 | s += '\n' 168 | break 169 | } 170 | } 171 | 172 | return s 173 | } -------------------------------------------------------------------------------- /www/perm_poly.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Binary Permutation Polynomials 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Binary Permutation Polynomials

14 | 15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 | 55 |
56 |
57 |
58 |
59 | 60 | -------------------------------------------------------------------------------- /www/perm_poly.js: -------------------------------------------------------------------------------- 1 | import './mathjax.js' 2 | import { Width, invert_poly, rand_poly } from './wasm.js' 3 | 4 | const input = document.getElementById('input') 5 | const input_error = document.getElementById('input-error') 6 | const invert_btn = document.getElementById('invert-btn') 7 | const rand = document.getElementById('rand-poly') 8 | const algorithm = document.getElementById('algorithm') 9 | const algorithms = document.getElementsByName('algorithm') 10 | const output = document.getElementById('output') 11 | 12 | // Setup handling for the algorithm dropdown. 13 | for (const li of algorithms) { 14 | li.onclick = (e) => { 15 | for (const li of algorithms) { 16 | li.classList.remove('active') 17 | } 18 | e.target.classList.add('active') 19 | algorithm.textContent = e.target.textContent 20 | } 21 | } 22 | 23 | rand.onclick = () => { 24 | const bits = Width[document.querySelector('input[name=width]:checked').value] 25 | const p = rand_poly(bits) 26 | input.value = p 27 | } 28 | 29 | invert_btn.onclick = () => { 30 | const poly = input.value 31 | const bits = Width[document.querySelector('input[name=width]:checked').value] 32 | const alg = algorithm.innerText.trim() 33 | try { 34 | input.classList.remove('is-invalid') 35 | input_error.textContent = '' 36 | const inverse = invert_poly(poly, bits, alg) 37 | MathJax.reset() 38 | output.replaceChildren() 39 | output.appendChild(MathJax.tex2chtml(inverse, { scale: 1.3 })) 40 | MathJax.set_css('mathjax-styles') 41 | } catch (err) { 42 | output.replaceChildren() 43 | if (typeof err === 'string') { 44 | input.classList.add('is-invalid') 45 | input_error.textContent = err 46 | } else { 47 | console.log(err); 48 | input.classList.add('is-invalid') 49 | input_error.textContent = 'Check console.' 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /www/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * One Light theme for prism.js 3 | * Based on Atom's One Light theme: https://github.com/atom/atom/tree/master/packages/one-light-syntax 4 | */ 5 | 6 | /** 7 | * One Light colours (accurate as of commit eb064bf on 19 Feb 2021) 8 | * From colors.less 9 | * --mono-1: hsl(230, 8%, 24%); 10 | * --mono-2: hsl(230, 6%, 44%); 11 | * --mono-3: hsl(230, 4%, 64%) 12 | * --hue-1: hsl(198, 99%, 37%); 13 | * --hue-2: hsl(221, 87%, 60%); 14 | * --hue-3: hsl(301, 63%, 40%); 15 | * --hue-4: hsl(119, 34%, 47%); 16 | * --hue-5: hsl(5, 74%, 59%); 17 | * --hue-5-2: hsl(344, 84%, 43%); 18 | * --hue-6: hsl(35, 99%, 36%); 19 | * --hue-6-2: hsl(35, 99%, 40%); 20 | * --syntax-fg: hsl(230, 8%, 24%); 21 | * --syntax-bg: hsl(230, 1%, 98%); 22 | * --syntax-gutter: hsl(230, 1%, 62%); 23 | * --syntax-guide: hsla(230, 8%, 24%, 0.2); 24 | * --syntax-accent: hsl(230, 100%, 66%); 25 | * From syntax-variables.less 26 | * --syntax-selection-color: hsl(230, 1%, 90%); 27 | * --syntax-gutter-background-color-selected: hsl(230, 1%, 90%); 28 | * --syntax-cursor-line: hsla(230, 8%, 24%, 0.05); 29 | */ 30 | 31 | code[class*="language-"], 32 | pre[class*="language-"] { 33 | background: hsl(230, 1%, 98%); 34 | color: hsl(230, 8%, 24%); 35 | font-family: "Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace; 36 | direction: ltr; 37 | text-align: left; 38 | white-space: pre; 39 | word-spacing: normal; 40 | word-break: normal; 41 | line-height: 1.5; 42 | -moz-tab-size: 2; 43 | -o-tab-size: 2; 44 | tab-size: 2; 45 | -webkit-hyphens: none; 46 | -moz-hyphens: none; 47 | -ms-hyphens: none; 48 | hyphens: none; 49 | } 50 | 51 | /* Selection */ 52 | ::selection { 53 | background: hsl(230, 1%, 90%); 54 | color: inherit; 55 | } 56 | 57 | /* Code blocks */ 58 | pre[class*="language-"] { 59 | padding: 1em; 60 | margin: 0.5em 0; 61 | overflow: auto; 62 | border-radius: 0.3em; 63 | } 64 | 65 | /* Inline code */ 66 | :not(pre) > code[class*="language-"] { 67 | padding: 0.2em 0.3em; 68 | border-radius: 0.3em; 69 | white-space: normal; 70 | } 71 | 72 | .token.comment, 73 | .token.prolog, 74 | .token.cdata { 75 | color: hsl(230, 4%, 64%); 76 | } 77 | 78 | .token.doctype, 79 | .token.punctuation, 80 | .token.entity { 81 | color: hsl(230, 8%, 24%); 82 | } 83 | 84 | .token.attr-name, 85 | .token.class-name, 86 | .token.boolean, 87 | .token.constant, 88 | .token.number, 89 | .token.atrule { 90 | color: hsl(35, 99%, 36%); 91 | } 92 | 93 | .token.keyword { 94 | color: hsl(301, 63%, 40%); 95 | } 96 | 97 | .token.property, 98 | .token.tag, 99 | .token.symbol, 100 | .token.deleted, 101 | .token.important { 102 | color: hsl(5, 74%, 59%); 103 | } 104 | 105 | .token.selector, 106 | .token.string, 107 | .token.char, 108 | .token.builtin, 109 | .token.inserted, 110 | .token.regex, 111 | .token.attr-value, 112 | .token.attr-value > .token.punctuation { 113 | color: hsl(119, 34%, 47%); 114 | } 115 | 116 | .token.variable, 117 | .token.operator, 118 | .token.function { 119 | color: hsl(221, 87%, 60%); 120 | } 121 | 122 | .token.url { 123 | color: hsl(198, 99%, 37%); 124 | } 125 | 126 | /* HTML overrides */ 127 | .token.attr-value > .token.punctuation.attr-equals, 128 | .token.special-attr > .token.attr-value > .token.value.css { 129 | color: hsl(230, 8%, 24%); 130 | } 131 | 132 | /* CSS overrides */ 133 | .language-css .token.selector { 134 | color: hsl(5, 74%, 59%); 135 | } 136 | 137 | .language-css .token.property { 138 | color: hsl(230, 8%, 24%); 139 | } 140 | 141 | .language-css .token.function, 142 | .language-css .token.url > .token.function { 143 | color: hsl(198, 99%, 37%); 144 | } 145 | 146 | .language-css .token.url > .token.string.url { 147 | color: hsl(119, 34%, 47%); 148 | } 149 | 150 | .language-css .token.important, 151 | .language-css .token.atrule .token.rule { 152 | color: hsl(301, 63%, 40%); 153 | } 154 | 155 | /* JS overrides */ 156 | .language-javascript .token.operator { 157 | color: hsl(301, 63%, 40%); 158 | } 159 | 160 | .language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation { 161 | color: hsl(344, 84%, 43%); 162 | } 163 | 164 | /* JSON overrides */ 165 | .language-json .token.operator { 166 | color: hsl(230, 8%, 24%); 167 | } 168 | 169 | .language-json .token.null.keyword { 170 | color: hsl(35, 99%, 36%); 171 | } 172 | 173 | /* MD overrides */ 174 | .language-markdown .token.url, 175 | .language-markdown .token.url > .token.operator, 176 | .language-markdown .token.url-reference.url > .token.string { 177 | color: hsl(230, 8%, 24%); 178 | } 179 | 180 | .language-markdown .token.url > .token.content { 181 | color: hsl(221, 87%, 60%); 182 | } 183 | 184 | .language-markdown .token.url > .token.url, 185 | .language-markdown .token.url-reference.url { 186 | color: hsl(198, 99%, 37%); 187 | } 188 | 189 | .language-markdown .token.blockquote.punctuation, 190 | .language-markdown .token.hr.punctuation { 191 | color: hsl(230, 4%, 64%); 192 | font-style: italic; 193 | } 194 | 195 | .language-markdown .token.code-snippet { 196 | color: hsl(119, 34%, 47%); 197 | } 198 | 199 | .language-markdown .token.bold .token.content { 200 | color: hsl(35, 99%, 36%); 201 | } 202 | 203 | .language-markdown .token.italic .token.content { 204 | color: hsl(301, 63%, 40%); 205 | } 206 | 207 | .language-markdown .token.strike .token.content, 208 | .language-markdown .token.strike .token.punctuation, 209 | .language-markdown .token.list.punctuation, 210 | .language-markdown .token.title.important > .token.punctuation { 211 | color: hsl(5, 74%, 59%); 212 | } 213 | 214 | /* General */ 215 | .token.bold { 216 | font-weight: bold; 217 | } 218 | 219 | .token.comment, 220 | .token.italic { 221 | font-style: italic; 222 | } 223 | 224 | .token.entity { 225 | cursor: help; 226 | } 227 | 228 | .token.namespace { 229 | opacity: 0.8; 230 | } 231 | 232 | /* Plugin overrides */ 233 | /* Selectors should have higher specificity than those in the plugins' default stylesheets */ 234 | 235 | /* Show Invisibles plugin overrides */ 236 | .token.token.tab:not(:empty):before, 237 | .token.token.cr:before, 238 | .token.token.lf:before, 239 | .token.token.space:before { 240 | color: hsla(230, 8%, 24%, 0.2); 241 | } 242 | 243 | /* Toolbar plugin overrides */ 244 | /* Space out all buttons and move them away from the right edge of the code block */ 245 | div.code-toolbar > .toolbar.toolbar > .toolbar-item { 246 | margin-right: 0.4em; 247 | } 248 | 249 | /* Styling the buttons */ 250 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, 251 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, 252 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { 253 | background: hsl(230, 1%, 90%); 254 | color: hsl(230, 6%, 44%); 255 | padding: 0.1em 0.4em; 256 | border-radius: 0.3em; 257 | } 258 | 259 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, 260 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, 261 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, 262 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, 263 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, 264 | div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { 265 | background: hsl(230, 1%, 78%); /* custom: darken(--syntax-bg, 20%) */ 266 | color: hsl(230, 8%, 24%); 267 | } 268 | 269 | /* Line Highlight plugin overrides */ 270 | /* The highlighted line itself */ 271 | .line-highlight.line-highlight { 272 | background: hsla(230, 8%, 24%, 0.05); 273 | } 274 | 275 | /* Default line numbers in Line Highlight plugin */ 276 | .line-highlight.line-highlight:before, 277 | .line-highlight.line-highlight[data-end]:after { 278 | background: hsl(230, 1%, 90%); 279 | color: hsl(230, 8%, 24%); 280 | padding: 0.1em 0.6em; 281 | border-radius: 0.3em; 282 | box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */ 283 | } 284 | 285 | /* Hovering over a linkable line number (in the gutter area) */ 286 | /* Requires Line Numbers plugin as well */ 287 | pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before { 288 | background-color: hsla(230, 8%, 24%, 0.05); 289 | } 290 | 291 | /* Line Numbers and Command Line plugins overrides */ 292 | /* Line separating gutter from coding area */ 293 | .line-numbers.line-numbers .line-numbers-rows, 294 | .command-line .command-line-prompt { 295 | border-right-color: hsla(230, 8%, 24%, 0.2); 296 | } 297 | 298 | /* Stuff in the gutter */ 299 | .line-numbers .line-numbers-rows > span:before, 300 | .command-line .command-line-prompt > span:before { 301 | color: hsl(230, 1%, 62%); 302 | } 303 | 304 | /* Match Braces plugin overrides */ 305 | /* Note: Outline colour is inherited from the braces */ 306 | .rainbow-braces .token.token.punctuation.brace-level-1, 307 | .rainbow-braces .token.token.punctuation.brace-level-5, 308 | .rainbow-braces .token.token.punctuation.brace-level-9 { 309 | color: hsl(5, 74%, 59%); 310 | } 311 | 312 | .rainbow-braces .token.token.punctuation.brace-level-2, 313 | .rainbow-braces .token.token.punctuation.brace-level-6, 314 | .rainbow-braces .token.token.punctuation.brace-level-10 { 315 | color: hsl(119, 34%, 47%); 316 | } 317 | 318 | .rainbow-braces .token.token.punctuation.brace-level-3, 319 | .rainbow-braces .token.token.punctuation.brace-level-7, 320 | .rainbow-braces .token.token.punctuation.brace-level-11 { 321 | color: hsl(221, 87%, 60%); 322 | } 323 | 324 | .rainbow-braces .token.token.punctuation.brace-level-4, 325 | .rainbow-braces .token.token.punctuation.brace-level-8, 326 | .rainbow-braces .token.token.punctuation.brace-level-12 { 327 | color: hsl(301, 63%, 40%); 328 | } 329 | 330 | /* Diff Highlight plugin overrides */ 331 | /* Taken from https://github.com/atom/github/blob/master/styles/variables.less */ 332 | pre.diff-highlight > code .token.token.deleted:not(.prefix), 333 | pre > code.diff-highlight .token.token.deleted:not(.prefix) { 334 | background-color: hsla(353, 100%, 66%, 0.15); 335 | } 336 | 337 | pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection, 338 | pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection, 339 | pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection, 340 | pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection { 341 | background-color: hsla(353, 95%, 66%, 0.25); 342 | } 343 | 344 | pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection, 345 | pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection, 346 | pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection, 347 | pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection { 348 | background-color: hsla(353, 95%, 66%, 0.25); 349 | } 350 | 351 | pre.diff-highlight > code .token.token.inserted:not(.prefix), 352 | pre > code.diff-highlight .token.token.inserted:not(.prefix) { 353 | background-color: hsla(137, 100%, 55%, 0.15); 354 | } 355 | 356 | pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection, 357 | pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection, 358 | pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection, 359 | pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection { 360 | background-color: hsla(135, 73%, 55%, 0.25); 361 | } 362 | 363 | pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection, 364 | pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection, 365 | pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection, 366 | pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection { 367 | background-color: hsla(135, 73%, 55%, 0.25); 368 | } 369 | 370 | /* Previewers plugin overrides */ 371 | /* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-light-ui */ 372 | /* Border around popup */ 373 | .prism-previewer.prism-previewer:before, 374 | .prism-previewer-gradient.prism-previewer-gradient div { 375 | border-color: hsl(0, 0, 95%); 376 | } 377 | 378 | /* Angle and time should remain as circles and are hence not included */ 379 | .prism-previewer-color.prism-previewer-color:before, 380 | .prism-previewer-gradient.prism-previewer-gradient div, 381 | .prism-previewer-easing.prism-previewer-easing:before { 382 | border-radius: 0.3em; 383 | } 384 | 385 | /* Triangles pointing to the code */ 386 | .prism-previewer.prism-previewer:after { 387 | border-top-color: hsl(0, 0, 95%); 388 | } 389 | 390 | .prism-previewer-flipped.prism-previewer-flipped.after { 391 | border-bottom-color: hsl(0, 0, 95%); 392 | } 393 | 394 | /* Background colour within the popup */ 395 | .prism-previewer-angle.prism-previewer-angle:before, 396 | .prism-previewer-time.prism-previewer-time:before, 397 | .prism-previewer-easing.prism-previewer-easing { 398 | background: hsl(0, 0%, 100%); 399 | } 400 | 401 | /* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */ 402 | /* For time, this is the alternate colour */ 403 | .prism-previewer-angle.prism-previewer-angle circle, 404 | .prism-previewer-time.prism-previewer-time circle { 405 | stroke: hsl(230, 8%, 24%); 406 | stroke-opacity: 1; 407 | } 408 | 409 | /* Stroke colours of the handle, direction point, and vector itself */ 410 | .prism-previewer-easing.prism-previewer-easing circle, 411 | .prism-previewer-easing.prism-previewer-easing path, 412 | .prism-previewer-easing.prism-previewer-easing line { 413 | stroke: hsl(230, 8%, 24%); 414 | } 415 | 416 | /* Fill colour of the handle */ 417 | .prism-previewer-easing.prism-previewer-easing circle { 418 | fill: transparent; 419 | } -------------------------------------------------------------------------------- /www/wasm.js: -------------------------------------------------------------------------------- 1 | // Re-exports everything from the wasm (except the init function) 2 | // but calls init on import. 3 | 4 | import init from './mba_wasm.js' 5 | 6 | await init() 7 | 8 | export * from './mba_wasm.js' --------------------------------------------------------------------------------