Skip to content

Commit a8a7ef7

Browse files
authored
Merge branch 'master' into feat/12554-msgpack-tagged
2 parents 43c0b3f + 721ba40 commit a8a7ef7

251 files changed

Lines changed: 7202 additions & 1990 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
set -eu
3+
4+
# Usage: ./extract-fuzz-seeds.sh <fuzz-output.log> [output-dir]
5+
#
6+
# Parses a captured `cargo nextest run -p noir_ast_fuzzer_fuzz` log and writes:
7+
# <output-dir>/seeds.txt — one deduplicated 0x... seed per line
8+
# <output-dir>/seeds-by-test.tsv — "<nextest FAIL line>\t<seed>" pairs
9+
#
10+
# Recognises both the arbtest panic line (e.g. " Seed: 0x6819c61400001000")
11+
# and the explicit repro env var (e.g. "NOIR_AST_FUZZER_SEED=0x6819c61400001000").
12+
13+
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
14+
echo "Usage: $0 <fuzz-output.log> [output-dir]" >&2
15+
exit 2
16+
fi
17+
18+
log_file=$1
19+
out_dir=${2:-.}
20+
21+
if [ ! -f "$log_file" ]; then
22+
echo "error: log file not found: $log_file" >&2
23+
exit 1
24+
fi
25+
26+
mkdir -p "$out_dir"
27+
seeds_file="$out_dir/seeds.txt"
28+
by_test_file="$out_dir/seeds-by-test.tsv"
29+
30+
# nextest emits ANSI color escapes even when stdout is a pipe, so any log
31+
# captured via `tee` contains CSI sequences like \x1b[31;1m wrapping seeds and
32+
# `FAIL` markers. Strip them up-front so the downstream patterns can match.
33+
cleaned_log=$(mktemp)
34+
trap 'rm -f "$cleaned_log"' EXIT
35+
sed -E $'s/\x1b\\[[0-9;?]*[ -/]*[@-~]//g' "$log_file" > "$cleaned_log"
36+
37+
grep -hoE '(Seed:[[:space:]]*0x[0-9a-fA-F]+|NOIR_AST_FUZZER_SEED=0x[0-9a-fA-F]+)' "$cleaned_log" \
38+
| grep -oE '0x[0-9a-fA-F]+' \
39+
| sort -u > "$seeds_file" || true
40+
41+
awk '
42+
/^[[:space:]]*FAIL / { current = $0 }
43+
/Seed:[[:space:]]*0x[0-9a-fA-F]+/ {
44+
match($0, /0x[0-9a-fA-F]+/); seed = substr($0, RSTART, RLENGTH);
45+
print current "\t" seed
46+
}
47+
' "$cleaned_log" > "$by_test_file" || true

.github/workflows/nightly-fuzz-test.yml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,37 @@ jobs:
3333
- uses: taiki-e/install-action@just
3434

3535
- name: Run fuzzer
36-
run: just fuzz-nightly
36+
id: fuzz
37+
shell: bash
38+
run: |
39+
set -o pipefail
40+
just fuzz-nightly 2>&1 | tee fuzz-output.log
3741
env:
3842
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3943
RUST_MIN_STACK: 8388608
4044

45+
- name: Extract fuzzer seeds
46+
if: failure()
47+
id: seeds
48+
shell: bash
49+
run: |
50+
./.github/scripts/extract-fuzz-seeds.sh fuzz-output.log .
51+
seed_count=$(wc -l < seeds.txt | tr -d ' ')
52+
seeds=$(paste -sd, - < seeds.txt)
53+
echo "seed_count=${seed_count}" >> "$GITHUB_OUTPUT"
54+
echo "seeds=${seeds}" >> "$GITHUB_OUTPUT"
55+
56+
- name: Upload fuzz failure artifact
57+
if: failure()
58+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
59+
with:
60+
name: nightly-fuzz-failure
61+
path: |
62+
fuzz-output.log
63+
seeds.txt
64+
seeds-by-test.tsv
65+
retention-days: 30
66+
4167
- name: Get current date
4268
id: date
4369
run: echo "DAY=date::$(date +'%u')" >> $GITHUB_OUTPUT
@@ -51,3 +77,6 @@ jobs:
5177
webhook-type: webhook-trigger
5278
payload: |
5379
workflow-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
80+
seed-count: ${{ steps.seeds.outputs.seed_count }}
81+
seeds: ${{ steps.seeds.outputs.seeds }}
82+
artifact-name: nightly-fuzz-failure

.github/workflows/publish-nightly.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
contents: read
1515
steps:
1616
- name: Dispatch to publish-nargo
17-
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
17+
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1
1818
with:
1919
workflow: publish-nargo.yml
2020
token: ${{ secrets.NOIR_REPO_TOKEN }}
@@ -23,7 +23,7 @@ jobs:
2323

2424

2525
- name: Dispatch to publish-es-packages
26-
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
26+
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1
2727
with:
2828
workflow: publish-es-packages.yml
2929
token: ${{ secrets.NOIR_REPO_TOKEN }}

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ jobs:
211211
contents: read
212212
steps:
213213
- name: Dispatch to publish workflow
214-
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
214+
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1
215215
with:
216216
workflow: publish-nargo.yml
217217
repo: noir-lang/noir
@@ -228,7 +228,7 @@ jobs:
228228
contents: read
229229
steps:
230230
- name: Dispatch to publish-es-packages
231-
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
231+
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1
232232
with:
233233
workflow: publish-es-packages.yml
234234
ref: master
@@ -245,7 +245,7 @@ jobs:
245245

246246
steps:
247247
- name: Dispatch to publish-acvm
248-
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
248+
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1
249249
with:
250250
workflow: publish-acvm.yml
251251
ref: master

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ Special labels to control CI behavior:
235235

236236
- Use when you think that the PR will result in improvements/degradation of compilation time or memory usage.
237237

238+
### GitHub Actions
239+
240+
Third-party actions in `.github/workflows/` are pinned by full commit SHA with the human-readable version in a trailing comment, e.g. `uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7`. When updating an action, rewrite the reference to the new commit SHA rather than a mutable `@vN` tag, and keep the `# vN` comment in sync.
241+
238242
### Workflow Reminders
239243

240244
- ✅ Always compile before testing

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ implicit_clone = "warn"
108108
map_unwrap_or = "warn"
109109
needless_borrows_for_generic_args = "warn"
110110
redundant_field_names = "warn"
111+
while_let_loop = "warn"
112+
for_kv_map = "warn"
113+
iter_kv_map = "warn"
114+
unnecessary_sort_by = "warn"
115+
useless_conversion = "warn"
111116

112117
[workspace.dependencies]
113118

EXTERNAL_NOIR_LIBRARIES.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ libraries:
6767
repo: AztecProtocol/aztec-packages
6868
ref: *AZ_COMMIT
6969
path: noir-projects/aztec-nr
70-
timeout: 260
70+
timeout: 280
7171
critical: false
7272
noir_contracts:
7373
repo: AztecProtocol/aztec-packages
7474
ref: *AZ_COMMIT
7575
path: noir-projects/noir-contracts
76-
timeout: 230
76+
timeout: 250
7777
critical: false
7878
blob:
7979
repo: AztecProtocol/aztec-packages
@@ -85,7 +85,7 @@ libraries:
8585
repo: AztecProtocol/aztec-packages
8686
ref: *AZ_COMMIT
8787
path: noir-projects/noir-protocol-circuits/crates/private-kernel-lib
88-
timeout: 550
88+
timeout: 620
8989
critical: false
9090
protocol_circuits_types:
9191
repo: AztecProtocol/aztec-packages

acvm-repo/acir/src/native_types/expression/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl<F> Expression<F> {
147147
}
148148
}
149149
self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2)));
150-
self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1));
150+
self.linear_combinations.sort_by_key(|a| a.1);
151151
}
152152

153153
#[cfg(test)]

acvm-repo/acir_field/src/field_element.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -319,11 +319,14 @@ impl<F: PrimeField> FieldElement<F> {
319319
///
320320
/// An i128 can represent values in the range [i128::MIN, i128::MAX], which corresponds
321321
/// to field elements in [0, 2^127 - 1] (positive) and [p - 2^127, p - 1] (negative),
322-
/// where p is the field modulus. Note that 2^127 itself cannot be represented as i128
323-
/// since it is not in the negative range.
322+
/// where p is the field modulus. The positive value 2^127 does not fit (it exceeds
323+
/// i128::MAX), but the field element representing -2^127 (i.e. p - 2^127) does fit
324+
/// (it is i128::MIN).
324325
pub fn fits_in_i128(&self) -> bool {
325-
let num_bits = u32::min(self.neg().num_bits(), self.num_bits());
326-
num_bits <= 127 && self != &FieldElement::from(I128_SIGN_BOUNDARY)
326+
let neg = self.neg();
327+
self.num_bits() <= 127
328+
|| neg.num_bits() <= 127
329+
|| self.neg() == FieldElement::from(I128_SIGN_BOUNDARY)
327330
}
328331

329332
/// Returns None, if the string is not a canonical
@@ -445,7 +448,10 @@ impl<F: PrimeField> AcirField for FieldElement<F> {
445448
// We can then differentiate positive from negative values by their MSB.
446449
if self.neg().num_bits() < self.num_bits() {
447450
let bytes = self.neg().to_be_bytes();
448-
i128::from_be_bytes(bytes[16..32].try_into().unwrap()).neg()
451+
// wrapping_neg handles i128::MIN: bytes of 2^127 decode to i128::MIN.
452+
// Because it fits in i128, we know the value is a valid i128 value
453+
// so using wrapping_neg() cannot not silently miss an overflow.
454+
i128::from_be_bytes(bytes[16..32].try_into().unwrap()).wrapping_neg()
449455
} else {
450456
let bytes = self.to_be_bytes();
451457
i128::from_be_bytes(bytes[16..32].try_into().unwrap())
@@ -716,19 +722,18 @@ mod tests {
716722
assert!(F::from(42_i128).fits_in_i128());
717723
assert!(F::from(i128::MAX).fits_in_i128());
718724

719-
// Negative values that fit (except i128::MIN)
725+
// Negative values that fit
720726
assert!(F::from(-1_i128).fits_in_i128());
721727
assert!(F::from(-42_i128).fits_in_i128());
722728
assert!(F::from(i128::MIN + 1).fits_in_i128());
729+
assert!(F::from(i128::MIN).fits_in_i128());
723730

724731
// Boundary: 2^127 - 1 fits (i128::MAX)
725732
assert!(F::from((1_u128 << 127) - 1).fits_in_i128());
726733

727-
// Boundary: 2^127 does NOT fit (exceeds i128::MAX, not negative)
728-
// Note: This also means i128::MIN doesn't fit, as it converts to a field element
729-
// that when interpreted as unsigned equals 2^127
734+
// Boundary: the positive field element 2^127 does NOT fit (exceeds i128::MAX).
735+
// This is distinct from F::from(i128::MIN), which is the field element p - 2^127.
730736
assert!(!F::from(1_u128 << 127).fits_in_i128());
731-
assert!(!F::from(i128::MIN).fits_in_i128());
732737

733738
// Values that don't fit
734739
let too_large = F::from(u128::MAX);
@@ -759,18 +764,26 @@ mod tests {
759764
// Test boundary values
760765
assert_eq!(F::from(-i128::MAX).to_i128(), -i128::MAX);
761766
assert_eq!(F::from(i128::MIN + 1).to_i128(), i128::MIN + 1);
762-
763-
// i128::MIN doesn't fit
767+
assert_eq!(F::from(i128::MIN).to_i128(), i128::MIN);
764768
}
765769

766770
#[test]
767771
fn test_to_i128_roundtrip() {
768772
type F = FieldElement<ark_bn254::Fr>;
769773

770774
// Test roundtrip for various values
771-
// i128::MIN doesn't fit
772-
let test_values =
773-
vec![0_i128, 1, -1, 42, -42, i128::MAX, i128::MAX - 1, i128::MIN + 1, -i128::MAX];
775+
let test_values = vec![
776+
0_i128,
777+
1,
778+
-1,
779+
42,
780+
-42,
781+
i128::MAX,
782+
i128::MAX - 1,
783+
i128::MIN,
784+
i128::MIN + 1,
785+
-i128::MAX,
786+
];
774787

775788
for value in test_values {
776789
let field = F::from(value);
@@ -802,7 +815,6 @@ mod tests {
802815
#[test]
803816
fn test_try_into_i128() {
804817
type F = FieldElement<ark_bn254::Fr>;
805-
806818
// Valid positive conversions
807819
assert_eq!(F::zero().try_into_i128(), Some(0));
808820
assert_eq!(F::from(42_i128).try_into_i128(), Some(42));
@@ -816,15 +828,16 @@ mod tests {
816828
assert_eq!(F::from(i128::MAX - 1).try_into_i128(), Some(i128::MAX - 1));
817829
assert_eq!(F::from(1_i128 << 126).try_into_i128(), Some(1_i128 << 126));
818830
assert_eq!(F::from(-((1_i128 << 126) - 1)).try_into_i128(), Some(-((1_i128 << 126) - 1)));
831+
// i128::MIN (= -2^127) fits: its field representation is p - 2^127, which is
832+
// the same field element as F::from(1_u128 << 127).neg().
833+
assert_eq!(F::from(i128::MIN).try_into_i128(), Some(i128::MIN));
834+
assert_eq!(F::from(1_u128 << 127).neg().try_into_i128(), Some(i128::MIN));
819835
// Invalid conversions
820836
assert_eq!(F::from(1_u128 << 127).try_into_i128(), None);
821837
assert_eq!(F::from(u128::MAX).try_into_i128(), None);
822-
// i128::MIN doesn't fit due to implementation
823-
assert_eq!(F::from(i128::MIN).try_into_i128(), None);
824838
// A few other invalid values
825839
assert_eq!(F::from((1_u128 << 127) + 1).try_into_i128(), None);
826840
assert_eq!(F::from((1_u128 << 127) + 1000).try_into_i128(), None);
827-
assert_eq!(F::from(1_u128 << 127).neg().try_into_i128(), None);
828841
assert_eq!(F::from((1_u128 << 127) + 1).neg().try_into_i128(), None);
829842
assert_eq!(F::from((1_u128 << 127) + 100).try_into_i128(), None);
830843
assert_eq!(F::from((1_u128 << 127) + 100).neg().try_into_i128(), None);

0 commit comments

Comments
 (0)