Skip to content

Commit a0a7b98

Browse files
authored
chore: add CI and stuff (#5)
# Description ## Problem\* Resolves <!-- Link to GitHub Issue --> ## Summary\* This PR adds CI, updates to the latest noir and deletes some unused functions. ## Additional Context # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
1 parent bed8e87 commit a0a7b98

10 files changed

+163
-89
lines changed

.github/NIGHTLY_CANARY_DIED.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: "Tests fail on latest Nargo nightly release"
3+
assignees: TomAFrench
4+
---
5+
6+
The tests on this Noir project have started failing when using the latest nightly release of the Noir compiler. This likely means that there have been breaking changes for which this project needs to be updated to take into account.
7+
8+
Check the [{{env.WORKFLOW_NAME}}]({{env.WORKFLOW_URL}}) workflow for details.

.github/workflows/nightly-canary.yml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Noir Nightly Canary
2+
3+
on:
4+
schedule:
5+
# Run a check at 9 AM UTC
6+
- cron: "0 9 * * *"
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
11+
permissions:
12+
issues: write
13+
14+
jobs:
15+
test:
16+
name: Test on Nargo ${{matrix.toolchain}}
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout sources
20+
uses: actions/checkout@v4
21+
22+
- name: Install Nargo
23+
uses: noir-lang/[email protected]
24+
with:
25+
toolchain: nightly
26+
27+
- name: Run Noir tests
28+
run: nargo test
29+
30+
- name: Run formatter
31+
run: nargo fmt --check
32+
33+
- name: Alert on dead canary
34+
uses: JasonEtco/create-an-issue@v2
35+
if: ${{ failure() }}
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
WORKFLOW_NAME: ${{ github.workflow }}
39+
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
40+
with:
41+
update_existing: true
42+
filename: .github/NIGHTLY_CANARY_DIED.md
43+

.github/workflows/test.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Noir tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
test:
14+
name: Test on Nargo ${{matrix.toolchain}}
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
toolchain: [nightly, 0.34.0]
20+
steps:
21+
- name: Checkout sources
22+
uses: actions/checkout@v4
23+
24+
- name: Install Nargo
25+
uses: noir-lang/[email protected]
26+
with:
27+
toolchain: ${{ matrix.toolchain }}
28+
29+
- name: Run Noir tests
30+
run: nargo test
31+
32+
format:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: Checkout sources
36+
uses: actions/checkout@v4
37+
38+
- name: Install Nargo
39+
uses: noir-lang/[email protected]
40+
with:
41+
toolchain: 0.34.0
42+
43+
- name: Run formatter
44+
run: nargo fmt --check

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
/target
1+
target

Nargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
name = "noir_string_search"
33
type = "lib"
44
authors = [""]
5-
compiler_version = ">=0.32.0"
5+
compiler_version = ">=0.34.0"
66

7-
[dependencies]
7+
[dependencies]

info.sh

-1
This file was deleted.

src/lib.nr

+41-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod utils;
22

3-
use utils::{conditional_select, lt_f, DebugRandomEngine};
3+
pub use utils::{conditional_select, lt_f, DebugRandomEngine};
44

55
/**
66
* @brief represents a byte-array of up to MaxBytes, that is used as a "haystack" array,
@@ -149,9 +149,16 @@ impl<let MaxPaddedBytes: u32, let PaddedChunksMinusOne: u32, let MaxBytes: u32>
149149

150150
impl<let MaxPaddedBytes: u32, let PaddedChunksMinusOne: u32, let MaxBytes: u32> SubStringTrait for SubString<MaxPaddedBytes, PaddedChunksMinusOne, MaxBytes> {
151151

152-
fn len(self) -> u32 { self.byte_length }
153-
fn get(self, idx: Field) -> u8 { self.body[idx] }
154-
fn get_body(self) -> [u8] { let x = self.body.as_slice(); x }
152+
fn len(self) -> u32 {
153+
self.byte_length
154+
}
155+
fn get(self, idx: Field) -> u8 {
156+
self.body[idx]
157+
}
158+
fn get_body(self) -> [u8] {
159+
let x = self.body.as_slice();
160+
x
161+
}
155162

156163
/**
157164
* @brief given some `haystack` 31-byte chunks, validate that there exist `num_full_chunks`
@@ -187,7 +194,7 @@ impl<let MaxPaddedBytes: u32, let PaddedChunksMinusOne: u32, let MaxBytes: u32>
187194
let rhs = haystack[predicate as Field * (i as Field + starting_haystack_chunk)];
188195
assert(predicate * (lhs - rhs) == 0);
189196
}
190-
}
197+
}
191198
}
192199

193200
// ######################################################
@@ -206,25 +213,7 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
206213
for i in 0..InputBytes {
207214
body[i] = data[i];
208215
}
209-
StringBody { body, chunks: StringBody::compute_chunks(body), byte_length: length }
210-
}
211-
212-
/**
213-
* @brief given an input byte array, convert into 31-byte chunks
214-
* cost is ~0.5 gates per byte
215-
**/
216-
fn compute_chunks(body: [u8; MaxPaddedBytes]) -> [Field; PaddedChunks] {
217-
let mut chunks: [Field; PaddedChunks] = [0; PaddedChunks];
218-
for i in 0..PaddedChunks {
219-
let mut limb: Field = 0;
220-
for j in 0..31 {
221-
limb *= 256;
222-
limb += body[i * 31 + j] as Field;
223-
}
224-
chunks[i] = limb;
225-
std::as_witness(chunks[i]);
226-
}
227-
chunks
216+
StringBody { body, chunks: compute_chunks(body), byte_length: length }
228217
}
229218

230219
/**
@@ -237,12 +226,15 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
237226
// use unconstrained function to determine:
238227
// a: is the substring present in the body text
239228
// b: the position of the first match in the body text
240-
let position: u32 = utils::search(
241-
self.body,
242-
substring.get_body(),
243-
self.byte_length,
244-
substring.len()
245-
);
229+
let position: u32 = unsafe {
230+
// Safety: The rest of this function checks this.
231+
utils::search(
232+
self.body,
233+
substring.get_body(),
234+
self.byte_length,
235+
substring.len()
236+
)
237+
};
246238

247239
assert(
248240
position + substring.len() <= self.byte_length, "substring not present in main text (match found if a padding text included. is main text correctly formatted?)"
@@ -311,8 +303,8 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
311303
let initial_haystack_chunk = self.chunks[chunk_index];
312304
let final_haystack_chunk = self.chunks[chunk_index_of_final_haystack_chunk_with_matching_needle_bytes];
313305

314-
let initial_body_bytes: [u8; 31] = initial_haystack_chunk.to_be_bytes(31).as_array();
315-
let final_body_bytes: [u8; 31] = final_haystack_chunk.to_be_bytes(31).as_array();
306+
let initial_body_bytes: [u8; 31] = initial_haystack_chunk.to_be_bytes();
307+
let final_body_bytes: [u8; 31] = final_haystack_chunk.to_be_bytes();
316308

317309
// When defining the initial chunk bytes, we can represent as Field elements as we are deriving values from known bytes.
318310
// This saves us a few gates
@@ -409,6 +401,23 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
409401
}
410402
}
411403

404+
/// Given an input byte array, convert into 31-byte chunks
405+
///
406+
/// Cost: ~0.5 gates per byte
407+
fn compute_chunks<let MaxPaddedBytes: u32, let PaddedChunks: u32>(body: [u8; MaxPaddedBytes]) -> [Field; PaddedChunks] {
408+
let mut chunks: [Field; PaddedChunks] = [0; PaddedChunks];
409+
for i in 0..PaddedChunks {
410+
let mut limb: Field = 0;
411+
for j in 0..31 {
412+
limb *= 256;
413+
limb += body[i * 31 + j] as Field;
414+
}
415+
chunks[i] = limb;
416+
std::as_witness(chunks[i]);
417+
}
418+
chunks
419+
}
420+
412421
#[test]
413422
fn test() {
414423
let haystack_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".as_bytes();

src/utils.nr

+24-45
Original file line numberDiff line numberDiff line change
@@ -28,64 +28,43 @@ unconstrained pub fn search<let N: u32>(
2828
found_index
2929
}
3030

31-
/**
32-
* @brief validate the body text contains zero-values for all indices >= byte_length
33-
* @note NOT NEEDED. Consider removing. Values beyond byte_length are not used in matching algorithm so no need to constrain them
34-
**/
35-
fn validate_body<let BODYBYTES: u32, let BODYCHUNKS: u32>(data: [u8; BODYBYTES], length: u32, _: [Field; BODYCHUNKS]) {
36-
// we want a conditional assert for cases where i >= length
37-
// if i >= length we want to assert that data = 0
38-
let mut delta: Field = length as Field;
39-
for i in 0..BODYBYTES {
40-
let predicate = lt_f(i as Field, length as Field);
41-
let predicate = get_lt_predicate_f(i as Field, length as Field);
42-
43-
let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
44-
lt_parameter.assert_max_bit_size(32);
45-
delta = delta - 1;
46-
std::as_witness(delta);
47-
48-
// assert that if predicate = 0 then byte = 0
49-
assert(data[i] as Field * predicate as Field == data[i] as Field);
50-
}
51-
}
52-
5331
unconstrained fn __conditional_select(lhs: u8, rhs: u8, predicate: bool) -> u8 {
54-
let mut result: u8 = 0;
55-
if (predicate) {
56-
result = lhs;
57-
} else {
58-
result = rhs;
59-
}
60-
result
32+
if (predicate) { lhs } else { rhs }
6133
}
6234

6335
pub fn conditional_select<T>(lhs: u8, rhs: u8, predicate: bool) -> u8 {
64-
let result = __conditional_select(lhs, rhs, predicate);
65-
let result_f = result as Field;
66-
let lhs_f = lhs as Field;
67-
let rhs_f = rhs as Field;
36+
// Safety: This is all just a very verbose `if (predicate) { lhs } else { rhs }`
37+
// formulated as `rhs + (lhs - rhs) * predicate`
38+
unsafe {
39+
let result = __conditional_select(lhs, rhs, predicate);
40+
let result_f = result as Field;
41+
let lhs_f = lhs as Field;
42+
let rhs_f = rhs as Field;
6843

69-
let diff = lhs_f - rhs_f;
70-
std::as_witness(diff);
71-
assert((predicate as Field) * (diff) + rhs_f == result_f);
72-
result
44+
let diff = lhs_f - rhs_f;
45+
std::as_witness(diff);
46+
assert_eq((predicate as Field) * diff + rhs_f, result_f);
47+
result
48+
}
7349
}
7450

7551
unconstrained pub fn get_lt_predicate_f(x: Field, y: Field) -> bool {
7652
let a = x as u32;
7753
let b = y as u32;
78-
let r = a < b;
79-
r
54+
a < b
8055
}
8156

8257
pub fn lt_f(x: Field, y: Field) -> bool {
83-
let predicate = get_lt_predicate_f(x, y);
84-
let delta = y as Field - x as Field;
85-
let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
86-
lt_parameter.assert_max_bit_size(32);
58+
// Safety: As `x` and `y` are known to be valid `u32`s, this function reimplements the
59+
// compiler's internal implementation of `lt`
60+
unsafe {
61+
let predicate = get_lt_predicate_f(x, y);
62+
let delta = y as Field - x as Field;
63+
let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
64+
lt_parameter.assert_max_bit_size(32);
8765

88-
predicate
66+
predicate
67+
}
8968
}
9069

9170
struct DebugRandomEngine {
@@ -95,7 +74,7 @@ struct DebugRandomEngine {
9574
impl DebugRandomEngine {
9675
unconstrained fn get_random_32_bytes(&mut self) -> [u8; 32] {
9776
self.seed += 1;
98-
let input: [u8; 32] = self.seed.to_be_bytes(32).as_array();
77+
let input: [u8; 32] = self.seed.to_be_bytes();
9978
let hash: [u8; 32] = dep::std::hash::sha256(input);
10079
hash
10180
}

target/noir_string_search.json

-1
This file was deleted.

target/noir_string_search.txt

-7
This file was deleted.

0 commit comments

Comments
 (0)