├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── Nargo.toml ├── README.md ├── src ├── array.nr ├── convert.nr ├── hash.nr ├── lib.nr ├── math.nr ├── math │ ├── numeric.nr │ └── sqrt.nr ├── solidity.nr ├── string.nr ├── tables.nr └── validate_inputs.nr └── tests ├── Nargo.toml └── src ├── lib.nr └── validate_inputs.nr /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.nr] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Noir tests 2 | 3 | on: [push] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: Test on Nargo ${{matrix.toolchain}} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | toolchain: [1.0.0-beta.4] 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Nargo 21 | uses: noir-lang/noirup@v0.1.3 22 | with: 23 | toolchain: ${{ matrix.toolchain }} 24 | 25 | - name: Run Noir tests 26 | run: nargo test 27 | 28 | format: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout sources 32 | uses: actions/checkout@v4 33 | 34 | - name: Install Nargo 35 | uses: noir-lang/noirup@v0.1.4 36 | with: 37 | toolchain: 1.0.0-beta.4 38 | 39 | - name: Run formatter 40 | run: nargo fmt --check 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | target 3 | .vscode 4 | -------------------------------------------------------------------------------- /Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nodash" 3 | type = "lib" 4 | description = "A Swiss knife for Noir" 5 | version = "0.41.3" 6 | authors = ["Oleh Misarosh "] 7 | repository = "https://github.com/olehmisar/nodash" 8 | keywords = ["Noir", "nodash", "util", "hash"] 9 | 10 | 11 | [dependencies] 12 | sha256 = { tag = "v0.1.4", git = "https://github.com/noir-lang/sha256" } 13 | keccak256 = { tag = "v0.1.0", git = "https://github.com/noir-lang/keccak256" } 14 | poseidon = { tag = "v0.1.1", git = "https://github.com/noir-lang/poseidon" } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nodash - Lodash for Noir 2 | 3 | Nodash is a utility library for [Noir](https://github.com/noir-lang/noir) language. 4 | 5 | ## Installation 6 | 7 | Put this into your Nargo.toml. 8 | 9 | ```toml 10 | nodash = { git = "https://github.com/olehmisar/nodash/", tag = "v0.41.3" } 11 | ``` 12 | 13 | ## Docs 14 | 15 | ### `sqrt` 16 | 17 | ```rs 18 | use nodash::sqrt; 19 | 20 | assert(sqrt(4 as u64) == 2); 21 | 22 | // it floors the result 23 | assert(sqrt(8 as u64) == 2); 24 | ``` 25 | 26 | ### `clamp` 27 | 28 | ```rs 29 | use nodash::clamp; 30 | 31 | // if too small, return min 32 | assert(clamp(1 as u64, 2 as u64, 3 as u64) == 2 as u64); 33 | // if too big, return max 34 | assert(clamp(4 as u64, 1 as u64, 3 as u64) == 3 as u64); 35 | // if in range, return value 36 | assert(clamp(2 as u64, 1 as u64, 3 as u64) == 2 as u64); 37 | ``` 38 | 39 | ### `div_ceil` 40 | 41 | Calculates `a / b` rounded up to the nearest integer. 42 | 43 | ```rs 44 | use nodash::div_ceil; 45 | 46 | assert(div_ceil(10 as u64, 3) == 4); 47 | ``` 48 | 49 | ### Hashes 50 | 51 | Hash functions can either accept a `[T; N]` or a `BoundedVec` (if technically possible). 52 | 53 | #### `poseidon2` 54 | 55 | ```rs 56 | use nodash::poseidon2; 57 | 58 | // hashes the whole array 59 | let hash = poseidon2([10, 20]); 60 | // hashes elements up to the length (in this case, 2) 61 | let hash = poseidon2(BoundedVec::from_parts([10, 20, 0], 2)); 62 | ``` 63 | 64 | #### `pedersen` 65 | 66 | ```rs 67 | use nodash::pedersen; 68 | 69 | let hash = pedersen([10, 20]); 70 | ``` 71 | 72 | #### `sha256` 73 | 74 | sha256 is expensive to compute in Noir, so use [poseidon2](#poseidon2) where possible. 75 | 76 | ```rs 77 | use nodash::sha256; 78 | 79 | let hash = sha256([10, 20]); 80 | // or 81 | let hash = sha256(BoundedVec::from_parts([10, 20, 0], 2)); 82 | ``` 83 | 84 | #### `keccak256` 85 | 86 | keccak256 is expensive to compute in Noir, so use [poseidon2](#poseidon2) where possible. 87 | 88 | ```rs 89 | use nodash::keccak256; 90 | 91 | let hash = keccak256([10, 20]); 92 | // or 93 | let hash = keccak256(BoundedVec::from_parts([10, 20, 0], 2)); 94 | ``` 95 | 96 | ### `solidity::encode_with_selector` 97 | 98 | Equivalent to `abi.encodeWithSelector` in Solidity. 99 | 100 | ```rs 101 | use nodash::solidity::encode_with_selector; 102 | 103 | let selector: u32 = 0xa9059cbb; // transfer(address,uint256) 104 | let args: [Field; 2] = [ 105 | 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, // address 106 | 123 // uint256 107 | ]; 108 | let encoded = encode_with_selector(selector, args); 109 | // typeof encoded: [u8; 68] 110 | ``` 111 | 112 | ### `field_to_hex` 113 | 114 | Converts a `Field` to a hex string (without `0x` prefix). 115 | 116 | ```rs 117 | let my_hash = 0x0d67824fead966192029093a3aa5c719f2b80262c4f14a5c97c5d70e4b27f2bf; 118 | let expected = "0d67824fead966192029093a3aa5c719f2b80262c4f14a5c97c5d70e4b27f2bf"; 119 | assert_eq(field_to_hex(my_hash), expected); 120 | ``` 121 | 122 | ### `str_to_u64` 123 | 124 | Converts a string to a `u64`. 125 | 126 | ```rs 127 | use nodash::str_to_u64; 128 | 129 | assert(str_to_u64("02345678912345678912") == 02345678912345678912); 130 | ``` 131 | 132 | ### `try_from` 133 | 134 | Fail-able conversion. 135 | 136 | ```rs 137 | use nodash::TryFrom; 138 | 139 | assert(u8::try_from(123) == 123); 140 | u8::try_from(256); // runtime error 141 | 142 | assert(u128::try_from(123) == 123); 143 | ``` 144 | 145 | ### `ord` 146 | 147 | Returns the ASCII code of a single character. 148 | 149 | ```rs 150 | use nodash::ord; 151 | 152 | assert(ord("a") == 97); 153 | ``` 154 | 155 | ### `ArrayExtensions` 156 | 157 | #### `slice(start: u32) -> [T; L]` 158 | 159 | Returns a slice of the array, starting at `start` and ending at `start + L`. Panics if `start + L` is out of bounds. 160 | 161 | ```rs 162 | use nodash::ArrayExtensions; 163 | 164 | assert([1, 2, 3, 4, 5].slice::<3>(1) == [2, 3, 4]); 165 | ``` 166 | 167 | #### `pad_start` 168 | 169 | Pads the start of the array with a value. 170 | 171 | ```rs 172 | use nodash::ArrayExtensions; 173 | 174 | assert([1, 2, 3].pad_start::<5>(0) == [0, 0, 1, 2, 3]); 175 | ``` 176 | 177 | #### `pad_end` 178 | 179 | Pads the end of the array with a value. 180 | 181 | ```rs 182 | use nodash::ArrayExtensions; 183 | 184 | assert([1, 2, 3].pad_end::<5>(0) == [1, 2, 3, 0, 0]); 185 | ``` 186 | 187 | #### `enumerate` 188 | 189 | Returns an array of tuples, where each tuple contains the index and the value of the array element. 190 | 191 | ```rs 192 | use nodash::ArrayExtensions; 193 | 194 | assert(["a", "b", "c"].enumerate() == [(0, "a"), (1, "b"), (2, "c")]); 195 | ``` 196 | 197 | ### `pack_bytes` 198 | 199 | Packs `[u8; N]` into `[Field; N / 31 + 1]`. Useful, if you need to get a hash over bytes. I.e., `nodash::poseidon2(nodash::pack_bytes(bytes))` will be MUCH cheaper than `nodash::poseidon2(bytes)`. 200 | 201 | ```rs 202 | use nodash::pack_bytes; 203 | 204 | let bytes: [u8; 32] = [0; 32]; 205 | let packed = pack_bytes(bytes); 206 | ``` 207 | 208 | ### Validate `main` function inputs 209 | 210 | `fn main` inputs are not validated by Noir. For example, you have a `U120` struct like this: 211 | 212 | ```rs 213 | struct U120 { 214 | inner: Field, 215 | } 216 | 217 | impl U120 { 218 | fn new(inner: Field) -> Self { 219 | inner.assert_max_bit_size::<120>(); 220 | Self { inner } 221 | } 222 | } 223 | ``` 224 | 225 | You then can create instances of `U120` with `U120::new(123)`. If you pass a value that is larger than 2^120 to `U120::new`, you will get a runtime error because we assert the max bit size of `Field` in `U120::new`. 226 | 227 | However, Noir does not check the validity of `U120` fields when passed to a `fn main` function. For example, for this circuit 228 | 229 | ```rs 230 | fn main(a: U120) { 231 | // do something with a 232 | } 233 | ``` 234 | 235 | ...you can pass any arbitrary value to `a` from JavaScript and it will NOT fail in Noir when `main` is executed: 236 | 237 | ```js 238 | // this succeeds but it shouldn't! 239 | await noir.execute({ 240 | a: { 241 | inner: 2n ** 120n + 1n, 242 | }, 243 | }); 244 | ``` 245 | 246 | To fix this, you can use the `validate_inputs` attribute on the `main` function: 247 | 248 | ```rs 249 | use nodash::{validate_inputs, ValidateInput}; 250 | 251 | // this attribute checks that `U120` is within the range via `ValidateInput` trait 252 | #[validate_inputs] 253 | fn main(a: U120) { 254 | // do something with a 255 | } 256 | 257 | impl ValidateInput for U120 { 258 | fn validate(self) { 259 | // call the `new` function that asserts the max bit size 260 | U120::new(self.inner); 261 | } 262 | } 263 | ``` 264 | 265 | Now, if you pass a value that is larger than 2^120 to `a` in JavaScript, you will get a runtime error: 266 | 267 | ```js 268 | // runtime error: "Assertion failed: call to assert_max_bit_size" 269 | await noir.execute({ 270 | a: { 271 | inner: 2n ** 120n + 1n, 272 | }, 273 | }); 274 | ``` 275 | -------------------------------------------------------------------------------- /src/array.nr: -------------------------------------------------------------------------------- 1 | use super::ArrayExtensions; 2 | 3 | impl crate::ArrayExtensions for [T; N] { 4 | fn slice(self, start: u32) -> [T; L] { 5 | let end = start + L; 6 | assert(end <= N, "slice: slice end out of bounds"); 7 | let mut result = [self[0]; L]; 8 | for i in 0..L { 9 | result[i] = self[start + i]; 10 | } 11 | result 12 | } 13 | 14 | fn pad_start(self, pad_value: T) -> [T; M] { 15 | assert(M >= N, "pad_start: array too long"); 16 | let mut res = [pad_value; M]; 17 | for i in 0..N { 18 | res[i + M - N] = self[i]; 19 | } 20 | res 21 | } 22 | 23 | fn pad_end(self, pad_value: T) -> [T; M] { 24 | assert(M >= N, "pad_end: array too long"); 25 | let mut res = [pad_value; M]; 26 | for i in 0..N { 27 | res[i] = self[i]; 28 | } 29 | res 30 | } 31 | 32 | fn enumerate(self) -> [(u32, T); N] { 33 | let mut res = [(0, self[0]); N]; // TODO: should I use std::zeroed() instead? 34 | for i in 1..N { 35 | res[i] = (i as u32, self[i]); 36 | } 37 | res 38 | } 39 | } 40 | 41 | // TODO: write tests 42 | pub fn pack_bytes(bytes: [u8; N]) -> [Field; N / 31 + 1] { 43 | let bytes_padded = bytes.pad_end::<(N / 31 + 1) * 31>(0); 44 | let mut res = [0 as Field; N / 31 + 1]; 45 | for i in 0..N / 31 + 1 { 46 | let chunk = bytes_padded.slice::<31>(i * 31); 47 | res[i] = field_from_bytes(chunk); 48 | } 49 | res 50 | } 51 | 52 | // copied from https://github.com/AztecProtocol/aztec-packages/blob/a2ed567ad42b237088c110ce12ce8212d5099da2/noir-projects/noir-protocol-circuits/crates/types/src/utils/field.nr#L4 53 | fn field_from_bytes(bytes: [u8; N]) -> Field { 54 | assert(bytes.len() < 32, "field_from_bytes: N must be less than 32"); 55 | let mut as_field = 0; 56 | let mut offset = 1; 57 | for i in 0..N { 58 | as_field += (bytes[i] as Field) * offset; 59 | offset *= 256; 60 | } 61 | as_field 62 | } 63 | 64 | mod tests { 65 | use crate::ArrayExtensions; 66 | 67 | #[test] 68 | fn test_slice() { 69 | assert([1, 2, 3, 4, 5].slice::<3>(1) == [2, 3, 4]); 70 | } 71 | 72 | #[test] 73 | fn test_pad_start() { 74 | assert([1, 2, 3].pad_start::<5>(0) == [0, 0, 1, 2, 3]); 75 | } 76 | 77 | #[test] 78 | fn test_pad_end() { 79 | assert([1, 2, 3].pad_end::<5>(0) == [1, 2, 3, 0, 0]); 80 | } 81 | 82 | #[test] 83 | fn test_enumerate() { 84 | assert(["a", "b", "c"].enumerate() == [(0, "a"), (1, "b"), (2, "c")]); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/convert.nr: -------------------------------------------------------------------------------- 1 | pub trait TryFrom { 2 | /// Convert or fail. 3 | fn try_from(input: T) -> Self; 4 | } 5 | 6 | impl TryFrom for u8 { 7 | fn try_from(input: Field) -> Self { 8 | input.assert_max_bit_size::<8>(); 9 | input as u8 10 | } 11 | } 12 | 13 | impl TryFrom for u16 { 14 | fn try_from(input: Field) -> Self { 15 | input.assert_max_bit_size::<16>(); 16 | input as u16 17 | } 18 | } 19 | 20 | impl TryFrom for u32 { 21 | fn try_from(input: Field) -> Self { 22 | input.assert_max_bit_size::<32>(); 23 | input as u32 24 | } 25 | } 26 | 27 | impl TryFrom for u64 { 28 | fn try_from(input: Field) -> Self { 29 | input.assert_max_bit_size::<64>(); 30 | input as u64 31 | } 32 | } 33 | 34 | impl TryFrom for u128 { 35 | fn try_from(input: Field) -> Self { 36 | input.assert_max_bit_size::<128>(); 37 | input as u128 38 | } 39 | } 40 | 41 | impl TryFrom for i8 { 42 | fn try_from(input: Field) -> Self { 43 | input.assert_max_bit_size::<8>(); 44 | input as i8 45 | } 46 | } 47 | 48 | impl TryFrom for i16 { 49 | fn try_from(input: Field) -> Self { 50 | input.assert_max_bit_size::<16>(); 51 | input as i16 52 | } 53 | } 54 | 55 | impl TryFrom for i32 { 56 | fn try_from(input: Field) -> Self { 57 | input.assert_max_bit_size::<32>(); 58 | input as i32 59 | } 60 | } 61 | 62 | impl TryFrom for i64 { 63 | fn try_from(input: Field) -> Self { 64 | input.assert_max_bit_size::<64>(); 65 | input as i64 66 | } 67 | } 68 | 69 | mod tests { 70 | use super::TryFrom; 71 | 72 | #[test] 73 | fn test_try_from() { 74 | assert(u8::try_from(123) == 123); 75 | assert(u128::try_from(123) == 123); 76 | } 77 | 78 | #[test(should_fail_with = "call to assert_max_bit_size")] 79 | fn test_try_from_fail() { 80 | let _ = u8::try_from(256); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/hash.nr: -------------------------------------------------------------------------------- 1 | pub fn poseidon2(input: impl ArrayOrBoundedVec) -> Field { 2 | let input = input.as_bounded_vec(); 3 | poseidon::poseidon2::Poseidon2::hash(input.storage(), input.len()) 4 | } 5 | 6 | // TODO: is it possible for pedersen to accept BoundedVec? 7 | pub fn pedersen(input: [Field; N]) -> Field { 8 | std::hash::pedersen_hash(input) 9 | } 10 | 11 | pub fn sha256(input: impl ArrayOrBoundedVec) -> [u8; 32] { 12 | let input = input.as_bounded_vec(); 13 | dep::sha256::sha256_var(input.storage(), input.len() as u64) 14 | } 15 | 16 | pub fn keccak256(input: impl ArrayOrBoundedVec) -> [u8; 32] { 17 | let input = input.as_bounded_vec(); 18 | dep::keccak256::keccak256(input.storage(), input.len()) 19 | } 20 | 21 | /// Needed because of https://github.com/noir-lang/noir/issues/7054 22 | trait ArrayOrBoundedVec { 23 | fn as_bounded_vec(self) -> BoundedVec; 24 | } 25 | 26 | impl ArrayOrBoundedVec for [T; N] { 27 | fn as_bounded_vec(self) -> BoundedVec { 28 | BoundedVec::from(self) 29 | } 30 | } 31 | 32 | impl ArrayOrBoundedVec for BoundedVec { 33 | fn as_bounded_vec(self) -> BoundedVec { 34 | self 35 | } 36 | } 37 | 38 | mod tests { 39 | use crate::hash::{keccak256, pedersen, poseidon2, sha256}; 40 | 41 | global FIELD_INPUT_ARR: [Field; 2] = [1, 2]; 42 | global FIELD_INPUT_VEC: BoundedVec = BoundedVec::from(FIELD_INPUT_ARR); 43 | global FIELD_INPUT_VEC_LONGER: BoundedVec = BoundedVec::from(FIELD_INPUT_ARR); 44 | 45 | global U8_INPUT_ARR: [u8; 2] = [1, 2]; 46 | global U8_INPUT_VEC: BoundedVec = BoundedVec::from(U8_INPUT_ARR); 47 | global U8_INPUT_VEC_LONGER: BoundedVec = BoundedVec::from(U8_INPUT_ARR); 48 | 49 | #[test] 50 | fn test_equivalence() { 51 | assert( 52 | (poseidon2(FIELD_INPUT_ARR) == poseidon2(FIELD_INPUT_VEC)), 53 | // TODO: is this a bug? https://discord.com/channels/1113924620781883405/1333383938198212659 54 | // & (poseidon2(FIELD_INPUT_ARR) == poseidon2(FIELD_INPUT_VEC_LONGER)), 55 | ); 56 | assert( 57 | (sha256(U8_INPUT_VEC) == sha256(U8_INPUT_ARR)) 58 | & (sha256(U8_INPUT_VEC_LONGER) == sha256(U8_INPUT_ARR)), 59 | ); 60 | assert( 61 | (keccak256(U8_INPUT_VEC) == keccak256(U8_INPUT_ARR)) 62 | & (keccak256(U8_INPUT_VEC_LONGER) == keccak256(U8_INPUT_ARR)), 63 | ); 64 | } 65 | 66 | #[test] 67 | fn test_against_std() { 68 | assert( 69 | poseidon2(FIELD_INPUT_ARR) 70 | == poseidon::poseidon2::Poseidon2::hash(FIELD_INPUT_ARR, FIELD_INPUT_ARR.len()), 71 | ); 72 | assert( 73 | poseidon2(FIELD_INPUT_VEC_LONGER) 74 | == poseidon::poseidon2::Poseidon2::hash( 75 | FIELD_INPUT_VEC_LONGER.storage(), 76 | FIELD_INPUT_VEC_LONGER.len(), 77 | ), 78 | ); 79 | assert(pedersen(FIELD_INPUT_ARR) == std::hash::pedersen_hash(FIELD_INPUT_ARR)); 80 | assert( 81 | sha256(U8_INPUT_ARR) 82 | == dep::sha256::sha256_var(U8_INPUT_ARR, U8_INPUT_ARR.len() as u64), 83 | ); 84 | assert( 85 | keccak256(U8_INPUT_ARR) == dep::keccak256::keccak256(U8_INPUT_ARR, U8_INPUT_ARR.len()), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib.nr: -------------------------------------------------------------------------------- 1 | mod convert; 2 | mod hash; 3 | mod math; 4 | pub mod solidity; 5 | mod string; 6 | mod tables; 7 | mod array; 8 | mod validate_inputs; 9 | 10 | pub use array::pack_bytes; 11 | pub use convert::TryFrom; 12 | pub use hash::{keccak256, pedersen, poseidon2, sha256}; 13 | pub use math::{clamp, div_ceil, sqrt::sqrt}; 14 | pub use string::{field_to_hex, ord, str_to_u64, to_hex_string_bytes}; 15 | pub use validate_inputs::{validate_inputs, ValidateInput}; 16 | 17 | pub trait ArrayExtensions { 18 | fn slice(self, start: u32) -> [T; L]; 19 | fn pad_start(self, pad_value: T) -> [T; M]; 20 | fn pad_end(self, pad_value: T) -> [T; M]; 21 | fn enumerate(self) -> [(u32, T); N]; 22 | } 23 | -------------------------------------------------------------------------------- /src/math.nr: -------------------------------------------------------------------------------- 1 | pub(crate) mod sqrt; 2 | pub(crate) mod numeric; 3 | 4 | use numeric::Numeric; 5 | use std::{cmp::Eq, ops::{Add, Div, Rem}}; 6 | 7 | pub fn clamp(x: T, min: T, max: T) -> T 8 | where 9 | T: Ord, 10 | { 11 | if (x < min) { 12 | min 13 | } else if (x > max) { 14 | max 15 | } else { 16 | x 17 | } 18 | } 19 | 20 | pub fn div_ceil(a: T, b: T) -> T 21 | where 22 | T: Add + Div + Rem + Eq + Numeric, 23 | { 24 | let q = a / b; 25 | if (a % b == T::zero()) { 26 | q 27 | } else { 28 | q + T::one() 29 | } 30 | } 31 | 32 | mod tests { 33 | use crate::math::{clamp, div_ceil}; 34 | 35 | #[test] 36 | fn test_clamp() { 37 | // if too small, return min 38 | assert(clamp(1 as u64, 2 as u64, 3 as u64) == 2 as u64); 39 | // if too big, return max 40 | assert(clamp(4 as u64, 1 as u64, 3 as u64) == 3 as u64); 41 | // if in range, return value 42 | assert(clamp(2 as u64, 1 as u64, 3 as u64) == 2 as u64); 43 | } 44 | 45 | #[test] 46 | fn test_div_ceil() { 47 | assert(div_ceil(1 as u64, 2) == 1); 48 | assert(div_ceil(2 as u64, 2) == 1); 49 | assert(div_ceil(3 as u64, 2) == 2); 50 | assert(div_ceil(4 as u64, 2) == 2); 51 | assert(div_ceil(5 as u64, 2) == 3); 52 | assert(div_ceil(1337 as u64, 19) == 71); 53 | assert(div_ceil(1337 as u64, 7) == 191); 54 | assert(div_ceil(10 as u64, 3) == 4); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/math/numeric.nr: -------------------------------------------------------------------------------- 1 | pub(crate) trait Numeric { 2 | fn zero() -> Self; 3 | fn one() -> Self; 4 | } 5 | 6 | impl Numeric for u8 { 7 | fn zero() -> Self { 8 | 0 9 | } 10 | fn one() -> Self { 11 | 1 12 | } 13 | } 14 | 15 | impl Numeric for u16 { 16 | fn zero() -> Self { 17 | 0 18 | } 19 | fn one() -> Self { 20 | 1 21 | } 22 | } 23 | 24 | impl Numeric for u32 { 25 | fn zero() -> Self { 26 | 0 27 | } 28 | fn one() -> Self { 29 | 1 30 | } 31 | } 32 | 33 | impl Numeric for u64 { 34 | fn zero() -> Self { 35 | 0 36 | } 37 | fn one() -> Self { 38 | 1 39 | } 40 | } 41 | 42 | impl Numeric for u128 { 43 | fn zero() -> Self { 44 | 0 45 | } 46 | fn one() -> Self { 47 | 1 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/math/sqrt.nr: -------------------------------------------------------------------------------- 1 | use crate::math::numeric::Numeric; 2 | use std::{cmp::Ord, ops::{Add, Div, Mul}}; 3 | 4 | pub fn sqrt(value: T) -> T 5 | where 6 | T: Numeric + Mul + Ord + Add + Div, 7 | { 8 | // Safety: unsafe sqrt is checked to be between bounds 9 | let r = unsafe { 10 | let r = sqrt_unconstrained(value); 11 | assert(value >= r * r); 12 | let ONE: T = Numeric::one(); 13 | let r_plus_1 = r + ONE; 14 | assert(value < r_plus_1 * r_plus_1); 15 | r 16 | }; 17 | r 18 | } 19 | 20 | unconstrained fn sqrt_unconstrained(value: T) -> T 21 | where 22 | T: Numeric + Ord + Add + Div, 23 | { 24 | let ZERO: T = Numeric::zero(); 25 | let ONE: T = Numeric::one(); 26 | let TWO = ONE + ONE; 27 | let THREE = TWO + ONE; 28 | 29 | if value > THREE { 30 | let mut z = value; 31 | let mut x = value / TWO + ONE; 32 | for _ in 0..999999999 { 33 | if x < z { 34 | z = x; 35 | x = (value / x + x) / TWO; 36 | } else { 37 | break; 38 | } 39 | } 40 | z 41 | } else if value > ZERO { 42 | ONE 43 | } else { 44 | ZERO 45 | } 46 | } 47 | 48 | mod tests { 49 | use crate::math::sqrt::sqrt; 50 | 51 | #[test] 52 | fn test_sqrt() { 53 | let value: u128 = 170141183460469231731687303715884105727; 54 | let result = sqrt(value); 55 | assert(result == 13043817825332782212); 56 | } 57 | 58 | #[test] 59 | fn test_multiple_data_types() { 60 | assert(sqrt(18 as u8) == 4); 61 | assert(sqrt(2482737472 as u32) == 49827); 62 | assert(sqrt(14446244073709551616 as u64) == 3800821499); 63 | assert(sqrt(1444624284781234073709551616 as u128) == 38008213385809); 64 | } 65 | 66 | #[test] 67 | fn test_sqrt_native_u128() { 68 | assert(sqrt(6 as u128) == 2); 69 | } 70 | 71 | #[test] 72 | fn test_sqrt_many_numbers() { 73 | let pairs: [[u128; 2]] = &[ 74 | [0, 0], 75 | [1, 1], 76 | [2, 1], 77 | [3, 1], 78 | [4, 2], 79 | [5, 2], 80 | [6, 2], 81 | [7, 2], 82 | [8, 2], 83 | [9, 3], 84 | [10, 3], 85 | [11, 3], 86 | [12, 3], 87 | [13, 3], 88 | [14, 3], 89 | [15, 3], 90 | [16, 4], 91 | [17, 4], 92 | [18, 4], 93 | [19, 4], 94 | [20, 4], 95 | [21, 4], 96 | [22, 4], 97 | [23, 4], 98 | [24, 4], 99 | [25, 5], 100 | [26, 5], 101 | [27, 5], 102 | [28, 5], 103 | [29, 5], 104 | [30, 5], 105 | [31, 5], 106 | [32, 5], 107 | [33, 5], 108 | [34, 5], 109 | [35, 5], 110 | [36, 6], 111 | [37, 6], 112 | [38, 6], 113 | [39, 6], 114 | [40, 6], 115 | [41, 6], 116 | [42, 6], 117 | [43, 6], 118 | [44, 6], 119 | [45, 6], 120 | [46, 6], 121 | [47, 6], 122 | [48, 6], 123 | [49, 7], 124 | [50, 7], 125 | [51, 7], 126 | [52, 7], 127 | [53, 7], 128 | [54, 7], 129 | [55, 7], 130 | [56, 7], 131 | [57, 7], 132 | [58, 7], 133 | [59, 7], 134 | [60, 7], 135 | [61, 7], 136 | [62, 7], 137 | [63, 7], 138 | [64, 8], 139 | [65, 8], 140 | [66, 8], 141 | [67, 8], 142 | [68, 8], 143 | [69, 8], 144 | [70, 8], 145 | [71, 8], 146 | // some big numbers 147 | [9000000000000000000, 3000000000], 148 | [8274823429819348192323, 90966056470], 149 | ]; 150 | 151 | for pair in pairs { 152 | let value = pair[0]; 153 | let result = sqrt(value); 154 | // std::println(f"{pair} {result}"); 155 | assert(result == pair[1]); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/solidity.nr: -------------------------------------------------------------------------------- 1 | use crate::ArrayExtensions; 2 | 3 | pub global SELECTOR_LENGTH: u32 = 4; 4 | 5 | /// Encodes a function selector and its arguments into a byte array. 6 | /// 7 | /// ## Arguments 8 | /// 9 | /// * `selector` - The function selector to encode. 10 | /// * `args` - The arguments to encode. 11 | /// 12 | /// ## Returns 13 | /// 14 | /// A byte array containing the function selector and its arguments. 15 | /// 16 | /// ## Example 17 | /// 18 | /// ```noir 19 | /// let selector: u32 = 0xa9059cbb; // transfer(address,uint256) 20 | /// let args: [Field; 2] = [ 21 | /// 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, // address 22 | /// 123, // uint256 23 | /// ]; 24 | /// let encoded = encode_with_selector(selector, args); 25 | /// ``` 26 | pub fn encode_with_selector( 27 | selector: u32, 28 | args: [Field; N], 29 | ) -> [u8; SELECTOR_LENGTH + N * 32] { 30 | let selector_bytes: [u8; SELECTOR_LENGTH] = (selector as Field).to_be_bytes(); 31 | encode_with_selector_bytes(selector_bytes, args) 32 | } 33 | 34 | pub fn encode_with_selector_bytes( 35 | selector: [u8; SELECTOR_LENGTH], 36 | args: [Field; N], 37 | ) -> [u8; SELECTOR_LENGTH + N * 32] { 38 | let mut result = [0; SELECTOR_LENGTH + N * 32]; 39 | 40 | for i in 0..SELECTOR_LENGTH { 41 | result[i] = selector[i]; 42 | } 43 | 44 | for i in 0..N { 45 | let bytes: [u8; 32] = args[i].to_be_bytes(); 46 | for j in 0..32 { 47 | result[SELECTOR_LENGTH + i * 32 + j] = bytes[j]; 48 | } 49 | } 50 | 51 | result 52 | } 53 | 54 | pub fn to_selector(signature: str) -> [u8; SELECTOR_LENGTH] { 55 | crate::keccak256(signature.as_bytes()).slice::(0) 56 | } 57 | 58 | mod tests { 59 | use crate::solidity::{encode_with_selector, encode_with_selector_bytes, to_selector}; 60 | 61 | #[test] 62 | fn test_simple_selector() { 63 | let selector: u32 = 0xa9059cbb; // transfer(address,uint256) 64 | let args: [Field; 2] = [ 65 | 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, // address 66 | 123, // uint256 67 | ]; 68 | let encoded = encode_with_selector(selector, args); 69 | let expected: [u8; 68] = [ 70 | 169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 218, 107, 242, 105, 100, 175, 71 | 157, 126, 237, 158, 3, 229, 52, 21, 211, 122, 169, 96, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 73 | ]; 74 | assert(encoded == expected); 75 | } 76 | 77 | #[test] 78 | fn test_simple_signature() { 79 | let args: [Field; 2] = [ 80 | 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, // address 81 | 123, // uint256 82 | ]; 83 | 84 | let encoded = 85 | encode_with_selector_bytes(comptime { to_selector("transfer(address,uint256)") }, args); 86 | let expected: [u8; 68] = [ 87 | 169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 218, 107, 242, 105, 100, 175, 88 | 157, 126, 237, 158, 3, 229, 52, 21, 211, 122, 169, 96, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 89 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 90 | ]; 91 | assert(encoded == expected); 92 | } 93 | 94 | #[test] 95 | fn test_deposit() { 96 | let result = encode_with_selector( 97 | // FunctionSelector::from_signature("deposit_private(bytes32,address[2],uint256[2])"), 98 | 0x86d09dae, 99 | [ 100 | 0x1a7097a0f09457b0b0496684b2ed6723a262da3a1b061b598ee92ce3376cb302, 101 | 0x610178dA211FEF7D417bC0e6FeD39F05609AD788, 102 | 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e, 103 | 333, 104 | 64, 105 | ], 106 | ); 107 | let expected = [ 108 | 134, 208, 157, 174, 26, 112, 151, 160, 240, 148, 87, 176, 176, 73, 102, 132, 178, 237, 109 | 103, 35, 162, 98, 218, 58, 27, 6, 27, 89, 142, 233, 44, 227, 55, 108, 179, 2, 0, 0, 0, 110 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 1, 120, 218, 33, 31, 239, 125, 65, 123, 192, 230, 254, 111 | 211, 159, 5, 96, 154, 215, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 183, 248, 188, 99, 112 | 187, 202, 209, 129, 85, 32, 19, 8, 200, 243, 84, 11, 7, 248, 79, 94, 0, 0, 0, 0, 0, 0, 113 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 77, 0, 0, 0, 114 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 115 | ]; 116 | assert(result == expected); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/string.nr: -------------------------------------------------------------------------------- 1 | global ASCII_HEX_TABLE: [u8; 16] = "0123456789abcdef".as_bytes(); 2 | 3 | pub fn field_to_hex(value: Field) -> str<64> { 4 | let mut result = [0 as u8; 64]; 5 | let bytes = value.to_be_bytes::<32>().map(|x| x as u32); 6 | for i in 0..bytes.len() { 7 | result[i * 2] = ASCII_HEX_TABLE[bytes[i] / 16]; 8 | result[i * 2 + 1] = ASCII_HEX_TABLE[bytes[i] % 16]; 9 | } 10 | From::from(result) 11 | } 12 | 13 | #[deprecated("use field_to_hex instead")] 14 | pub fn to_hex_string_bytes(value: Field) -> [u8; 64] { 15 | field_to_hex(value).as_bytes() 16 | } 17 | 18 | global U64_STR_LEN: u32 = 20; 19 | pub fn str_to_u64(arr: impl Into<[u8; U64_STR_LEN]>) -> u64 { 20 | let arr = arr.into().map(|x| x as u32); 21 | 22 | let mut parsed_number: Field = 0; 23 | for i in 0..U64_STR_LEN { 24 | let chr = arr[i]; 25 | if chr != 0 { 26 | parsed_number *= 10; 27 | let value = crate::tables::ASCII_TO_NUMBER[arr[i]] as Field; 28 | parsed_number += value; 29 | } 30 | } 31 | parsed_number as u64 32 | } 33 | 34 | pub fn ord(s: str<1>) -> u8 { 35 | s.as_bytes()[0] 36 | } 37 | 38 | mod tests { 39 | use crate::string::{field_to_hex, ord, str_to_u64}; 40 | 41 | #[test] 42 | fn test_field_to_hex() { 43 | let my_hash = 0x0d67824fead966192029093a3aa5c719f2b80262c4f14a5c97c5d70e4b27f2bf; 44 | let expected = "0d67824fead966192029093a3aa5c719f2b80262c4f14a5c97c5d70e4b27f2bf"; 45 | assert_eq(field_to_hex(my_hash), expected); 46 | } 47 | 48 | #[test] 49 | fn some_test() { 50 | let outer_hash = 0x0d67824fead966192029093a3aa5c719f2b80262c4f14a5c97c5d70e4b27f2bf; 51 | let hex_challenge = field_to_hex(outer_hash); 52 | let header_prefix: [u8; 26] = "subject:Re: Tx request: 0x".as_bytes(); 53 | let header: [u8; 90] = header_prefix.concat(hex_challenge.as_bytes()); 54 | assert( 55 | header 56 | == [ 57 | 115, 117, 98, 106, 101, 99, 116, 58, 82, 101, 58, 32, 84, 120, 32, 114, 101, 58 | 113, 117, 101, 115, 116, 58, 32, 48, 120, 48, 100, 54, 55, 56, 50, 52, 102, 101, 59 | 97, 100, 57, 54, 54, 49, 57, 50, 48, 50, 57, 48, 57, 51, 97, 51, 97, 97, 53, 99, 60 | 55, 49, 57, 102, 50, 98, 56, 48, 50, 54, 50, 99, 52, 102, 49, 52, 97, 53, 99, 61 | 57, 55, 99, 53, 100, 55, 48, 101, 52, 98, 50, 55, 102, 50, 98, 102, 62 | ], 63 | ); 64 | } 65 | 66 | #[test] 67 | fn test_str_to_u64() { 68 | let s = "13378584420".as_bytes(); 69 | assert(str_to_u64(s.concat([0; 9])) == 13378584420); 70 | assert(str_to_u64("02345678912345678912") == 02345678912345678912); 71 | } 72 | 73 | #[test] 74 | fn test_char() { 75 | assert(ord("a") == 97); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/tables.nr: -------------------------------------------------------------------------------- 1 | // https://github.com/noir-lang/noir_json_parser/blob/bc7094394baeaa185c5bf56ae806e302c786bdd3/src/json_tables.nr 2 | pub(crate) global ASCII_TO_NUMBER: [u8; 128] = [ 3 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4 | /* */ 5 | 0, /*"!"*/ 6 | 0, /* " */ 7 | 0, /*"#"*/ 8 | 0, /*"$"*/ 9 | 0, /*"%"*/ 10 | 0, /*"&"*/ 11 | 0, /*"'"*/ 12 | 0, /*"("*/ 13 | 0, /*")"*/ 14 | 0, /*"*"*/ 15 | 0, /*"+"*/ 16 | 0, /*","*/ 17 | 0, /*"-"*/ 18 | 0, /*"."*/ 19 | 0, /*"/"*/ 20 | 0, /*"0"*/ 21 | 0, // numeric value 22 | /*"1"*/ 23 | 1, // numeric value 24 | /*"2"*/ 25 | 2, // numeric value 26 | /*"3"*/ 27 | 3, // numeric value 28 | /*"4"*/ 29 | 4, // numeric value 30 | /*"5"*/ 31 | 5, // numeric value 32 | /*"6"*/ 33 | 6, // numeric value 34 | /*"7"*/ 35 | 7, // numeric value 36 | /*"8"*/ 37 | 8, // numeric value 38 | /*"9"*/ 39 | 9, // numeric value 40 | /*":"*/ 41 | 0, /*";"*/ 42 | 0, /*"<"*/ 43 | 0, /*"="*/ 44 | 0, /*">"*/ 45 | 0, /*"?"*/ 46 | 0, /*"@"*/ 47 | 0, /*"A"*/ 48 | 0, /*"B"*/ 49 | 0, /*"C"*/ 50 | 0, /*"D"*/ 51 | 0, /*"E"*/ 52 | 0, /*"F"*/ 53 | 0, /*"G"*/ 54 | 0, /*"H"*/ 55 | 0, /*"I"*/ 56 | 0, /*"J"*/ 57 | 0, /*"K"*/ 58 | 0, /*"L"*/ 59 | 0, /*"M"*/ 60 | 0, /*"N"*/ 61 | 0, /*"O"*/ 62 | 0, /*"P"*/ 63 | 0, /*"Q"*/ 64 | 0, /*"R"*/ 65 | 0, /*"S"*/ 66 | 0, /*"T"*/ 67 | 0, /*"U"*/ 68 | 0, /*"V"*/ 69 | 0, /*"W"*/ 70 | 0, /*"X"*/ 71 | 0, /*"Y"*/ 72 | 0, /*"Z"*/ 73 | 0, /*"["*/ 74 | 0, // an array 75 | /*"\"*/ 76 | 0, /*"]"*/ 77 | 0, /*"^"*/ 78 | 0, /*"_"*/ 79 | 0, /*"`"*/ 80 | 0, /*"a"*/ 81 | 0, /*"b"*/ 82 | 0, /*"c"*/ 83 | 0, /*"d"*/ 84 | 0, /*"e"*/ 85 | 0, /*"f"*/ 86 | 0, // "0" 87 | /*"g"*/ 88 | 0, /*"h"*/ 89 | 0, /*"i"*/ 90 | 0, /*"j"*/ 91 | 0, /*"k"*/ 92 | 0, /*"l"*/ 93 | 0, /*"m"*/ 94 | 0, /*"n"*/ 95 | 0, /*"o"*/ 96 | 0, /*"p"*/ 97 | 0, /*"q"*/ 98 | 0, /*"r"*/ 99 | 0, /*"s"*/ 100 | 0, /*"t"*/ 101 | 0, // "0" 102 | /*"u"*/ 103 | 0, /*"v"*/ 104 | 0, /*"w"*/ 105 | 0, /*"x"*/ 106 | 0, /*"y"*/ 107 | 0, /*"z"*/ 108 | 0, /*"{"*/ 109 | 0, // an object 110 | /*"|"*/ 111 | 0, /*"}"*/ 112 | 0, /*"~"*/ 113 | 0, /*DEL*/ 114 | 0, 115 | ]; 116 | -------------------------------------------------------------------------------- /src/validate_inputs.nr: -------------------------------------------------------------------------------- 1 | pub comptime fn validate_inputs(f: FunctionDefinition) { 2 | let validated_inputs = f 3 | .parameters() 4 | .map(|(name, _typ): (Quoted, Type)| quote {{ nodash::ValidateInput::validate($name); }}) 5 | .join(quote {;}); 6 | let checks_body = quote {{ $validated_inputs }}.as_expr().expect( 7 | f"failed to parse ValidateInput checks code", 8 | ); // should never fail 9 | 10 | let old_body = f.body(); 11 | let checked_body = quote {{ 12 | $checks_body; 13 | $old_body 14 | }}; 15 | f.set_body(checked_body.as_expr().expect(f"failed to concatenate body with checks")); 16 | } 17 | 18 | #[derive_via(derive_validate_input)] 19 | pub trait ValidateInput { 20 | fn validate(self); 21 | } 22 | 23 | comptime fn derive_validate_input(s: TypeDefinition) -> Quoted { 24 | let name = quote { nodash::ValidateInput }; 25 | let signature = quote { fn validate(self) }; 26 | let for_each_field = |name| quote { nodash::ValidateInput::validate(self.$name); }; 27 | let body = |fields| quote { $fields }; 28 | std::meta::make_trait_impl(s, name, signature, for_each_field, quote { , }, body) 29 | } 30 | 31 | impl ValidateInput for u8 { 32 | fn validate(self) {} 33 | } 34 | 35 | impl ValidateInput for u16 { 36 | fn validate(self) {} 37 | } 38 | 39 | impl ValidateInput for u32 { 40 | fn validate(self) {} 41 | } 42 | 43 | impl ValidateInput for u64 { 44 | fn validate(self) {} 45 | } 46 | 47 | impl ValidateInput for i8 { 48 | fn validate(self) {} 49 | } 50 | impl ValidateInput for i16 { 51 | fn validate(self) {} 52 | } 53 | impl ValidateInput for i32 { 54 | fn validate(self) {} 55 | } 56 | 57 | impl ValidateInput for i64 { 58 | fn validate(self) {} 59 | } 60 | 61 | impl ValidateInput for Field { 62 | fn validate(self) {} 63 | } 64 | 65 | impl ValidateInput for bool { 66 | fn validate(self) {} 67 | } 68 | 69 | impl ValidateInput for str { 70 | fn validate(self) {} 71 | } 72 | 73 | impl ValidateInput for [T; N] 74 | where 75 | T: ValidateInput, 76 | { 77 | fn validate(mut self) { 78 | for i in 0..N { 79 | self[i].validate(); 80 | } 81 | } 82 | } 83 | 84 | impl ValidateInput for BoundedVec 85 | where 86 | T: ValidateInput, 87 | { 88 | fn validate(mut self) { 89 | for i in 0..MaxLen { 90 | if i < self.len() { 91 | self.get_unchecked(i).validate() 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/Nargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | type = "lib" 4 | 5 | [dependencies] 6 | nodash = { path = "../" } 7 | -------------------------------------------------------------------------------- /tests/src/lib.nr: -------------------------------------------------------------------------------- 1 | mod validate_inputs; 2 | -------------------------------------------------------------------------------- /tests/src/validate_inputs.nr: -------------------------------------------------------------------------------- 1 | #[nodash::validate_inputs] 2 | fn my_main(a: Field, b: u64) -> Field { 3 | a + b as Field 4 | } 5 | 6 | #[test] 7 | fn test_validate_inputs() { 8 | let result = my_main(1, 2); 9 | assert(result == 3); 10 | } 11 | 12 | #[nodash::validate_inputs] 13 | fn main_collections(a: [U120; 1], b: BoundedVec) -> Field { 14 | a[0].inner + b.get(0).inner 15 | } 16 | 17 | #[test] 18 | fn test_validate_collections() { 19 | let result = main_collections( 20 | [U120::new(1)], 21 | BoundedVec::from_parts([U120::new(2), U120 { inner: 2.pow_32(120) }], 1), 22 | ); 23 | assert(result == 3); 24 | } 25 | 26 | #[test(should_fail_with = "call to assert_max_bit_size")] 27 | fn test_validate_array_fail() { 28 | let _ = main_collections([U120 { inner: 2.pow_32(120) }], BoundedVec::new()); 29 | } 30 | 31 | #[test(should_fail_with = "call to assert_max_bit_size")] 32 | fn test_validate_bounded_vec_fail() { 33 | let _ = main_collections( 34 | [U120::new(1)], 35 | BoundedVec::from_parts([U120::new(2), U120 { inner: 2.pow_32(120) }], 2), 36 | ); 37 | } 38 | 39 | #[nodash::validate_inputs] 40 | fn main_u120(a: U120) -> Field { 41 | a.inner 42 | } 43 | 44 | #[test] 45 | fn test_validate_u120() { 46 | let inner = 2.pow_32(120) - 1; 47 | let result = main_u120(U120 { inner }); 48 | assert(result == inner); 49 | } 50 | 51 | #[test(should_fail_with = "call to assert_max_bit_size")] 52 | fn test_validate_u120_fail() { 53 | let inner = 2.pow_32(120); 54 | let _ = main_u120(U120 { inner }); 55 | } 56 | 57 | #[nodash::validate_inputs] 58 | fn main_struct_derive(a: NestedStruct) -> Field { 59 | a.value.inner 60 | } 61 | 62 | #[test] 63 | fn test_validate_struct_derive() { 64 | let inner = 2.pow_32(120) - 1; 65 | let result = main_struct_derive(NestedStruct { value: U120 { inner } }); 66 | assert(result == inner); 67 | } 68 | 69 | #[test(should_fail_with = "call to assert_max_bit_size")] 70 | fn test_validate_struct_derive_fail() { 71 | let inner = 2.pow_32(120); 72 | let _ = main_struct_derive(NestedStruct { value: U120 { inner } }); 73 | } 74 | 75 | struct U120 { 76 | inner: Field, 77 | } 78 | 79 | impl U120 { 80 | fn new(inner: Field) -> Self { 81 | inner.assert_max_bit_size::<120>(); 82 | Self { inner } 83 | } 84 | } 85 | 86 | impl nodash::ValidateInput for U120 { 87 | fn validate(self) { 88 | let _ = U120::new(self.inner); 89 | } 90 | } 91 | 92 | #[derive(nodash::ValidateInput)] 93 | struct NestedStruct { 94 | value: U120, 95 | } 96 | --------------------------------------------------------------------------------