Skip to content

Commit

Permalink
chore: add CI and stuff (#5)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
TomAFrench authored Sep 17, 2024
1 parent bed8e87 commit a0a7b98
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 89 deletions.
8 changes: 8 additions & 0 deletions .github/NIGHTLY_CANARY_DIED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "Tests fail on latest Nargo nightly release"
assignees: TomAFrench
---

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.

Check the [{{env.WORKFLOW_NAME}}]({{env.WORKFLOW_URL}}) workflow for details.
43 changes: 43 additions & 0 deletions .github/workflows/nightly-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Noir Nightly Canary

on:
schedule:
# Run a check at 9 AM UTC
- cron: "0 9 * * *"

env:
CARGO_TERM_COLOR: always

permissions:
issues: write

jobs:
test:
name: Test on Nargo ${{matrix.toolchain}}
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install Nargo
uses: noir-lang/noirup@v0.1.3
with:
toolchain: nightly

- name: Run Noir tests
run: nargo test

- name: Run formatter
run: nargo fmt --check

- name: Alert on dead canary
uses: JasonEtco/create-an-issue@v2
if: ${{ failure() }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WORKFLOW_NAME: ${{ github.workflow }}
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
update_existing: true
filename: .github/NIGHTLY_CANARY_DIED.md

44 changes: 44 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Noir tests

on:
push:
branches:
- main
pull_request:

env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Test on Nargo ${{matrix.toolchain}}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain: [nightly, 0.34.0]
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install Nargo
uses: noir-lang/noirup@v0.1.3
with:
toolchain: ${{ matrix.toolchain }}

- name: Run Noir tests
run: nargo test

format:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install Nargo
uses: noir-lang/noirup@v0.1.3
with:
toolchain: 0.34.0

- name: Run formatter
run: nargo fmt --check
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/target
target
4 changes: 2 additions & 2 deletions Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
name = "noir_string_search"
type = "lib"
authors = [""]
compiler_version = ">=0.32.0"
compiler_version = ">=0.34.0"

[dependencies]
[dependencies]
1 change: 0 additions & 1 deletion info.sh

This file was deleted.

73 changes: 41 additions & 32 deletions src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod utils;

use utils::{conditional_select, lt_f, DebugRandomEngine};
pub use utils::{conditional_select, lt_f, DebugRandomEngine};

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

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

fn len(self) -> u32 { self.byte_length }
fn get(self, idx: Field) -> u8 { self.body[idx] }
fn get_body(self) -> [u8] { let x = self.body.as_slice(); x }
fn len(self) -> u32 {
self.byte_length
}
fn get(self, idx: Field) -> u8 {
self.body[idx]
}
fn get_body(self) -> [u8] {
let x = self.body.as_slice();
x
}

/**
* @brief given some `haystack` 31-byte chunks, validate that there exist `num_full_chunks`
Expand Down Expand Up @@ -187,7 +194,7 @@ impl<let MaxPaddedBytes: u32, let PaddedChunksMinusOne: u32, let MaxBytes: u32>
let rhs = haystack[predicate as Field * (i as Field + starting_haystack_chunk)];
assert(predicate * (lhs - rhs) == 0);
}
}
}
}

// ######################################################
Expand All @@ -206,25 +213,7 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
for i in 0..InputBytes {
body[i] = data[i];
}
StringBody { body, chunks: StringBody::compute_chunks(body), byte_length: length }
}

/**
* @brief given an input byte array, convert into 31-byte chunks
* cost is ~0.5 gates per byte
**/
fn compute_chunks(body: [u8; MaxPaddedBytes]) -> [Field; PaddedChunks] {
let mut chunks: [Field; PaddedChunks] = [0; PaddedChunks];
for i in 0..PaddedChunks {
let mut limb: Field = 0;
for j in 0..31 {
limb *= 256;
limb += body[i * 31 + j] as Field;
}
chunks[i] = limb;
std::as_witness(chunks[i]);
}
chunks
StringBody { body, chunks: compute_chunks(body), byte_length: length }
}

/**
Expand All @@ -237,12 +226,15 @@ impl<let MaxPaddedBytes: u32, let PaddedChunks: u32, let MaxBytes: u32> StringBo
// use unconstrained function to determine:
// a: is the substring present in the body text
// b: the position of the first match in the body text
let position: u32 = utils::search(
self.body,
substring.get_body(),
self.byte_length,
substring.len()
);
let position: u32 = unsafe {
// Safety: The rest of this function checks this.
utils::search(
self.body,
substring.get_body(),
self.byte_length,
substring.len()
)
};

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

let initial_body_bytes: [u8; 31] = initial_haystack_chunk.to_be_bytes(31).as_array();
let final_body_bytes: [u8; 31] = final_haystack_chunk.to_be_bytes(31).as_array();
let initial_body_bytes: [u8; 31] = initial_haystack_chunk.to_be_bytes();
let final_body_bytes: [u8; 31] = final_haystack_chunk.to_be_bytes();

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

/// Given an input byte array, convert into 31-byte chunks
///
/// Cost: ~0.5 gates per byte
fn compute_chunks<let MaxPaddedBytes: u32, let PaddedChunks: u32>(body: [u8; MaxPaddedBytes]) -> [Field; PaddedChunks] {
let mut chunks: [Field; PaddedChunks] = [0; PaddedChunks];
for i in 0..PaddedChunks {
let mut limb: Field = 0;
for j in 0..31 {
limb *= 256;
limb += body[i * 31 + j] as Field;
}
chunks[i] = limb;
std::as_witness(chunks[i]);
}
chunks
}

#[test]
fn test() {
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();
Expand Down
69 changes: 24 additions & 45 deletions src/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -28,64 +28,43 @@ unconstrained pub fn search<let N: u32>(
found_index
}

/**
* @brief validate the body text contains zero-values for all indices >= byte_length
* @note NOT NEEDED. Consider removing. Values beyond byte_length are not used in matching algorithm so no need to constrain them
**/
fn validate_body<let BODYBYTES: u32, let BODYCHUNKS: u32>(data: [u8; BODYBYTES], length: u32, _: [Field; BODYCHUNKS]) {
// we want a conditional assert for cases where i >= length
// if i >= length we want to assert that data = 0
let mut delta: Field = length as Field;
for i in 0..BODYBYTES {
let predicate = lt_f(i as Field, length as Field);
let predicate = get_lt_predicate_f(i as Field, length as Field);

let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
lt_parameter.assert_max_bit_size(32);
delta = delta - 1;
std::as_witness(delta);

// assert that if predicate = 0 then byte = 0
assert(data[i] as Field * predicate as Field == data[i] as Field);
}
}

unconstrained fn __conditional_select(lhs: u8, rhs: u8, predicate: bool) -> u8 {
let mut result: u8 = 0;
if (predicate) {
result = lhs;
} else {
result = rhs;
}
result
if (predicate) { lhs } else { rhs }
}

pub fn conditional_select<T>(lhs: u8, rhs: u8, predicate: bool) -> u8 {
let result = __conditional_select(lhs, rhs, predicate);
let result_f = result as Field;
let lhs_f = lhs as Field;
let rhs_f = rhs as Field;
// Safety: This is all just a very verbose `if (predicate) { lhs } else { rhs }`
// formulated as `rhs + (lhs - rhs) * predicate`
unsafe {
let result = __conditional_select(lhs, rhs, predicate);
let result_f = result as Field;
let lhs_f = lhs as Field;
let rhs_f = rhs as Field;

let diff = lhs_f - rhs_f;
std::as_witness(diff);
assert((predicate as Field) * (diff) + rhs_f == result_f);
result
let diff = lhs_f - rhs_f;
std::as_witness(diff);
assert_eq((predicate as Field) * diff + rhs_f, result_f);
result
}
}

unconstrained pub fn get_lt_predicate_f(x: Field, y: Field) -> bool {
let a = x as u32;
let b = y as u32;
let r = a < b;
r
a < b
}

pub fn lt_f(x: Field, y: Field) -> bool {
let predicate = get_lt_predicate_f(x, y);
let delta = y as Field - x as Field;
let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
lt_parameter.assert_max_bit_size(32);
// Safety: As `x` and `y` are known to be valid `u32`s, this function reimplements the
// compiler's internal implementation of `lt`
unsafe {
let predicate = get_lt_predicate_f(x, y);
let delta = y as Field - x as Field;
let lt_parameter = 2 * (predicate as Field) * delta - predicate as Field - delta;
lt_parameter.assert_max_bit_size(32);

predicate
predicate
}
}

struct DebugRandomEngine {
Expand All @@ -95,7 +74,7 @@ struct DebugRandomEngine {
impl DebugRandomEngine {
unconstrained fn get_random_32_bytes(&mut self) -> [u8; 32] {
self.seed += 1;
let input: [u8; 32] = self.seed.to_be_bytes(32).as_array();
let input: [u8; 32] = self.seed.to_be_bytes();
let hash: [u8; 32] = dep::std::hash::sha256(input);
hash
}
Expand Down
1 change: 0 additions & 1 deletion target/noir_string_search.json

This file was deleted.

7 changes: 0 additions & 7 deletions target/noir_string_search.txt

This file was deleted.

0 comments on commit a0a7b98

Please sign in to comment.