Skip to content

Commit 7172080

Browse files
authored
feat: validate inputs of fn main (#4)
Related noir-lang/noir#7181 Related noir-lang/noir#4218
1 parent f7fdec1 commit 7172080

File tree

7 files changed

+276
-7
lines changed

7 files changed

+276
-7
lines changed

.github/workflows/test.yml

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
name: Noir tests
22

3-
on:
4-
push:
5-
branches:
6-
- main
7-
pull_request:
3+
on: [push]
84

95
env:
106
CARGO_TERM_COLOR: always
@@ -27,7 +23,9 @@ jobs:
2723
toolchain: ${{ matrix.toolchain }}
2824

2925
- name: Run Noir tests
30-
run: nargo test
26+
run: |
27+
nargo test
28+
cd tests && nargo test
3129
3230
format:
3331
runs-on: ubuntu-latest

README.md

+69-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Nodash is a utility library for [Noir](https://github.com/noir-lang/noir) langua
77
Put this into your Nargo.toml.
88

99
```toml
10-
nodash = { git = "https://github.com/olehmisar/nodash/", tag = "v0.39.5" }
10+
nodash = { git = "https://github.com/olehmisar/nodash/", tag = "v0.39.6" }
1111
```
1212

1313
## Docs
@@ -201,3 +201,71 @@ use nodash::pack_bytes;
201201
let bytes: [u8; 32] = [0; 32];
202202
let packed = pack_bytes(bytes);
203203
```
204+
205+
### Validate `main` function inputs
206+
207+
`fn main` inputs are not validated by Noir. For example, you have a `U120` struct like this:
208+
209+
```rs
210+
struct U120 {
211+
inner: Field,
212+
}
213+
214+
impl U120 {
215+
fn new(inner: Field) -> Self {
216+
inner.assert_max_bit_size::<120>();
217+
Self { inner }
218+
}
219+
}
220+
```
221+
222+
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`.
223+
224+
However, Noir does not check the validity of `U120` fields when passed to a `fn main` function. For example, for this circuit
225+
226+
```rs
227+
fn main(a: U120) {
228+
// do something with a
229+
}
230+
```
231+
232+
...you can pass any arbitrary value to `a` from JavaScript and it will NOT fail in Noir when `main` is executed:
233+
234+
```js
235+
// this succeeds but it shouldn't!
236+
await noir.execute({
237+
a: {
238+
inner: 2n * 10n ** 120n + 1n,
239+
},
240+
});
241+
```
242+
243+
To fix this, you can use the `validate_inputs` attribute on the `main` function:
244+
245+
```rs
246+
use nodash::{validate_inputs, ValidateInput};
247+
248+
// this attribute checks that `U120` is within the range via `ValidateInput` trait
249+
#[validate_inputs]
250+
fn main(a: U120) {
251+
// do something with a
252+
}
253+
254+
impl ValidateInput for U120 {
255+
fn validate(self) {
256+
// call the `new` function that asserts the max bit size
257+
U120::new(self.inner);
258+
}
259+
}
260+
```
261+
262+
Now, if you pass a value that is larger than 2^120 to `a` in JavaScript, you will get a runtime error:
263+
264+
```js
265+
// runtime error: "Assertion failed: call to assert_max_bit_size"
266+
await noir.execute({
267+
a: {
268+
inner: 2n * 10n ** 120n + 1n,
269+
},
270+
});
271+
```

src/lib.nr

+2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ mod solidity;
44
mod string;
55
mod tables;
66
mod array;
7+
mod validate_inputs;
78

89
pub use array::pack_bytes;
910
pub use hash::{keccak256, pedersen, poseidon2, sha256};
1011
pub use math::{clamp, div_ceil, sqrt::sqrt};
1112
pub use string::{field_to_hex, ord, str_to_u64, to_hex_string_bytes};
13+
pub use validate_inputs::{validate_inputs, ValidateInput};
1214

1315
pub trait ArrayExtensions<T, let N: u32> {
1416
fn slice<let L: u32>(self, start: u32) -> [T; L];

src/validate_inputs.nr

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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: StructDefinition) -> 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 U128 {
70+
fn validate(self) {}
71+
}
72+
73+
impl<let N: u32> ValidateInput for str<N> {
74+
fn validate(self) {}
75+
}
76+
77+
impl<T, let N: u32> ValidateInput for [T; N]
78+
where
79+
T: ValidateInput,
80+
{
81+
fn validate(mut self) {
82+
for i in 0..N {
83+
self[i].validate();
84+
}
85+
}
86+
}
87+
88+
impl<T, let MaxLen: u32> ValidateInput for BoundedVec<T, MaxLen>
89+
where
90+
T: ValidateInput,
91+
{
92+
fn validate(mut self) {
93+
for i in 0..MaxLen {
94+
if i < self.len() {
95+
self.get_unchecked(i).validate()
96+
}
97+
}
98+
}
99+
}

tests/Nargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "tests"
3+
type = "lib"
4+
5+
[dependencies]
6+
nodash = { path = "../" }

tests/src/lib.nr

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod validate_inputs;

tests/src/validate_inputs.nr

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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<U120, 2>) -> 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+
}

0 commit comments

Comments
 (0)