Skip to content

Commit

Permalink
Merge branch 'master' into jcf/reorganise_compiler
Browse files Browse the repository at this point in the history
* master:
  feat: Update Arkworks' dependencies on `acir_field` (noir-lang#69)
  add tests for `from_hex` and `to_hex` (noir-lang#85)
  improve field formatting (noir-lang#84)
  chore(ci): Leverage reusable workflows (noir-lang#81)
  fix clippy (noir-lang#82)
  0.4.1 Release
  remove duplicated match statement for Directive::Log (noir-lang#79)
  0.4.0 Release
  update changelog
  feat(acvm): Directive for sorting networks (noir-lang#77)
  feat: Preprocess methods for ACVM interface  (noir-lang#71)
  expose acir_field through acir (noir-lang#75)
  chore(acvm): rename remaining references to gadget calls (noir-lang#76)
  feat: Add Log Directive (noir-lang#61)
  blackbox to black_box (noir-lang#72)
  feat(ci)!: add spellchecker (noir-lang#73)
  • Loading branch information
TomAFrench committed Feb 10, 2023
2 parents 8d34dcb + 65d6130 commit a3a04db
Show file tree
Hide file tree
Showing 31 changed files with 979 additions and 203 deletions.
37 changes: 20 additions & 17 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
name: Rust

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always
on: [push, pull_request]

jobs:
build:
check_n_test:
name: cargo check & test
uses: noir-lang/.github/.github/workflows/rust-test.yml@main

runs-on: ubuntu-latest
clippy:
name: cargo clippy
uses: noir-lang/.github/.github/workflows/rust-clippy.yml@main

format:
name: cargo fmt
uses: noir-lang/.github/.github/workflows/rust-format.yml@main

spellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Clippy
run: cargo clippy --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v3
- uses: streetsidesoftware/cspell-action@v2
with:
files: |
**/*.{md,rs}
incremental_files_only: true # Run this action on files which have changed in PR
strict: false # Do not fail, if a spelling mistake is found (This can be annoying for contributors)
23 changes: 21 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,33 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.4.1] - 2023-02-08

### Added

### Fixed

- Removed duplicated logic in match branch

### Changed

### Removed

## [0.4.0] - 2023-02-08

### Added

- Add log directive
- Expose `acir_field` through `acir` crate
- Add permutation directive
- Add preprocess methods to ACVM interface

### Fixed

### Changed

- Changed spellings of many functions to be correct using spellchecker

### Removed

## [0.3.1] - 2023-01-18
Expand Down Expand Up @@ -42,7 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- XOR, Range and AND gates are no longer special case. They are now another opcode in the GadgetCall
- Move fallback module to `stdlib`
- optimiser code and any other passes will live in acvm. acir is solely for defining the IR now.
- Optimizer code and any other passes will live in acvm. acir is solely for defining the IR now.
- ACIR passes now live under the compiler parent module
- Moved opcode module in acir crate to circuit/opcode
- Rename GadgetCall to BlackBoxFuncCall
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ACIR - Abstract Circuit Intermediate Representation

ACIR is an NP complete language that generalises R1CS and arithmetic circuits while not losing proving system specific optimisations through the use of black box functions.
ACIR is an NP complete language that generalizes R1CS and arithmetic circuits while not losing proving system specific optimizations through the use of black box functions.

# ACVM - Abstract Circuit Virtual Machine

This can be seen as the ACIR compiler. It will take an ACIR instance and convert it to the format required
by a particular proving system to create a proof.
by a particular proving system to create a proof.
8 changes: 6 additions & 2 deletions acir/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "acir"
version = "0.3.1"
version = "0.4.1"
authors = ["Kevaundray Wedderburn <kevtheappdev@gmail.com>"]
edition = "2021"
license = "MIT"
Expand All @@ -9,7 +9,7 @@ description = "ACIR is the IR that the VM processes, it is analogous to LLVM IR"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
acir_field = { version = "0.3.1", path = "../acir_field" }
acir_field = { version = "0.4.1", path = "../acir_field" }
serde = { version = "1.0.136", features = ["derive"] }
rmp-serde = "1.1.0"
flate2 = "1.0.24"
Expand All @@ -18,3 +18,7 @@ flate2 = "1.0.24"
serde_json = "1.0"
strum = "0.24"
strum_macros = "0.24"

[features]
bn254 = ["acir_field/bn254"]
bls12_381 = ["acir_field/bls12_381"]
File renamed without changes.
90 changes: 88 additions & 2 deletions acir/src/circuit/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::{Read, Write};

use crate::{
native_types::{Expression, Witness},
serialisation::{read_n, read_u16, read_u32, write_bytes, write_u16, write_u32},
serialization::{read_n, read_u16, read_u32, write_bytes, write_u16, write_u32},
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -48,6 +48,16 @@ pub enum Directive {
b: Vec<Witness>,
radix: u32,
},

// Sort directive, using a sorting network
// This directive is used to generate the values of the control bits for the sorting network such that its outputs are properly sorted according to sort_by
PermutationSort {
inputs: Vec<Vec<Expression>>, // Array of tuples to sort
tuple: u32, // tuple size; if 1 then inputs is a single array [a0,a1,..], if 2 then inputs=[(a0,b0),..] is [a0,b0,a1,b1,..], etc..
bits: Vec<Witness>, // control bits of the network which permutes the inputs into its sorted version
sort_by: Vec<u32>, // specify primary index to sort by, then the secondary,... For instance, if tuple is 2 and sort_by is [1,0], then a=[(a0,b0),..] is sorted by bi and then ai.
},
Log(LogInfo),
}

impl Directive {
Expand All @@ -58,6 +68,8 @@ impl Directive {
Directive::Truncate { .. } => "truncate",
Directive::OddRange { .. } => "odd_range",
Directive::ToRadix { .. } => "to_radix",
Directive::PermutationSort { .. } => "permutation_sort",
Directive::Log { .. } => "log",
}
}
fn to_u16(&self) -> u16 {
Expand All @@ -67,6 +79,8 @@ impl Directive {
Directive::Truncate { .. } => 2,
Directive::OddRange { .. } => 3,
Directive::ToRadix { .. } => 4,
Directive::Log { .. } => 5,
Directive::PermutationSort { .. } => 6,
}
}

Expand Down Expand Up @@ -116,6 +130,39 @@ impl Directive {
}
write_u32(&mut writer, *radix)?;
}
Directive::PermutationSort {
inputs: a,
tuple,
bits,
sort_by,
} => {
write_u32(&mut writer, *tuple)?;
write_u32(&mut writer, a.len() as u32)?;
for e in a {
for i in 0..*tuple {
e[i as usize].write(&mut writer)?;
}
}
write_u32(&mut writer, bits.len() as u32)?;
for b in bits {
write_u32(&mut writer, b.witness_index())?;
}
write_u32(&mut writer, sort_by.len() as u32)?;
for i in sort_by {
write_u32(&mut writer, *i)?;
}
}
Directive::Log(info) => match info {
LogInfo::FinalizedOutput(output_string) => {
write_bytes(&mut writer, output_string.as_bytes())?;
}
LogInfo::WitnessOutput(witnesses) => {
write_u32(&mut writer, witnesses.len() as u32)?;
for w in witnesses {
write_u32(&mut writer, w.witness_index())?;
}
}
},
};

Ok(())
Expand Down Expand Up @@ -178,14 +225,53 @@ impl Directive {

Ok(Directive::ToRadix { a, b, radix })
}
6 => {
let tuple = read_u32(&mut reader)?;
let a_len = read_u32(&mut reader)?;
let mut a = Vec::with_capacity(a_len as usize);
for _ in 0..a_len {
let mut element = Vec::new();
for _ in 0..tuple {
element.push(Expression::read(&mut reader)?);
}
a.push(element);
}

let bits_len = read_u32(&mut reader)?;
let mut bits = Vec::with_capacity(bits_len as usize);
for _ in 0..bits_len {
bits.push(Witness(read_u32(&mut reader)?));
}
let sort_by_len = read_u32(&mut reader)?;
let mut sort_by = Vec::with_capacity(sort_by_len as usize);
for _ in 0..sort_by_len {
sort_by.push(read_u32(&mut reader)?);
}
Ok(Directive::PermutationSort {
inputs: a,
tuple,
bits,
sort_by,
})
}

_ => Err(std::io::ErrorKind::InvalidData.into()),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
// If values are compile time and/or known during
// evaluation, we can form an output string during ACIR generation.
// Otherwise, we must store witnesses whose values will
// be fetched during the PWG stage.
pub enum LogInfo {
FinalizedOutput(String),
WitnessOutput(Vec<Witness>),
}

#[test]
fn serialisation_roundtrip() {
fn serialization_roundtrip() {
fn read_write(directive: Directive) -> (Directive, Directive) {
let mut bytes = Vec::new();
directive.write(&mut bytes).unwrap();
Expand Down
12 changes: 6 additions & 6 deletions acir/src/circuit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub mod blackbox_functions;
pub mod black_box_functions;
pub mod directives;
pub mod opcodes;
pub use opcodes::Opcode;

use crate::native_types::Witness;
use crate::serialisation::{read_u32, write_u32};
use crate::serialization::{read_u32, write_u32};
use rmp_serde;
use serde::{Deserialize, Serialize};

Expand All @@ -29,7 +29,7 @@ impl Circuit {
}

#[deprecated(
note = "we want to use a serialisation strategy that is easy to implement in many languages (without ffi). use `read` instead"
note = "we want to use a serialization strategy that is easy to implement in many languages (without ffi). use `read` instead"
)]
pub fn from_bytes(bytes: &[u8]) -> Circuit {
let mut deflater = DeflateDecoder::new(bytes);
Expand All @@ -39,7 +39,7 @@ impl Circuit {
}

#[deprecated(
note = "we want to use a serialisation strategy that is easy to implement in many languages (without ffi).use `write` instead"
note = "we want to use a serialization strategy that is easy to implement in many languages (without ffi).use `write` instead"
)]
pub fn to_bytes(&self) -> Vec<u8> {
let buf = rmp_serde::to_vec(&self).unwrap();
Expand Down Expand Up @@ -71,7 +71,7 @@ impl Circuit {
// TODO (Note): we could use semver versioning from the Cargo.toml
// here and then reject anything that has a major bump
//
// We may also not want to do that if we do not want to couple serialisation
// We may also not want to do that if we do not want to couple serialization
// with other breaking changes
if version_number != VERSION_NUMBER {
return Err(std::io::ErrorKind::InvalidData.into());
Expand Down Expand Up @@ -180,7 +180,7 @@ mod test {
}

#[test]
fn serialisation_roundtrip() {
fn serialization_roundtrip() {
let circuit = Circuit {
current_witness_index: 5,
opcodes: vec![and_opcode(), range_opcode()],
Expand Down
39 changes: 33 additions & 6 deletions acir/src/circuit/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::io::{Read, Write};

use super::directives::Directive;
use super::directives::{Directive, LogInfo};
use crate::native_types::{Expression, Witness};
use crate::serialisation::{read_n, read_u16, read_u32, write_bytes, write_u16, write_u32};
use crate::serialization::{read_n, read_u16, read_u32, write_bytes, write_u16, write_u32};
use crate::BlackBoxFunc;
use serde::{Deserialize, Serialize};

Expand All @@ -25,7 +25,7 @@ impl Opcode {
}
// We have three types of opcodes allowed in the IR
// Expression, BlackBoxFuncCall and Directives
// When we serialise these opcodes, we use the index
// When we serialize these opcodes, we use the index
// to uniquely identify which category of opcode we are dealing with.
pub(crate) fn to_index(&self) -> u8 {
match self {
Expand Down Expand Up @@ -164,6 +164,33 @@ impl std::fmt::Display for Opcode {
b.last().unwrap().witness_index(),
)
}
Opcode::Directive(Directive::PermutationSort {
inputs: a,
tuple,
bits,
sort_by,
}) => {
write!(f, "DIR::PERMUTATIONSORT ")?;
write!(
f,
"(permutation size: {} {}-tuples, sort_by: {:#?}, bits: [_{}..._{}]))",
a.len(),
tuple,
sort_by,
// (Note): the bits do not have contiguous index but there are too many for display
bits.first().unwrap().witness_index(),
bits.last().unwrap().witness_index(),
)
}
Opcode::Directive(Directive::Log(info)) => match info {
LogInfo::FinalizedOutput(output_string) => write!(f, "Log: {output_string}"),
LogInfo::WitnessOutput(witnesses) => write!(
f,
"Log: _{}..._{}",
witnesses.first().unwrap().witness_index(),
witnesses.last().unwrap().witness_index()
),
},
}
}
}
Expand Down Expand Up @@ -326,7 +353,7 @@ impl std::fmt::Debug for BlackBoxFuncCall {
}

#[test]
fn serialisation_roundtrip() {
fn serialization_roundtrip() {
fn read_write(opcode: Opcode) -> (Opcode, Opcode) {
let mut bytes = Vec::new();
opcode.write(&mut bytes).unwrap();
Expand All @@ -336,7 +363,7 @@ fn serialisation_roundtrip() {

let opcode_arith = Opcode::Arithmetic(Expression::default());

let opcode_blackbox_func = Opcode::BlackBoxFuncCall(BlackBoxFuncCall {
let opcode_black_box_func = Opcode::BlackBoxFuncCall(BlackBoxFuncCall {
name: BlackBoxFunc::AES,
inputs: vec![
FunctionInput {
Expand All @@ -356,7 +383,7 @@ fn serialisation_roundtrip() {
result: Witness(56789u32),
});

let opcodes = vec![opcode_arith, opcode_blackbox_func, opcode_directive];
let opcodes = vec![opcode_arith, opcode_black_box_func, opcode_directive];

for opcode in opcodes {
let (op, got_op) = read_write(opcode);
Expand Down
Loading

0 comments on commit a3a04db

Please sign in to comment.