diff --git a/.gitignore b/.gitignore index f2e972d..b5cf87c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ # These are backup files generated by rustfmt **/*.rs.bk + +# nargo (integration tests) +tests/test_programs/*/target +tests/test_programs/*/proofs diff --git a/Cargo.lock b/Cargo.lock index 1685955..9414607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,9 @@ version = 3 [[package]] name = "acir" -version = "0.4.1" -source = "git+https://github.com/noir-lang/acvm#373c18fc05edf673cfec9e8bbb78bd7d7514999e" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104242ac56c936464e3ac9618e1f635151e83334d8d4138f9a39490ca11acaa9" dependencies = [ "acir_field", "flate2", @@ -15,8 +16,9 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.4.1" -source = "git+https://github.com/noir-lang/acvm#373c18fc05edf673cfec9e8bbb78bd7d7514999e" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd6cc7ebc948cde55bf10a8c4f3e1479e03f34e00ee8ab83e86891b693dd503" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -32,8 +34,9 @@ dependencies = [ [[package]] name = "acvm" -version = "0.4.1" -source = "git+https://github.com/noir-lang/acvm#373c18fc05edf673cfec9e8bbb78bd7d7514999e" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6145438d6c89c208cae93e49b925b0e2ac1963048a8ee4e002ca0f0de87d789e" dependencies = [ "acir", "acvm_stdlib", @@ -48,8 +51,9 @@ dependencies = [ [[package]] name = "acvm_stdlib" -version = "0.4.1" -source = "git+https://github.com/noir-lang/acvm#373c18fc05edf673cfec9e8bbb78bd7d7514999e" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaabcf52a534df7ed591451cf5ac2a765416fee4e38e0c523cc29da8c244aa7c" dependencies = [ "acir", ] @@ -947,6 +951,7 @@ dependencies = [ "cfg-if", "hex", "hex-literal", + "rand", "serde", "serde_json", "thiserror", @@ -1115,6 +1120,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core 0.6.4", ] @@ -1140,6 +1146,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rayon" diff --git a/Cargo.toml b/Cargo.toml index 16f1672..e10769b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -acvm = { git = "https://github.com/noir-lang/acvm" } +# acvm = { git = "https://github.com/noir-lang/acvm" } +acvm = "0.5" ark-ff = "0.4.0" ark-bls12-381 = "0.4.0" @@ -19,6 +20,7 @@ cfg-if = "1" hex = "0.4" hex-literal = "0.3.4" thiserror = "1.0" +rand = "0.8" [profile.test] opt-level = 3 @@ -30,5 +32,6 @@ debug-assertions = true [features] default = ["bn254", "groth16"] groth16 = [] +plonk = [] bn254 = ["acvm/bn254"] bls12_381 = ["acvm/bls12_381"] diff --git a/Makefile b/Makefile index 50a1f03..86acd22 100644 --- a/Makefile +++ b/Makefile @@ -9,14 +9,18 @@ build-go: # Temporary solution for testing the only tests we have. We should test recurively. test-go: $ cd ${FFI_LIB_PATH}; \ - go test -run '' gnark_backend_ffi/structs + go test -run '' gnark_backend_ffi/backend/groth16; \ + go test -run '' gnark_backend_ffi/backend/plonk build: build-go $ RUSTFLAGS="-L${FFI_LIB_PATH}" cargo build test: build-go - $ RUSTFLAGS="-L${FFI_LIB_PATH}" cargo test + $ RUSTFLAGS="-L${FFI_LIB_PATH}" cargo test ${TEST} -- --nocapture clippy: $ cargo clippy --all-targets -- -D warnings +nargo: + $ cargo install --force --locked --git https://github.com/lambdaclass/noir --branch fork nargo + diff --git a/build.rs b/build.rs index 6c44e85..30d45a0 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,22 @@ -#[allow(clippy::all)] +fn build_go_backend() { + std::process::Command::new("make") + .arg("-C") + .arg( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .to_str() + .unwrap(), + ) + .arg("build-go") + .spawn() + .unwrap(); +} + fn main() { + build_go_backend(); + let path = "gnark_backend_ffi"; let lib = "gnark_backend"; - println!("cargo:rustc-link-search=native={}", path); - println!("cargo:rustc-link-lib=static={}", lib); + println!("cargo:rustc-link-search=native={path}"); + println!("cargo:rustc-link-lib=static={lib}"); } diff --git a/gnark_backend_ffi/structs/add_term.go b/gnark_backend_ffi/backend/add_term.go similarity index 98% rename from gnark_backend_ffi/structs/add_term.go rename to gnark_backend_ffi/backend/add_term.go index e9a98e0..439fe83 100644 --- a/gnark_backend_ffi/structs/add_term.go +++ b/gnark_backend_ffi/backend/add_term.go @@ -1,4 +1,4 @@ -package structs +package backend import ( "encoding/json" diff --git a/gnark_backend_ffi/structs/add_term_test.go b/gnark_backend_ffi/backend/add_term_test.go similarity index 98% rename from gnark_backend_ffi/structs/add_term_test.go rename to gnark_backend_ffi/backend/add_term_test.go index 38285f0..ecc79ac 100644 --- a/gnark_backend_ffi/structs/add_term_test.go +++ b/gnark_backend_ffi/backend/add_term_test.go @@ -1,4 +1,4 @@ -package structs +package backend import ( "encoding/json" diff --git a/gnark_backend_ffi/backend/groth16/helpers.go b/gnark_backend_ffi/backend/groth16/helpers.go new file mode 100644 index 0000000..7d72a2a --- /dev/null +++ b/gnark_backend_ffi/backend/groth16/helpers.go @@ -0,0 +1,36 @@ +package groth16 + +import ( + "encoding/json" + "log" +) + +func UncheckedDeserializeRawGate(rawGate string) RawGate { + var r RawGate + err := json.Unmarshal([]byte(rawGate), &r) + if err != nil { + log.Fatal(err) + } + + return r +} + +func UncheckedDeserializeRawGates(rawGates string) []RawGate { + var r []RawGate + err := json.Unmarshal([]byte(rawGates), &r) + if err != nil { + log.Fatal(err) + } + + return r +} + +func UncheckedDeserializeRawR1CS(rawR1CS string) RawR1CS { + var r RawR1CS + err := json.Unmarshal([]byte(rawR1CS), &r) + if err != nil { + log.Fatal(err) + } + + return r +} diff --git a/gnark_backend_ffi/structs/raw_gate.go b/gnark_backend_ffi/backend/groth16/raw_gate.go similarity index 86% rename from gnark_backend_ffi/structs/raw_gate.go rename to gnark_backend_ffi/backend/groth16/raw_gate.go index d70301c..34d561c 100644 --- a/gnark_backend_ffi/structs/raw_gate.go +++ b/gnark_backend_ffi/backend/groth16/raw_gate.go @@ -1,15 +1,16 @@ -package structs +package groth16 import ( "encoding/json" + "gnark_backend_ffi/backend" "log" fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" ) type RawGate struct { - MulTerms []MulTerm - AddTerms []AddTerm + MulTerms []backend.MulTerm + AddTerms []backend.AddTerm ConstantTerm fr_bn254.Element } @@ -21,8 +22,8 @@ func (g *RawGate) UnmarshalJSON(data []byte) error { return err } - var mulTerms []MulTerm - var addTerms []AddTerm + var mulTerms []backend.MulTerm + var addTerms []backend.AddTerm var constantTerm fr_bn254.Element // Deserialize mul terms. @@ -61,7 +62,7 @@ func (g *RawGate) UnmarshalJSON(data []byte) error { // Deserialize constant term. if encodedConstantTerm, ok := rawGateMap["constant_term"].(string); ok { - constantTerm = DeserializeFelt(encodedConstantTerm) + constantTerm = backend.DeserializeFelt(encodedConstantTerm) } else { log.Fatal("Error: coefficient is not a felt.") return &json.UnmarshalTypeError{} diff --git a/gnark_backend_ffi/structs/raw_gate_test.go b/gnark_backend_ffi/backend/groth16/raw_gate_test.go similarity index 74% rename from gnark_backend_ffi/structs/raw_gate_test.go rename to gnark_backend_ffi/backend/groth16/raw_gate_test.go index a95587e..34233fb 100644 --- a/gnark_backend_ffi/structs/raw_gate_test.go +++ b/gnark_backend_ffi/backend/groth16/raw_gate_test.go @@ -1,8 +1,9 @@ -package structs +package groth16 import ( "encoding/json" "fmt" + "gnark_backend_ffi/backend" "log" "math/rand" "testing" @@ -13,13 +14,13 @@ import ( // TODO: Test error cases. func TestRawGateTermUnmarshalJSON(t *testing.T) { - encodedCoefficient, _ := SampleEncodedFelt() + encodedCoefficient, _ := backend.SampleEncodedFelt() multiplicand := rand.Uint32() multiplier := rand.Uint32() sum := rand.Uint32() mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) - encodedConstantTerm, nonEncodedConstantTerm := SampleEncodedFelt() + encodedConstantTerm, nonEncodedConstantTerm := backend.SampleEncodedFelt() rawGate := fmt.Sprintf(`{"mul_terms":%s,"add_terms":%s,"constant_term":"%s"}`, mulTerms, addTerms, encodedConstantTerm) var r RawGate @@ -29,19 +30,19 @@ func TestRawGateTermUnmarshalJSON(t *testing.T) { } assert.NoError(t, err) - assert.Equal(t, UncheckedDeserializeMulTerms(mulTerms), r.MulTerms) - assert.Equal(t, UncheckedDeserializeAddTerms(addTerms), r.AddTerms) + assert.Equal(t, backend.UncheckedDeserializeMulTerms(mulTerms), r.MulTerms) + assert.Equal(t, backend.UncheckedDeserializeAddTerms(addTerms), r.AddTerms) assert.Equal(t, nonEncodedConstantTerm, r.ConstantTerm) } func TestRawGatesTermUnmarshalJSON(t *testing.T) { - encodedCoefficient, _ := SampleEncodedFelt() + encodedCoefficient, _ := backend.SampleEncodedFelt() multiplicand := rand.Uint32() multiplier := rand.Uint32() sum := rand.Uint32() mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) - encodedConstantTerm, nonEncodedConstantTerm := SampleEncodedFelt() + encodedConstantTerm, nonEncodedConstantTerm := backend.SampleEncodedFelt() rawGate := fmt.Sprintf(`{"mul_terms":%s,"add_terms":%s,"constant_term":"%s"}`, mulTerms, addTerms, encodedConstantTerm) rawGates := fmt.Sprintf(`[%s,%s]`, rawGate, rawGate) @@ -53,8 +54,8 @@ func TestRawGatesTermUnmarshalJSON(t *testing.T) { assert.NoError(t, err) for _, rawGate := range r { - assert.Equal(t, UncheckedDeserializeMulTerms(mulTerms), rawGate.MulTerms) - assert.Equal(t, UncheckedDeserializeAddTerms(addTerms), rawGate.AddTerms) + assert.Equal(t, backend.UncheckedDeserializeMulTerms(mulTerms), rawGate.MulTerms) + assert.Equal(t, backend.UncheckedDeserializeAddTerms(addTerms), rawGate.AddTerms) assert.Equal(t, nonEncodedConstantTerm, rawGate.ConstantTerm) } } diff --git a/gnark_backend_ffi/structs/raw_r1cs.go b/gnark_backend_ffi/backend/groth16/raw_r1cs.go similarity index 92% rename from gnark_backend_ffi/structs/raw_r1cs.go rename to gnark_backend_ffi/backend/groth16/raw_r1cs.go index c648b58..f368387 100644 --- a/gnark_backend_ffi/structs/raw_r1cs.go +++ b/gnark_backend_ffi/backend/groth16/raw_r1cs.go @@ -1,8 +1,8 @@ -package structs +package groth16 import ( "encoding/json" - "fmt" + "gnark_backend_ffi/backend" "log" fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" @@ -10,7 +10,7 @@ import ( type RawR1CS struct { Gates []RawGate - PublicInputs Witnesses + PublicInputs backend.Witnesses Values fr_bn254.Vector NumVariables uint64 NumConstraints uint64 @@ -25,7 +25,7 @@ func (r *RawR1CS) UnmarshalJSON(data []byte) error { } var gates []RawGate - var publicInputs Witnesses + var publicInputs backend.Witnesses var values fr_bn254.Vector var numVariables uint64 var numConstraints uint64 @@ -65,7 +65,7 @@ func (r *RawR1CS) UnmarshalJSON(data []byte) error { // Deserialize values. if encodedValues, ok := rawR1CSMap["values"].(string); ok { - values = DeserializeFelts(encodedValues) + values = backend.DeserializeFelts(encodedValues) } else { log.Fatal("Error: couldn't deserialize values.") return &json.UnmarshalTypeError{} @@ -80,7 +80,6 @@ func (r *RawR1CS) UnmarshalJSON(data []byte) error { } // Deserialize num_constraints. - fmt.Println("", rawR1CSMap["num_constraints"]) if numConstraintsValue, ok := rawR1CSMap["num_constraints"].(float64); ok { numConstraints = uint64(numConstraintsValue) } else { diff --git a/gnark_backend_ffi/structs/raw_r1cs_test.go b/gnark_backend_ffi/backend/groth16/raw_r1cs_test.go similarity index 82% rename from gnark_backend_ffi/structs/raw_r1cs_test.go rename to gnark_backend_ffi/backend/groth16/raw_r1cs_test.go index 30228d7..b2ca76d 100644 --- a/gnark_backend_ffi/structs/raw_r1cs_test.go +++ b/gnark_backend_ffi/backend/groth16/raw_r1cs_test.go @@ -1,8 +1,9 @@ -package structs +package groth16 import ( "encoding/json" "fmt" + "gnark_backend_ffi/backend" "math/rand" "testing" @@ -12,17 +13,17 @@ import ( // TODO: Test error cases. func TestRawR1CSTermUnmarshalJSON(t *testing.T) { - encodedCoefficient, _ := SampleEncodedFelt() + encodedCoefficient, _ := backend.SampleEncodedFelt() multiplicand := rand.Uint32() multiplier := rand.Uint32() sum := rand.Uint32() mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) - encodedConstantTerm, _ := SampleEncodedFelt() + encodedConstantTerm, _ := backend.SampleEncodedFelt() rawGate := fmt.Sprintf(`{"mul_terms":%s,"add_terms":%s,"constant_term":"%s"}`, mulTerms, addTerms, encodedConstantTerm) rawGates := fmt.Sprintf(`[%s,%s]`, rawGate, rawGate) publicInputs := fmt.Sprintf("[%d,%d,%d]", multiplicand, multiplier, sum) - encodedValues, nonEncodedValues := SampleEncodedFelts() + encodedValues, nonEncodedValues := backend.SampleEncodedFelts() numVariables := uint64(10) numConstraints := uint64(10) rawR1CS := fmt.Sprintf(`{"gates":%s,"public_inputs":%s,"values":"%s","num_variables":%d,"num_constraints":%d}`, rawGates, publicInputs, encodedValues, numVariables, numConstraints) @@ -32,7 +33,7 @@ func TestRawR1CSTermUnmarshalJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, UncheckedDeserializeRawGates(rawGates), r.Gates) - assert.Equal(t, Witnesses{multiplicand, multiplier, sum}, r.PublicInputs) + assert.Equal(t, backend.Witnesses{multiplicand, multiplier, sum}, r.PublicInputs) assert.Equal(t, nonEncodedValues, r.Values) assert.Equal(t, numConstraints, r.NumConstraints) assert.Equal(t, numVariables, r.NumVariables) diff --git a/gnark_backend_ffi/structs/helpers.go b/gnark_backend_ffi/backend/helpers.go similarity index 79% rename from gnark_backend_ffi/structs/helpers.go rename to gnark_backend_ffi/backend/helpers.go index dc760dd..f6de906 100644 --- a/gnark_backend_ffi/structs/helpers.go +++ b/gnark_backend_ffi/backend/helpers.go @@ -1,4 +1,4 @@ -package structs +package backend import ( "encoding/hex" @@ -79,36 +79,6 @@ func UncheckedDeserializeMulTerms(mulTerms string) []MulTerm { return m } -func UncheckedDeserializeRawGate(rawGate string) RawGate { - var r RawGate - err := json.Unmarshal([]byte(rawGate), &r) - if err != nil { - log.Fatal(err) - } - - return r -} - -func UncheckedDeserializeRawGates(rawGates string) []RawGate { - var r []RawGate - err := json.Unmarshal([]byte(rawGates), &r) - if err != nil { - log.Fatal(err) - } - - return r -} - -func UncheckedDeserializeRawR1CS(rawR1CS string) RawR1CS { - var r RawR1CS - err := json.Unmarshal([]byte(rawR1CS), &r) - if err != nil { - log.Fatal(err) - } - - return r -} - // Samples a felt and returns the encoded felt and the non-encoded felt. func SampleEncodedFelt() (string, fr_bn254.Element) { var felt fr_bn254.Element diff --git a/gnark_backend_ffi/structs/mul_term.go b/gnark_backend_ffi/backend/mul_term.go similarity index 98% rename from gnark_backend_ffi/structs/mul_term.go rename to gnark_backend_ffi/backend/mul_term.go index 4e065d6..c2f6c78 100644 --- a/gnark_backend_ffi/structs/mul_term.go +++ b/gnark_backend_ffi/backend/mul_term.go @@ -1,4 +1,4 @@ -package structs +package backend import ( "encoding/json" diff --git a/gnark_backend_ffi/structs/mul_term_test.go b/gnark_backend_ffi/backend/mul_term_test.go similarity index 98% rename from gnark_backend_ffi/structs/mul_term_test.go rename to gnark_backend_ffi/backend/mul_term_test.go index 942ccfb..51ca2dd 100644 --- a/gnark_backend_ffi/structs/mul_term_test.go +++ b/gnark_backend_ffi/backend/mul_term_test.go @@ -1,4 +1,4 @@ -package structs +package backend import ( "encoding/json" diff --git a/gnark_backend_ffi/backend/plonk/acir.go b/gnark_backend_ffi/backend/plonk/acir.go new file mode 100644 index 0000000..895839a --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/acir.go @@ -0,0 +1,71 @@ +package groth16 + +import ( + "encoding/json" + "gnark_backend_ffi/backend" + "log" +) + +type ACIR struct { + CurrentWitness backend.Witness + Opcodes []Opcode + PublicInputs backend.Witnesses +} + +func (a *ACIR) UnmarshalJSON(data []byte) error { + var acirMap map[string]interface{} + err := json.Unmarshal(data, &acirMap) + if err != nil { + log.Print(err) + return err + } + + var opcodes []Opcode + var publicInputs backend.Witnesses + var currentWitness uint32 + + if opcodesValue, ok := acirMap["opcodes"]; ok { + opcodesJSON, err := json.Marshal(opcodesValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(opcodesJSON, &opcodes) + if err != nil { + log.Print(err) + return err + } + } else { + log.Print("Error: couldn't deserialize opcodes.") + return &json.UnmarshalTypeError{} + } + + if publicInputsValue, ok := acirMap["public_inputs"].([]interface{}); ok { + publicInputsJSON, err := json.Marshal(publicInputsValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(publicInputsJSON, &publicInputs) + if err != nil { + log.Print(err) + return err + } + } else { + log.Print("Error: couldn't deserialize public inputs.") + return &json.UnmarshalTypeError{} + } + + if currentWitnessValue, ok := acirMap["current_witness_index"].(float64); ok { + currentWitness = uint32(currentWitnessValue) + } else { + log.Print("Error: couldn't deserialize current witness.") + return &json.UnmarshalTypeError{} + } + + a.CurrentWitness = currentWitness + a.Opcodes = opcodes + a.PublicInputs = publicInputs + + return nil +} diff --git a/gnark_backend_ffi/backend/plonk/acir_test.go b/gnark_backend_ffi/backend/plonk/acir_test.go new file mode 100644 index 0000000..30f4cd3 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/acir_test.go @@ -0,0 +1,38 @@ +package groth16 + +import ( + "encoding/json" + "fmt" + "gnark_backend_ffi/backend" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TODO: Test error cases. + +func TestACIRUnmarshalJSON(t *testing.T) { + encodedCoefficient, _ := backend.SampleEncodedFelt() + multiplicand := rand.Uint32() + multiplier := rand.Uint32() + sum := rand.Uint32() + mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) + addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) + encodedConstantTerm, _ := backend.SampleEncodedFelt() + arithmetic_opcode := fmt.Sprintf(`{"Arithmetic": {"mul_terms":%s,"linear_combinations":%s,"q_c":"%s"}}`, mulTerms, addTerms, encodedConstantTerm) + x := rand.Uint32() + result := rand.Uint32() + invertDirective := fmt.Sprintf(`{"Invert": {"x":%d,"result":%d}}`, x, result) + opcodes := fmt.Sprintf(`[%s,%s]`, arithmetic_opcode, invertDirective) + publicInputs := fmt.Sprintf("[%d,%d,%d]", multiplicand, multiplier, sum) + currentWitness := uint32(1) + acirJson := fmt.Sprintf(`{"current_witness_index": %d, "opcodes": %s, "public_inputs": %s}`, currentWitness, opcodes, publicInputs) + + var a ACIR + err := json.Unmarshal([]byte(acirJson), &a) + assert.NoError(t, err) + assert.Equal(t, currentWitness, a.CurrentWitness) + assert.Equal(t, UncheckedDeserializeOpcodes(opcodes), a.Opcodes) + assert.Equal(t, backend.Witnesses{multiplicand, multiplier, sum}, a.PublicInputs) +} diff --git a/gnark_backend_ffi/backend/plonk/arithmetic_opcode.go b/gnark_backend_ffi/backend/plonk/arithmetic_opcode.go new file mode 100644 index 0000000..6ea2728 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/arithmetic_opcode.go @@ -0,0 +1,93 @@ +package groth16 + +import ( + "encoding/json" + "gnark_backend_ffi/backend" + "log" + + fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +type ArithmeticOpcode struct { + MulTerms []backend.MulTerm + AddTerms []backend.AddTerm + QC fr_bn254.Element +} + +func (g *ArithmeticOpcode) UnmarshalJSON(data []byte) error { + var opcodeMap map[string]interface{} + var gateMap map[string]interface{} + err := json.Unmarshal(data, &opcodeMap) + if err != nil { + log.Print(err) + return err + } + + if gateValue, ok := opcodeMap["Arithmetic"]; ok { + gateJSON, err := json.Marshal(gateValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(gateJSON, &gateMap) + if err != nil { + log.Print(err) + return err + } + } else { + log.Print("Error: couldn't deserialize gate.") + return &json.UnmarshalTypeError{} + } + + var mulTerms []backend.MulTerm + var addTerms []backend.AddTerm + var constantTerm fr_bn254.Element + + // Deserialize mul terms. + if mulTermsValue, ok := gateMap["mul_terms"].([]interface{}); ok { + mulTermsJSON, err := json.Marshal(mulTermsValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(mulTermsJSON, &mulTerms) + if err != nil { + log.Print(err) + return err + } + } else { + log.Print("Error: couldn't deserialize mul terms.") + return &json.UnmarshalTypeError{} + } + + // Deserialize add terms. + if addTermsValue, ok := gateMap["linear_combinations"].([]interface{}); ok { + addTermsJSON, err := json.Marshal(addTermsValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(addTermsJSON, &addTerms) + if err != nil { + log.Print(err) + return err + } + } else { + log.Print("Error: couldn't deserialize add terms.") + return &json.UnmarshalTypeError{} + } + + // Deserialize constant term. + if encodedConstantTerm, ok := gateMap["q_c"].(string); ok { + constantTerm = backend.DeserializeFelt(encodedConstantTerm) + } else { + log.Print("Error: coefficient is not a felt.") + return &json.UnmarshalTypeError{} + } + + g.MulTerms = mulTerms + g.AddTerms = addTerms + g.QC = constantTerm + + return nil +} diff --git a/gnark_backend_ffi/backend/plonk/arithmetic_opcode_test.go b/gnark_backend_ffi/backend/plonk/arithmetic_opcode_test.go new file mode 100644 index 0000000..59f699c --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/arithmetic_opcode_test.go @@ -0,0 +1,54 @@ +package groth16 + +import ( + "encoding/json" + "fmt" + "gnark_backend_ffi/backend" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TODO: Test error cases. + +func TestArithmeticOpcodeUnmarshalJSON(t *testing.T) { + encodedCoefficient, _ := backend.SampleEncodedFelt() + multiplicand := rand.Uint32() + multiplier := rand.Uint32() + sum := rand.Uint32() + mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) + addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) + encodedConstantTerm, nonEncodedConstantTerm := backend.SampleEncodedFelt() + arithmetic_opcode := fmt.Sprintf(`{"Arithmetic": {"mul_terms":%s,"linear_combinations":%s,"q_c":"%s"}}`, mulTerms, addTerms, encodedConstantTerm) + + var r ArithmeticOpcode + err := json.Unmarshal([]byte(arithmetic_opcode), &r) + + assert.NoError(t, err) + assert.Equal(t, backend.UncheckedDeserializeMulTerms(mulTerms), r.MulTerms) + assert.Equal(t, backend.UncheckedDeserializeAddTerms(addTerms), r.AddTerms) + assert.Equal(t, nonEncodedConstantTerm, r.QC) +} + +func TestArithmeticOpcodesTermUnmarshalJSON(t *testing.T) { + encodedCoefficient, _ := backend.SampleEncodedFelt() + multiplicand := rand.Uint32() + multiplier := rand.Uint32() + sum := rand.Uint32() + mulTerms := fmt.Sprintf(`[{"coefficient":"%s","multiplicand":%d,"multiplier":%d},{"coefficient":"%s","multiplicand":%d,"multiplier":%d}]`, encodedCoefficient, multiplicand, multiplier, encodedCoefficient, multiplicand, multiplier) + addTerms := fmt.Sprintf(`[{"coefficient":"%s","sum":%d},{"coefficient":"%s","sum":%d}]`, encodedCoefficient, sum, encodedCoefficient, sum) + encodedConstantTerm, nonEncodedConstantTerm := backend.SampleEncodedFelt() + arithmetic_opcode := fmt.Sprintf(`{"Arithmetic": {"mul_terms":%s,"linear_combinations":%s,"q_c":"%s"}}`, mulTerms, addTerms, encodedConstantTerm) + arithmetic_opcodes := fmt.Sprintf(`[%s,%s]`, arithmetic_opcode, arithmetic_opcode) + + var r []ArithmeticOpcode + err := json.Unmarshal([]byte(arithmetic_opcodes), &r) + + assert.NoError(t, err) + for _, op := range r { + assert.Equal(t, backend.UncheckedDeserializeMulTerms(mulTerms), op.MulTerms) + assert.Equal(t, backend.UncheckedDeserializeAddTerms(addTerms), op.AddTerms) + assert.Equal(t, nonEncodedConstantTerm, op.QC) + } +} diff --git a/gnark_backend_ffi/backend/plonk/black_box_function_call_opcode.go b/gnark_backend_ffi/backend/plonk/black_box_function_call_opcode.go new file mode 100644 index 0000000..3bb8b81 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/black_box_function_call_opcode.go @@ -0,0 +1,34 @@ +package groth16 + +import "gnark_backend_ffi/backend" + +type BlackBoxFunctionName = int + +const ( + AES BlackBoxFunctionName = iota + AND + XOR + RANGE + SHA256 + Blake2s + MerkleMembership + SchnorrVerify + Pedersen + // 128 here specifies that this function + // should have 128 bits of security + HashToField128Security + EcdsaSecp256k1 + FixedBaseScalarMul + Keccak256 +) + +type FunctionInput struct { + Witness backend.Witness + NumBits uint32 +} + +type BlackBoxFunction struct { + Name BlackBoxFunctionName + Inputs []FunctionInput + Outputs backend.Witnesses +} diff --git a/gnark_backend_ffi/backend/plonk/directive_opcode.go b/gnark_backend_ffi/backend/plonk/directive_opcode.go new file mode 100644 index 0000000..80e4590 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/directive_opcode.go @@ -0,0 +1,88 @@ +package groth16 + +import ( + "encoding/json" + "gnark_backend_ffi/backend" + "log" +) + +type DirectiveName = int + +const ( + Invert DirectiveName = iota +) + +type DirectiveOpcode struct { + Name DirectiveName + Directive Directive +} + +type Directive interface{} + +type InvertDirective struct { + X backend.Witness + Result backend.Witness +} + +func (d *InvertDirective) UnmarshalJSON(data []byte) error { + var invertDirectiveMap map[string]interface{} + err := json.Unmarshal(data, &invertDirectiveMap) + if err != nil { + log.Print(err) + return err + } + + var X backend.Witness + var Result backend.Witness + + // Deserialize X. + if XValue, ok := invertDirectiveMap["x"].(float64); ok { + X = backend.Witness(XValue) + } else { + log.Fatal("Error: couldn't deserialize X.") + return &json.UnmarshalTypeError{} + } + + // Deserialize Result. + if ResultValue, ok := invertDirectiveMap["result"].(float64); ok { + Result = backend.Witness(ResultValue) + } else { + log.Fatal("Error: couldn't deserialize Result.") + return &json.UnmarshalTypeError{} + } + + d.X = X + d.Result = Result + + return nil +} + +func (d *DirectiveOpcode) UnmarshalJSON(data []byte) error { + var directiveMap map[string]interface{} + err := json.Unmarshal(data, &directiveMap) + if err != nil { + log.Fatal(err) + return err + } + + if invertDirectiveValue, ok := directiveMap["Invert"]; ok { + var dir InvertDirective + invertDirectiveJSON, err := json.Marshal(invertDirectiveValue) + if err != nil { + log.Print(err) + return err + } + err = json.Unmarshal(invertDirectiveJSON, &dir) + if err != nil { + log.Print(err) + return err + } + d.Name = Invert + d.Directive = dir + } else { + log.Print("Error: couldn't deserialize directive.") + return &json.UnmarshalTypeError{} + } + + return nil +} diff --git a/gnark_backend_ffi/backend/plonk/directive_opcode_test.go b/gnark_backend_ffi/backend/plonk/directive_opcode_test.go new file mode 100644 index 0000000..3584a99 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/directive_opcode_test.go @@ -0,0 +1,46 @@ +package groth16 + +import ( + "encoding/json" + "fmt" + "log" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TODO: Test error cases. + +func TestInvertDirectiveUnmarshalJSON(t *testing.T) { + x := rand.Uint32() + result := rand.Uint32() + invertDirectiveJSON := fmt.Sprintf(`{"x":%d,"result":%d}`, x, result) + + var d InvertDirective + err := json.Unmarshal([]byte(invertDirectiveJSON), &d) + if err != nil { + log.Fatal(err) + } + + assert.NoError(t, err) + assert.Equal(t, x, d.X) + assert.Equal(t, result, d.Result) +} + +func TestDirectiveUnmarshalJSONInvertDirective(t *testing.T) { + x := rand.Uint32() + result := rand.Uint32() + invertDirectiveJSON := fmt.Sprintf(`{"Invert": {"x":%d,"result":%d}}`, x, result) + + var d DirectiveOpcode + err := json.Unmarshal([]byte(invertDirectiveJSON), &d) + if err != nil { + log.Fatal(err) + } + + assert.NoError(t, err) + assert.Equal(t, Invert, d.Name) + assert.Equal(t, x, d.Directive.(InvertDirective).X) + assert.Equal(t, result, d.Directive.(InvertDirective).Result) +} diff --git a/gnark_backend_ffi/backend/plonk/helpers.go b/gnark_backend_ffi/backend/plonk/helpers.go new file mode 100644 index 0000000..8b287b2 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/helpers.go @@ -0,0 +1,16 @@ +package groth16 + +import ( + "encoding/json" + "log" +) + +func UncheckedDeserializeOpcodes(opcodes string) []Opcode { + var o []Opcode + err := json.Unmarshal([]byte(opcodes), &o) + if err != nil { + log.Fatal(err) + } + + return o +} diff --git a/gnark_backend_ffi/backend/plonk/opcode.go b/gnark_backend_ffi/backend/plonk/opcode.go new file mode 100644 index 0000000..c27faa3 --- /dev/null +++ b/gnark_backend_ffi/backend/plonk/opcode.go @@ -0,0 +1,3 @@ +package groth16 + +type Opcode interface{} diff --git a/gnark_backend_ffi/main.go b/gnark_backend_ffi/main.go index ec0c9ee..3358a77 100644 --- a/gnark_backend_ffi/main.go +++ b/gnark_backend_ffi/main.go @@ -7,9 +7,10 @@ import ( "encoding/json" "fmt" "log" - "strconv" - "gnark_backend_ffi/structs" + "gnark_backend_ffi/backend" + groth16_backend "gnark_backend_ffi/backend/groth16" + plonk_backend "gnark_backend_ffi/backend/plonk" fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark/backend/groth16" @@ -18,44 +19,123 @@ import ( cs_bn254 "github.com/consensys/gnark/constraint/bn254" ) -func buildR1CS(r structs.RawR1CS) (*cs_bn254.R1CS, fr_bn254.Vector, fr_bn254.Vector, int, int) { +// qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xa⋅xb) + qC == 0 +func buildSparseR1CS(a plonk_backend.ACIR) *cs_bn254.SparseR1CS { + sparseR1CS := cs_bn254.NewSparseR1CS(int(a.CurrentWitness) - 1) + + for _, opcode := range a.Opcodes { + if gate, ok := opcode.(plonk_backend.ArithmeticOpcode); ok { + var xa, xb, xc int + var qL, qR, qO, qC, qM constraint.Coeff + + // Case qM⋅(xa⋅xb) + if len(gate.MulTerms) != 0 { + mulTerm := gate.MulTerms[0] + qM = sparseR1CS.FromInterface(mulTerm.Coefficient) + xa = int(mulTerm.Multiplicand) + xb = int(mulTerm.Multiplier) + } + + // Case qO⋅xc + if len(gate.AddTerms) == 1 { + qOwOTerm := gate.AddTerms[0] + qO = sparseR1CS.FromInterface(qOwOTerm.Coefficient) + xc = int(qOwOTerm.Sum) + } + + // Case qL⋅xa + qR⋅xb + if len(gate.AddTerms) == 2 { + // qL⋅xa + qLwLTerm := gate.AddTerms[0] + qL = sparseR1CS.FromInterface(qLwLTerm.Coefficient) + xa = int(qLwLTerm.Sum) + // qR⋅xb + qRwRTerm := gate.AddTerms[1] + qR = sparseR1CS.FromInterface(qRwRTerm.Coefficient) + xb = int(qRwRTerm.Sum) + } + + // Case qL⋅xa + qR⋅xb + qO⋅xc + if len(gate.AddTerms) == 3 { + // qL⋅xa + qLwLTerm := gate.AddTerms[0] + qL = sparseR1CS.FromInterface(qLwLTerm.Coefficient) + xa = int(qLwLTerm.Sum) + // qR⋅xb + qRwRTerm := gate.AddTerms[1] + qR = sparseR1CS.FromInterface(qRwRTerm.Coefficient) + xb = int(qRwRTerm.Sum) + // qO⋅xc + qOwOTerm := gate.AddTerms[2] + qO = sparseR1CS.FromInterface(qOwOTerm.Coefficient) + xc = int(qOwOTerm.Sum) + } + + // Add the qC term + qC = sparseR1CS.FromInterface(gate.QC) + + K := sparseR1CS.MakeTerm(&qC, 0) + K.MarkConstant() + + constraint := constraint.SparseR1C{ + L: sparseR1CS.MakeTerm(&qL, xa), + R: sparseR1CS.MakeTerm(&qR, xb), + O: sparseR1CS.MakeTerm(&qO, xc), + M: [2]constraint.Term{sparseR1CS.MakeTerm(&qM, xa), sparseR1CS.MakeTerm(&qM, xb)}, + K: K.CoeffID(), + } + + sparseR1CS.AddConstraint(constraint) + } else { + log.Print("unhandled opcode:", opcode) + } + } + + return sparseR1CS +} + +func buildR1CS(r groth16_backend.RawR1CS) (*cs_bn254.R1CS, fr_bn254.Vector, fr_bn254.Vector) { // Create R1CS. r1cs := cs_bn254.NewR1CS(int(r.NumConstraints)) - // Fill process RawR1CS. - nPublicVariables := 0 - nPrivateVariables := 0 - var allVariableIndices []int + // Define the R1CS variables. + _ = r1cs.AddPublicVariable("1") // ONE_WIRE var publicVariables fr_bn254.Vector var privateVariables fr_bn254.Vector for i, value := range r.Values { - variableName := strconv.Itoa(i) + i++ for _, publicInput := range r.PublicInputs { if uint32(i) == publicInput { - allVariableIndices = append(allVariableIndices, r1cs.AddPublicVariable(variableName)) + r1cs.AddPublicVariable(fmt.Sprintf("public_%d", i)) publicVariables = append(publicVariables, value) - nPublicVariables++ } else { - allVariableIndices = append(allVariableIndices, r1cs.AddSecretVariable(variableName)) + r1cs.AddSecretVariable(fmt.Sprintf("secret_%d", i)) privateVariables = append(privateVariables, value) - nPrivateVariables++ } } } // Generate constraints. - ONE := r1cs.AddPublicVariable("ONE") - ZERO := r1cs.AddPublicVariable("ZERO") COEFFICIENT_ONE := r1cs.FromInterface(1) for _, gate := range r.Gates { var terms constraint.LinearExpression for _, mul_term := range gate.MulTerms { coefficient := r1cs.FromInterface(mul_term.Coefficient) + multiplicand := r.Values[mul_term.Multiplicand] + multiplier := r.Values[mul_term.Multiplier] + var product fr_bn254.Element + product.Mul(&multiplicand, &multiplier) + + productVariable := r1cs.AddInternalVariable() - product := mul_term.Multiplicand * mul_term.Multiplier - productVariableName := strconv.FormatUint(uint64(product), 10) - productVariable := r1cs.AddSecretVariable(productVariableName) + mulR1C := constraint.R1C{ + L: constraint.LinearExpression{r1cs.MakeTerm(&COEFFICIENT_ONE, int(mul_term.Multiplicand))}, + R: constraint.LinearExpression{r1cs.MakeTerm(&COEFFICIENT_ONE, int(mul_term.Multiplier))}, + O: constraint.LinearExpression{r1cs.MakeTerm(&coefficient, productVariable)}, + } + + r1cs.AddConstraint(mulR1C) terms = append(terms, r1cs.MakeTerm(&coefficient, productVariable)) } @@ -64,24 +144,22 @@ func buildR1CS(r structs.RawR1CS) (*cs_bn254.R1CS, fr_bn254.Vector, fr_bn254.Vec coefficient := r1cs.FromInterface(add_term.Coefficient) sum := add_term.Sum - sumVariable := allVariableIndices[sum] + terms = append(terms, r1cs.MakeTerm(&coefficient, int(sum))) + } - terms = append(terms, r1cs.MakeTerm(&coefficient, sumVariable)) + r1c := constraint.R1C{ + L: constraint.LinearExpression{r1cs.MakeTerm(&COEFFICIENT_ONE, 0)}, + R: terms, + O: constraint.LinearExpression{}, } - r1cs.AddConstraint( - constraint.R1C{ - L: constraint.LinearExpression{r1cs.MakeTerm(&COEFFICIENT_ONE, ONE)}, - R: terms, - O: constraint.LinearExpression{r1cs.MakeTerm(&COEFFICIENT_ONE, ZERO)}, - }, - ) + r1cs.AddConstraint(r1c) } - return r1cs, publicVariables, privateVariables, nPublicVariables, nPrivateVariables + return r1cs, publicVariables, privateVariables } -func buildWitnesses(r1cs *cs_bn254.R1CS, publicVariables fr_bn254.Vector, privateVariables fr_bn254.Vector, nPublicVariables int, nPrivateVariables int) witness.Witness { +func buildWitnesses(r1cs *cs_bn254.R1CS, publicVariables fr_bn254.Vector, privateVariables fr_bn254.Vector) witness.Witness { witnessValues := make(chan any) go func() { @@ -99,7 +177,8 @@ func buildWitnesses(r1cs *cs_bn254.R1CS, publicVariables fr_bn254.Vector, privat log.Fatal(err) } - witness.Fill(nPublicVariables, nPrivateVariables, witnessValues) + // -1 because the first variable is the ONE_WIRE. + witness.Fill(r1cs.GetNbPublicVariables()-1, r1cs.GetNbSecretVariables(), witnessValues) return witness } @@ -107,68 +186,62 @@ func buildWitnesses(r1cs *cs_bn254.R1CS, publicVariables fr_bn254.Vector, privat //export ProveWithMeta func ProveWithMeta(rawR1CS string) *C.char { // Deserialize rawR1CS. - var r structs.RawR1CS + var r groth16_backend.RawR1CS err := json.Unmarshal([]byte(rawR1CS), &r) if err != nil { log.Fatal(err) } - // Create R1CS. - r1cs := cs_bn254.NewR1CS(0) - - // Add variables. - witness, err := witness.New(r1cs.CurveID().ScalarField()) - if err != nil { - log.Fatal(err) - } - witness.Fill(0, 0, nil) + r1cs, publicVariables, privateVariables := buildR1CS(r) - // Add constraints. + witness := buildWitnesses(r1cs, publicVariables, privateVariables) // Setup. - pk, _, err := groth16.Setup(r1cs) + provingKey, _, err := groth16.Setup(r1cs) if err != nil { log.Fatal(err) } // Prove. - proof, err := groth16.Prove(r1cs, pk, witness) + proof, err := groth16.Prove(r1cs, provingKey, witness) if err != nil { log.Fatal(err) } - fmt.Println("Proof: ", proof) // Serialize proof var serialized_proof bytes.Buffer proof.WriteTo(&serialized_proof) - proof_string := serialized_proof.String() + proof_string := hex.EncodeToString(serialized_proof.Bytes()) return C.CString(proof_string) } //export ProveWithPK -func ProveWithPK(rawR1CS string, provingKey string) *C.char { - // Create R1CS. - r1cs := cs_bn254.NewR1CS(1) - - // Add variables. - witness, err := witness.New(r1cs.CurveID().ScalarField()) +func ProveWithPK(rawR1CS string, encodedProvingKey string) *C.char { + // Deserialize rawR1CS. + var r groth16_backend.RawR1CS + err := json.Unmarshal([]byte(rawR1CS), &r) if err != nil { log.Fatal(err) } - witness.Fill(0, 0, nil) - // Add constraints. + r1cs, publicVariables, privateVariables := buildR1CS(r) + + witness := buildWitnesses(r1cs, publicVariables, privateVariables) // Deserialize proving key. - pk := groth16.NewProvingKey(r1cs.CurveID()) - _, err = pk.ReadFrom(bytes.NewReader([]byte(provingKey))) + provingKey := groth16.NewProvingKey(r1cs.CurveID()) + decodedProvingKey, err := hex.DecodeString(encodedProvingKey) + if err != nil { + log.Fatal(err) + } + _, err = provingKey.ReadFrom(bytes.NewReader([]byte(decodedProvingKey))) if err != nil { log.Fatal(err) } // Prove. - proof, err := groth16.Prove(r1cs, pk, witness) + proof, err := groth16.Prove(r1cs, provingKey, witness) if err != nil { log.Fatal(err) } @@ -176,28 +249,31 @@ func ProveWithPK(rawR1CS string, provingKey string) *C.char { // Serialize proof var serialized_proof bytes.Buffer proof.WriteTo(&serialized_proof) - proof_string := serialized_proof.String() + proof_string := hex.EncodeToString(serialized_proof.Bytes()) return C.CString(proof_string) } //export VerifyWithMeta -func VerifyWithMeta(rawr1cs string, proof string) bool { - // Create R1CS. - r1cs := cs_bn254.NewR1CS(1) - - // Add variables. - witness, err := witness.New(r1cs.CurveID().ScalarField()) +func VerifyWithMeta(rawR1CS string, encodedProof string) bool { + // Deserialize rawR1CS. + var r groth16_backend.RawR1CS + err := json.Unmarshal([]byte(rawR1CS), &r) if err != nil { log.Fatal(err) } - witness.Fill(0, 0, nil) - // Add constraints. + r1cs, publicVariables, privateVariables := buildR1CS(r) + + witness := buildWitnesses(r1cs, publicVariables, privateVariables) // Deserialize proof. - p := groth16.NewProof(r1cs.CurveID()) - _, err = p.ReadFrom(bytes.NewReader([]byte(proof))) + proof := groth16.NewProof(r1cs.CurveID()) + decodedProof, err := hex.DecodeString(encodedProof) + if err != nil { + log.Fatal(err) + } + _, err = proof.ReadFrom(bytes.NewReader([]byte(decodedProof))) if err != nil { log.Fatal(err) } @@ -215,7 +291,7 @@ func VerifyWithMeta(rawr1cs string, proof string) bool { } // Verify. - if groth16.Verify(p, vk, publicInputs) != nil { + if groth16.Verify(proof, vk, publicInputs) != nil { return false } @@ -223,29 +299,36 @@ func VerifyWithMeta(rawr1cs string, proof string) bool { } //export VerifyWithVK -func VerifyWithVK(rawr1cs string, proof string, verifyingKey string) bool { - // Create R1CS. - r1cs := cs_bn254.NewR1CS(1) - - // Add variables. - witness, err := witness.New(r1cs.CurveID().ScalarField()) +func VerifyWithVK(rawR1CS string, encodedProof string, encodedVerifyingKey string) bool { + // Deserialize rawR1CS. + var r groth16_backend.RawR1CS + err := json.Unmarshal([]byte(rawR1CS), &r) if err != nil { log.Fatal(err) } - witness.Fill(0, 0, nil) - // Add constraints. + r1cs, publicVariables, privateVariables := buildR1CS(r) + + witness := buildWitnesses(r1cs, publicVariables, privateVariables) // Deserialize proof. - p := groth16.NewProof(r1cs.CurveID()) - _, err = p.ReadFrom(bytes.NewReader([]byte(proof))) + proof := groth16.NewProof(r1cs.CurveID()) + decodedProof, err := hex.DecodeString(encodedProof) + if err != nil { + log.Fatal(err) + } + _, err = proof.ReadFrom(bytes.NewReader(decodedProof)) if err != nil { log.Fatal(err) } // Deserialize verifying key. - vk := groth16.NewVerifyingKey(r1cs.CurveID()) - _, err = vk.ReadFrom(bytes.NewReader([]byte(verifyingKey))) + verifyingKey := groth16.NewVerifyingKey(r1cs.CurveID()) + decodedVerifyingKey, err := hex.DecodeString(encodedVerifyingKey) + if err != nil { + log.Fatal(err) + } + _, err = verifyingKey.ReadFrom(bytes.NewReader(decodedVerifyingKey)) if err != nil { log.Fatal(err) } @@ -257,7 +340,7 @@ func VerifyWithVK(rawr1cs string, proof string, verifyingKey string) bool { } // Verify. - if groth16.Verify(p, vk, publicInputs) != nil { + if groth16.Verify(proof, verifyingKey, publicInputs) != nil { return false } @@ -266,41 +349,38 @@ func VerifyWithVK(rawr1cs string, proof string, verifyingKey string) bool { //export Preprocess func Preprocess(rawR1CS string) (*C.char, *C.char) { - // Create R1CS. - r1cs := cs_bn254.NewR1CS(1) - - // Add variables. - witness, err := witness.New(r1cs.CurveID().ScalarField()) + // Deserialize rawR1CS. + var r groth16_backend.RawR1CS + err := json.Unmarshal([]byte(rawR1CS), &r) if err != nil { log.Fatal(err) } - witness.Fill(0, 0, nil) - // Add constraints. + r1cs, _, _ := buildR1CS(r) // Setup. - pk, vk, err := groth16.Setup(r1cs) + provingKey, verifyingKey, err := groth16.Setup(r1cs) if err != nil { log.Fatal(err) } // Serialize proving key. - var serialized_pk bytes.Buffer - pk.WriteTo(&serialized_pk) - pk_string := serialized_pk.String() + var serializedProvingKey bytes.Buffer + provingKey.WriteTo(&serializedProvingKey) + provingKeyString := hex.EncodeToString(serializedProvingKey.Bytes()) // Serialize verifying key. - var serialized_vk bytes.Buffer - vk.WriteTo(&serialized_vk) - vk_string := serialized_vk.String() + var serializedVerifyingKey bytes.Buffer + verifyingKey.WriteTo(&serializedVerifyingKey) + verifyingKeyString := hex.EncodeToString(serializedVerifyingKey.Bytes()) - return C.CString(pk_string), C.CString(vk_string) + return C.CString(provingKeyString), C.CString(verifyingKeyString) } //export IntegrationTestFeltSerialization func IntegrationTestFeltSerialization(encodedFelt string) *C.char { - deserializedFelt := structs.DeserializeFelt(encodedFelt) - fmt.Printf("| GO |\n%v\n", deserializedFelt) + deserializedFelt := backend.DeserializeFelt(encodedFelt) + fmt.Printf("| GO |n%vn", deserializedFelt) // Serialize the felt. serializedFelt := deserializedFelt.Bytes() @@ -313,7 +393,7 @@ func IntegrationTestFeltSerialization(encodedFelt string) *C.char { //export IntegrationTestFeltsSerialization func IntegrationTestFeltsSerialization(encodedFelts string) *C.char { - deserializedFelts := structs.DeserializeFelts(encodedFelts) + deserializedFelts := backend.DeserializeFelts(encodedFelts) // Serialize the felt. serializedFelts, err := deserializedFelts.MarshalBinary() @@ -334,38 +414,366 @@ func IntegrationTestU64Serialization(number uint64) uint64 { } //export IntegrationTestMulTermSerialization -func IntegrationTestMulTermSerialization(mulTerm string) *C.char { - return C.CString("unimplemented") +func IntegrationTestMulTermSerialization(mulTermJSON string) *C.char { + var deserializedMulTerm backend.MulTerm + err := json.Unmarshal([]byte(mulTermJSON), &deserializedMulTerm) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + fmt.Println("", deserializedMulTerm.Coefficient) + fmt.Println("", deserializedMulTerm.Multiplicand) + fmt.Println("", deserializedMulTerm.Multiplier) + + serializedMulTerm, err := json.Marshal(deserializedMulTerm) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedMulTerm)) } //export IntegrationTestMulTermsSerialization -func IntegrationTestMulTermsSerialization(encodedMulTerms string) *C.char { - return C.CString("unimplemented") +func IntegrationTestMulTermsSerialization(mulTermsJSON string) *C.char { + var deserializedMulTerms []backend.MulTerm + err := json.Unmarshal([]byte(mulTermsJSON), &deserializedMulTerms) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + for _, deserializedMulTerm := range deserializedMulTerms { + fmt.Println("", deserializedMulTerm.Coefficient) + fmt.Println("", deserializedMulTerm.Multiplicand) + fmt.Println("", deserializedMulTerm.Multiplier) + fmt.Println() + } + + serializedMulTerms, err := json.Marshal(deserializedMulTerms) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedMulTerms)) } //export IntegrationTestAddTermSerialization -func IntegrationTestAddTermSerialization(encodedAddTerm string) *C.char { - return C.CString("unimplemented") +func IntegrationTestAddTermSerialization(addTermJSON string) *C.char { + var deserializedAddTerm backend.AddTerm + err := json.Unmarshal([]byte(addTermJSON), &deserializedAddTerm) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + fmt.Println("", deserializedAddTerm.Coefficient) + fmt.Println("", deserializedAddTerm.Sum) + + serializedAddTerm, err := json.Marshal(deserializedAddTerm) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedAddTerm)) } //export IntegrationTestAddTermsSerialization -func IntegrationTestAddTermsSerialization(encodedAddTerms string) *C.char { - return C.CString("unimplemented") +func IntegrationTestAddTermsSerialization(addTermsJSON string) *C.char { + var deserializedAddTerms []backend.AddTerm + err := json.Unmarshal([]byte(addTermsJSON), &deserializedAddTerms) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + for _, deserializedAddTerm := range deserializedAddTerms { + fmt.Println("", deserializedAddTerm.Coefficient) + fmt.Println("", deserializedAddTerm.Sum) + fmt.Println() + } + + serializedAddTerms, err := json.Marshal(deserializedAddTerms) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedAddTerms)) } //export IntegrationTestRawGateSerialization -func IntegrationTestRawGateSerialization(encodedRawGate string) *C.char { - return C.CString("unimplemented") +func IntegrationTestRawGateSerialization(rawGateJSON string) *C.char { + var deserializedRawGate groth16_backend.RawGate + err := json.Unmarshal([]byte(rawGateJSON), &deserializedRawGate) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + fmt.Println("", deserializedRawGate.MulTerms) + fmt.Println("", deserializedRawGate.AddTerms) + fmt.Println("", deserializedRawGate.ConstantTerm) + fmt.Println() + + serializedRawGate, err := json.Marshal(deserializedRawGate) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedRawGate)) } //export IntegrationTestRawGatesSerialization -func IntegrationTestRawGatesSerialization(encodedRawGates string) *C.char { - return C.CString("unimplemented") +func IntegrationTestRawGatesSerialization(rawGatesJSON string) *C.char { + var deserializedRawGates []groth16_backend.RawGate + err := json.Unmarshal([]byte(rawGatesJSON), &deserializedRawGates) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + for _, deserializedRawGate := range deserializedRawGates { + fmt.Println("", deserializedRawGate.MulTerms) + fmt.Println("", deserializedRawGate.AddTerms) + fmt.Println("", deserializedRawGate.ConstantTerm) + fmt.Println() + } + + serializedRawGate, err := json.Marshal(deserializedRawGates) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedRawGate)) } //export IntegrationTestRawR1CSSerialization -func IntegrationTestRawR1CSSerialization(encodedR1CS string) *C.char { - return C.CString("unimplemented") +func IntegrationTestRawR1CSSerialization(rawR1CSJSON string) *C.char { + var deserializedRawR1CS groth16_backend.RawR1CS + err := json.Unmarshal([]byte(rawR1CSJSON), &deserializedRawR1CS) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + fmt.Println("Gates: ", deserializedRawR1CS.Gates) + fmt.Println("Public inputs: ", deserializedRawR1CS.PublicInputs) + fmt.Println("Values: ", deserializedRawR1CS.Values) + fmt.Println("Number of variables: ", deserializedRawR1CS.NumVariables) + fmt.Println("Number of constraints: ", deserializedRawR1CS.NumConstraints) + fmt.Println() + + serializedRawR1CS, err := json.Marshal(deserializedRawR1CS) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedRawR1CS)) } -func main() {} +//export IntegrationTestCircuitSerialization +func IntegrationTestCircuitSerialization(acirJSON string) *C.char { + var acir plonk_backend.ACIR + err := json.Unmarshal([]byte(acirJSON), &acir) + if err != nil { + log.Fatal(err) + } + + fmt.Println("| GO |") + fmt.Println("Current Witness: ", acir.CurrentWitness) + fmt.Println("Opcodes: ", acir.Opcodes) + fmt.Println("Public Inputs: ", acir.PublicInputs) + fmt.Println() + + serializedAcir, err := json.Marshal(acir) + if err != nil { + log.Fatal(err) + } + + return C.CString(string(serializedAcir)) +} + +func ExampleSimpleCircuit() { + publicVariables := []fr_bn254.Element{fr_bn254.NewElement(2), fr_bn254.NewElement(6)} + secretVariables := []fr_bn254.Element{fr_bn254.NewElement(3)} + + /* R1CS Building */ + + fmt.Println("Building R1CS...") + // x * y == z + // x is secret + // y is public + r1cs := cs_bn254.NewR1CS(1) + + // Variables + _ = r1cs.AddPublicVariable("1") // the ONE_WIRE + Y := r1cs.AddPublicVariable("Y") + Z := r1cs.AddPublicVariable("Z") + X := r1cs.AddSecretVariable("X") + + // Coefficients + cOne := r1cs.FromInterface(1) + + // Constraints + fmt.Println("Adding constraints...") + r1cs.AddConstraint(constraint.R1C{ + L: constraint.LinearExpression{r1cs.MakeTerm(&cOne, X)}, + R: constraint.LinearExpression{r1cs.MakeTerm(&cOne, Y)}, + O: constraint.LinearExpression{r1cs.MakeTerm(&cOne, Z)}, + }) + fmt.Println("Constraints added.") + fmt.Println("R1CS built.") + + constraints, r := r1cs.GetConstraints() + + for _, r1c := range constraints { + fmt.Println(r1c.String(r)) + } + + /* Universal SRS Generation */ + + fmt.Println("Generating SRS...") + + pk, vk, _ := groth16.Setup(r1cs) + + fmt.Println("SRS generated.") + + /* Proving */ + + fmt.Println("Proving...") + + witness := buildWitnesses(r1cs, publicVariables, secretVariables) + + p, _ := groth16.Prove(r1cs, pk, witness) + + fmt.Println("Proof generated.") + + /* Verification */ + + fmt.Println("Verifying...") + + publicWitness, _ := witness.Public() + + verifies := groth16.Verify(p, vk, publicWitness) + + fmt.Println("Verifies:", verifies == nil) +} + +func main() { + ExampleSimpleCircuit() + + // // constrain x == y + // // constrain 0 == 0 + // // rawR1CS := `{"gates":[{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":1},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":2},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":4}]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":5}]},{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]}],"num_constraints":11,"num_variables":7,"public_inputs":[2],"values":"00000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}` + // // constrain 1 == 1 + // // rawR1CS := `{"gates":[{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":1},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":2},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":4}]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":5}]},{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]}],"num_constraints":11,"num_variables":7,"public_inputs":[2],"values":"00000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}` + // // constrain 2 == 2 + // rawR1CS := `{"gates":[{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":1},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":2},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":4}]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":5}]},{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]}],"num_constraints":11,"num_variables":7,"public_inputs":[2],"values":"00000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}` + // // constrain 3 == 3 + // // rawR1CS := `{"gates":[{"mul_terms":[],"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":1},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":2},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000"},{"mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":4}],"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000"},{"mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":5}],"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000"},{"mul_terms":[],"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000"}],"public_inputs":[2],"values":"00000006000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","num_variables":7,"num_constraints":11}` + // // Invalid + // invalidRawR1CS := `{"gates":[{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":1},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":2},{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":4}]},{"add_terms":[{"coefficient":"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000","sum":3}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","multiplicand":3,"multiplier":5}]},{"add_terms":[{"coefficient":"0000000000000000000000000000000000000000000000000000000000000001","sum":5}],"constant_term":"0000000000000000000000000000000000000000000000000000000000000000","mul_terms":[]}],"num_constraints":11,"num_variables":7,"public_inputs":[2],"values":"00000006000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}` + + // acir := `{"current_witness_index":6,"opcodes":[{"Arithmetic":{"linear_combinations":[["0000000000000000000000000000000000000000000000000000000000000001",1],["30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",2],["30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",3]],"mul_terms":[],"q_c":"0000000000000000000000000000000000000000000000000000000000000000"}},{"Directive":{"Invert":{"result":4,"x":3}}},{"Arithmetic":{"linear_combinations":[["30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",5]],"mul_terms":[["0000000000000000000000000000000000000000000000000000000000000001",3,4]],"q_c":"0000000000000000000000000000000000000000000000000000000000000000"}},{"Arithmetic":{"linear_combinations":[["30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",3]],"mul_terms":[["0000000000000000000000000000000000000000000000000000000000000001",3,5]],"q_c":"0000000000000000000000000000000000000000000000000000000000000000"}},{"Arithmetic":{"linear_combinations":[["0000000000000000000000000000000000000000000000000000000000000001",5]],"mul_terms":[],"q_c":"0000000000000000000000000000000000000000000000000000000000000000"}}],"public_inputs":[2]}` + + // var r backend.RawR1CS + // err := json.Unmarshal([]byte(rawR1CS), &r) + // if err != nil { + // log.Fatal(err) + // } + + // // fmt.Println("Gates: ", len(r.Gates)) + // // mulTerms := 0 + // // addTerms := 0 + // // for g, gate := range r.Gates { + // // fmt.Println("Gate", g) + // // fmt.Println() + + // // fmt.Println("MulTerms:") + // // mulTerms += len(gate.MulTerms) + // // for _, mulTerm := range gate.MulTerms { + // // fmt.Println("MulTerm:", mulTerm) + // // var product fr_bn254.Element + // // product.Mul(&r.Values[mulTerm.Multiplier], &r.Values[mulTerm.Multiplicand]) + // // fmt.Println("Multiplication", mulTerm.Coefficient.String(), "*", r.Values[mulTerm.Multiplier].String(), "*", r.Values[mulTerm.Multiplicand].String(), "=", product.String()) + // // fmt.Println("Product:", product.String()) + // // } + // // fmt.Println() + + // // addTerms += len(gate.AddTerms) + // // fmt.Println("AddTerms:") + // // for _, addTerm := range gate.AddTerms { + // // fmt.Println("AddTerm:", addTerm) + // // fmt.Println("Addition", addTerm.Coefficient.String(), "*", r.Values[addTerm.Sum].String()) + // // } + // // fmt.Println() + + // // fmt.Println("ConstantTerm:", gate.ConstantTerm) + // // fmt.Println() + + // // fmt.Println() + // // } + // // fmt.Println("MulTerms: ", mulTerms) + // // fmt.Println("AddTerms: ", mulTerms) + + // r1cs, publicVariables, privateVariables := buildR1CS(r) + + // constraints, res := r1cs.GetConstraints() + // for _, r1c := range constraints { + // fmt.Println(r1c.String(res)) + // } + // fmt.Println() + // fmt.Println("NbValues: ", len(r.Values)) + // for _, value := range r.Values { + // fmt.Println("Value: ", value.String()) + // } + // fmt.Println("NbPublicInputs: ", len(r.PublicInputs), "PublicInputs: ", r.PublicInputs) + + // witness := buildWitnesses(r1cs, publicVariables, privateVariables) + // publicWitnesses, _ := witness.Public() + + // // Setup. + // fmt.Println("Setting up...") + // pk, vk, err := groth16.Setup(r1cs) + // if err != nil { + // log.Fatal(err) + // } + // fmt.Println("Set up") + + // // Prove. + // fmt.Println("Proving...") + // proof, err := groth16.Prove(r1cs, pk, witness) + // if err != nil { + // log.Fatal(err) + // } + // fmt.Println("Proved") + + // // Verify. + // verified := groth16.Verify(proof, vk, publicWitnesses) + + // fmt.Println("Verifies with valid public inputs: ", verified == nil) + // fmt.Println() + + // // Invalid verification (same proof, wrong public value). + // err = json.Unmarshal([]byte(invalidRawR1CS), &r) + // if err != nil { + // log.Fatal(err) + // } + + // invalidR1CS, publicVariables, privateVariables := buildR1CS(r) + + // constraints, res = invalidR1CS.GetConstraints() + // for _, r1c := range constraints { + // fmt.Println(r1c.String(res)) + // } + + // invalidWitness := buildWitnesses(invalidR1CS, publicVariables, privateVariables) + // invalidPublicWitnesses, _ := invalidWitness.Public() + // invalidVerified := groth16.Verify(proof, vk, invalidPublicWitnesses) + + // fmt.Println("Valid Public Witnesses: ", publicWitnesses.Vector().(fr_bn254.Vector).String()) + // fmt.Println("Invalid Public Witnesses: ", invalidPublicWitnesses.Vector().(fr_bn254.Vector).String()) + // fmt.Println() + + // fmt.Println("Verifies with invalid public inputs: ", invalidVerified == nil) +} diff --git a/src/acvm/mod.rs b/src/acvm/mod.rs index a279614..90bfb53 100644 --- a/src/acvm/mod.rs +++ b/src/acvm/mod.rs @@ -1,5 +1,9 @@ -pub use acvm::acir::{ - circuit::{Circuit, Opcode, PublicInputs}, - native_types::{Expression, Witness}, - FieldElement, +pub use acvm::{ + acir::{ + circuit::{Circuit, Opcode, PublicInputs}, + native_types::{Expression, Witness}, + FieldElement, + }, + pwg::witness_to_value, + PartialWitnessGenerator, }; diff --git a/src/acvm_interop.rs b/src/backend.rs similarity index 80% rename from src/acvm_interop.rs rename to src/backend.rs index b90e387..4f5c201 100644 --- a/src/acvm_interop.rs +++ b/src/backend.rs @@ -4,6 +4,7 @@ use acvm::acir::{ circuit::opcodes::BlackBoxFuncCall, circuit::Circuit, native_types::Witness, BlackBoxFunc, }; +use acvm::pwg::witness_to_value; use acvm::{ FieldElement, Language, OpcodeResolutionError, PartialWitnessGenerator, ProofSystemCompiler, SmartContract, @@ -72,19 +73,31 @@ impl ProofSystemCompiler for Gnark { witness_values: std::collections::BTreeMap, proving_key: &[u8], ) -> Vec { - // TODO: modify gnark serializer to accept the BTreeMap - let values: Vec = witness_values.values().copied().collect(); + let num_witnesses = circuit.num_vars(); + let values = (1..num_witnesses) + .map(|wit_index| { + *witness_to_value(&witness_values, Witness(wit_index)) + .unwrap_or(&FieldElement::zero()) + }) + .collect(); gnark_backend::prove_with_pk(circuit, values, proving_key).unwrap() } fn verify_with_vk( &self, proof: &[u8], - public_inputs: Vec, + public_inputs: BTreeMap, circuit: &Circuit, verification_key: &[u8], ) -> bool { - gnark_backend::verify_with_vk(circuit, proof, &public_inputs, verification_key).unwrap() + let num_witnesses = circuit.num_vars(); + let public: Vec = (1..num_witnesses) + .map(|wit_index| { + *witness_to_value(&public_inputs, Witness(wit_index)) + .unwrap_or(&FieldElement::zero()) + }) + .collect(); + gnark_backend::verify_with_vk(circuit, proof, &public, verification_key).unwrap() } } @@ -93,7 +106,7 @@ impl PartialWitnessGenerator for Gnark { _initial_witness: &mut BTreeMap, _func_call: &BlackBoxFuncCall, ) -> Result<(), OpcodeResolutionError> { - todo!() + unimplemented!() } } @@ -104,7 +117,7 @@ impl GadgetCaller { _initial_witness: &mut BTreeMap, _gadget_call: &BlackBoxFuncCall, ) -> Result<(), OpcodeResolutionError> { - todo!() + unimplemented!() } } diff --git a/src/gnark_backend_wrapper/groth16/c_go_structures.rs b/src/gnark_backend_wrapper/c_go_structures.rs similarity index 89% rename from src/gnark_backend_wrapper/groth16/c_go_structures.rs rename to src/gnark_backend_wrapper/c_go_structures.rs index 0509ee2..6c850a5 100644 --- a/src/gnark_backend_wrapper/groth16/c_go_structures.rs +++ b/src/gnark_backend_wrapper/c_go_structures.rs @@ -1,4 +1,4 @@ -use crate::gnark_backend_wrapper::groth16::GnarkBackendError; +use crate::gnark_backend_wrapper::GnarkBackendError; use std::ffi::CString; use std::os::raw::c_char; diff --git a/src/gnark_backend_wrapper/groth16/errors.rs b/src/gnark_backend_wrapper/errors.rs similarity index 85% rename from src/gnark_backend_wrapper/groth16/errors.rs rename to src/gnark_backend_wrapper/errors.rs index b04d1e6..cc05a37 100644 --- a/src/gnark_backend_wrapper/groth16/errors.rs +++ b/src/gnark_backend_wrapper/errors.rs @@ -1,3 +1,4 @@ +use acvm::OpcodeResolutionError; use thiserror::Error; #[derive(Error, Debug)] @@ -23,6 +24,9 @@ pub enum GnarkBackendError { #[error("Verify did not return a valid bool")] VerifyInvalidBoolError, + #[error("Opcode resolution error: {0}")] + OpcodeResolutionError(#[from] OpcodeResolutionError), + #[error("an error occurred: {0}")] Error(String), } diff --git a/src/gnark_backend_wrapper/groth16/acir_to_r1cs.rs b/src/gnark_backend_wrapper/groth16/acir_to_r1cs.rs index 2405992..86012d2 100644 --- a/src/gnark_backend_wrapper/groth16/acir_to_r1cs.rs +++ b/src/gnark_backend_wrapper/groth16/acir_to_r1cs.rs @@ -1,6 +1,10 @@ -use super::{from_felt, Fr}; +use super::super::{from_felt, Fr}; use crate::acvm; +use crate::gnark_backend_wrapper::groth16::serialize::{ + deserialize_felt, deserialize_felts, serialize_felt, serialize_felts, +}; use crate::gnark_backend_wrapper::groth16::GnarkBackendError; +use crate::gnark_backend_wrapper::num_constraints; use std::num::TryFromIntError; // AcirCircuit and AcirArithGate are R1CS-friendly structs. @@ -10,28 +14,62 @@ use std::num::TryFromIntError; // - These structures only support arithmetic gates, while the compiler has other // gate types. These can be added later once the backend knows how to deal with things like XOR // or once ACIR is taught how to do convert these black box functions to Arithmetic gates. -#[derive(Clone)] +#[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct RawR1CS { pub gates: Vec, - pub public_inputs: acvm::PublicInputs, + pub public_inputs: Vec, + #[serde( + serialize_with = "serialize_felts", + deserialize_with = "deserialize_felts" + )] pub values: Vec, - pub num_variables: usize, - pub num_constraints: usize, + pub num_variables: u64, + pub num_constraints: u64, } -#[derive(Clone, Debug)] +/* +1. Implementar Deserialize para +*/ +#[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct RawGate { - pub mul_terms: Vec<(Fr, acvm::Witness, acvm::Witness)>, - pub add_terms: Vec<(Fr, acvm::Witness)>, + pub mul_terms: Vec, + pub add_terms: Vec, + #[serde( + serialize_with = "serialize_felt", + deserialize_with = "deserialize_felt" + )] pub constant_term: Fr, } +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct MulTerm { + #[serde( + serialize_with = "serialize_felt", + deserialize_with = "deserialize_felt" + )] + pub coefficient: Fr, + pub multiplicand: acvm::Witness, + pub multiplier: acvm::Witness, +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct AddTerm { + #[serde( + serialize_with = "serialize_felt", + deserialize_with = "deserialize_felt" + )] + pub coefficient: Fr, + pub sum: acvm::Witness, +} + impl RawR1CS { pub fn new( acir: acvm::Circuit, values: Vec, ) -> Result { - let num_constraints = Self::num_constraints(&acir)?; + let num_constraints: u64 = num_constraints(&acir)? + .try_into() + .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?; // Currently non-arithmetic gates are not supported // so we extract all of the arithmetic gates only let mut gates = Vec::new(); @@ -51,48 +89,32 @@ impl RawR1CS { Ok(Self { gates, values, - num_variables: (acir.current_witness_index + 1) - .try_into() - .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?, - public_inputs: acir.public_inputs, + num_variables: u64::from(acir.current_witness_index) + 1, + public_inputs: acir.public_inputs.0.into_iter().collect(), num_constraints, }) } - - pub fn num_constraints(acir: &acvm::Circuit) -> Result { - // each multiplication term adds an extra constraint - let mut num_opcodes = acir.opcodes.len(); - - for opcode in acir.opcodes.iter() { - match opcode { - acvm::Opcode::Arithmetic(arith) => num_opcodes += arith.num_mul_terms() + 1, // plus one for the linear combination gate - acvm::Opcode::Directive(_) => (), - _ => { - return Err(GnarkBackendError::UnsupportedOpcodeError( - opcode.to_string(), - )) - } - } - } - - Ok(num_opcodes) - } } impl RawGate { pub fn new(arithmetic_gate: acvm::Expression) -> Self { - let converted_mul_terms: Vec<_> = arithmetic_gate + let converted_mul_terms: Vec = arithmetic_gate .mul_terms .into_iter() - .map(|(coefficient, multiplicand, multiplier)| { - (from_felt(coefficient), multiplicand, multiplier) + .map(|(coefficient, multiplicand, multiplier)| MulTerm { + coefficient: from_felt(coefficient), + multiplicand, + multiplier, }) .collect(); let converted_linear_combinations: Vec<_> = arithmetic_gate .linear_combinations .into_iter() - .map(|(coefficient, sum)| (from_felt(coefficient), sum)) + .map(|(coefficient, sum)| AddTerm { + coefficient: from_felt(coefficient), + sum, + }) .collect(); Self { @@ -102,3 +124,57 @@ impl RawGate { } } } + +impl Copy for MulTerm {} +impl Copy for AddTerm {} + +impl std::fmt::Debug for MulTerm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Coefficient: {:?}", self.coefficient.0 .0)?; + writeln!(f, "Multiplicand: {:?}", self.multiplicand.0)?; + writeln!(f, "Multiplier: {:?}", self.multiplier.0)?; + writeln!(f) + } +} + +impl std::fmt::Debug for AddTerm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Coefficient: {:?}", self.coefficient.0 .0)?; + writeln!(f, "Sum: {:?}", self.sum.0)?; + writeln!(f) + } +} + +impl std::fmt::Debug for RawGate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.mul_terms.fmt(f)?; + self.add_terms.fmt(f)?; + writeln!(f, "Constant term: {:?}", self.constant_term.0 .0)?; + writeln!(f) + } +} + +impl std::fmt::Debug for RawR1CS { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.gates.fmt(f)?; + writeln!( + f, + "Public Inputs: {:?}", + self.public_inputs + .iter() + .map(|public_input| public_input.0) + .collect::>() + )?; + writeln!( + f, + "Values: {:?}", + self.values + .iter() + .map(|value| value.0 .0) + .collect::>() + )?; + writeln!(f, "Number of variables: {}", self.num_variables)?; + writeln!(f, "Number of constraints: {}", self.num_constraints)?; + writeln!(f) + } +} diff --git a/src/gnark_backend_wrapper/groth16/mod.rs b/src/gnark_backend_wrapper/groth16/mod.rs index 5853b32..0c13ea4 100644 --- a/src/gnark_backend_wrapper/groth16/mod.rs +++ b/src/gnark_backend_wrapper/groth16/mod.rs @@ -1,40 +1,17 @@ +use crate::acvm; +use std::collections::BTreeMap; use std::ffi::{CStr, CString}; use std::num::TryFromIntError; use std::os::raw::{c_char, c_uchar}; -use acvm::{acir::circuit::Circuit, FieldElement}; mod acir_to_r1cs; -mod c_go_structures; -mod errors; + mod serialize; -use crate::gnark_backend_wrapper::groth16::acir_to_r1cs::RawR1CS; -use crate::gnark_backend_wrapper::groth16::c_go_structures::GoString; -use crate::gnark_backend_wrapper::groth16::c_go_structures::KeyPair; -use crate::gnark_backend_wrapper::groth16::errors::GnarkBackendError; - -// Arkworks's types are generic for `Field` but Noir's types are concrete and -// its value depends on the feature flag. -cfg_if::cfg_if! { - if #[cfg(feature = "bn254")] { - pub use ark_bn254::{Bn254 as Curve, Fr}; - - // Converts a FieldElement to a Fr - // noir_field uses arkworks for bn254 - pub fn from_felt(felt: acvm::FieldElement) -> Fr { - felt.into_repr() - } - } else if #[cfg(feature = "bls12_381")] { - pub use ark_bls12_381::{Bls12_381 as Curve, Fr}; - - // Converts a FieldElement to a Fr - // noir_field uses arkworks for bls12_381 - pub fn from_felt(felt: FieldElement) -> Fr { - felt.into_repr() - } - } else { - compile_error!("please specify a field to compile with"); - } -} +use crate::gnark_backend_wrapper::c_go_structures::{GoString, KeyPair}; +use crate::gnark_backend_wrapper::errors::GnarkBackendError; +pub use crate::gnark_backend_wrapper::groth16::acir_to_r1cs::{AddTerm, MulTerm, RawGate, RawR1CS}; +use crate::gnark_backend_wrapper::num_constraints; +use crate::Gnark; extern "C" { fn VerifyWithMeta(rawr1cs: GoString, proof: GoString) -> c_uchar; @@ -45,8 +22,8 @@ extern "C" { } pub fn prove_with_meta( - circuit: Circuit, - values: Vec, + circuit: acvm::Circuit, + values: Vec, ) -> Result, GnarkBackendError> { let rawr1cs = RawR1CS::new(circuit, values)?; @@ -68,8 +45,8 @@ pub fn prove_with_meta( } pub fn prove_with_pk( - circuit: &Circuit, - values: Vec, + circuit: &acvm::Circuit, + values: Vec, proving_key: &[u8], ) -> Result, GnarkBackendError> { let rawr1cs = RawR1CS::new(circuit.clone(), values)?; @@ -81,27 +58,27 @@ pub fn prove_with_pk( .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; let rawr1cs_go_string = GoString::try_from(&rawr1cs_c_str)?; - let proving_key_serialized = String::from_utf8(proving_key.to_vec()) - .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; + let proving_key_serialized = hex::encode(proving_key); let proving_key_c_str = CString::new(proving_key_serialized) .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; let proving_key_go_string = GoString::try_from(&proving_key_c_str) .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; - let result: *const c_char = unsafe { ProveWithPK(rawr1cs_go_string, proving_key_go_string) }; - let c_str = unsafe { CStr::from_ptr(result) }; - let bytes = c_str + let proof: *const c_char = unsafe { ProveWithPK(rawr1cs_go_string, proving_key_go_string) }; + let proof_c_str = unsafe { CStr::from_ptr(proof) }; + let proof_str = proof_c_str .to_str() - .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))? - .as_bytes(); + .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))?; + let decoded_proof = hex::decode(proof_str) + .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))?; - Ok(bytes.to_vec()) + Ok(decoded_proof) } pub fn verify_with_meta( - circuit: Circuit, + circuit: acvm::Circuit, proof: &[u8], - public_inputs: &[FieldElement], + public_inputs: &[acvm::FieldElement], ) -> Result { let rawr1cs = RawR1CS::new(circuit, public_inputs.to_vec())?; @@ -127,9 +104,9 @@ pub fn verify_with_meta( } pub fn verify_with_vk( - circuit: &Circuit, + circuit: &acvm::Circuit, proof: &[u8], - public_inputs: &[FieldElement], + public_inputs: &[acvm::FieldElement], verifying_key: &[u8], ) -> Result { let rawr1cs = RawR1CS::new(circuit.clone(), public_inputs.to_vec())?; @@ -141,57 +118,65 @@ pub fn verify_with_vk( .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; let rawr1cs_go_string = GoString::try_from(&rawr1cs_c_str)?; - let proof_serialized = String::from_utf8(proof.to_vec()) - .map_err(|e| GnarkBackendError::SerializeProofError(e.to_string()))?; + let proof_serialized = hex::encode(proof); let proof_c_str = CString::new(proof_serialized) .map_err(|e| GnarkBackendError::SerializeProofError(e.to_string()))?; let proof_go_string = GoString::try_from(&proof_c_str)?; - let verifying_key_serialized = String::from_utf8(verifying_key.to_vec()) - .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; + let verifying_key_serialized = hex::encode(verifying_key); let verifying_key_c_str = CString::new(verifying_key_serialized) .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; let verifying_key_go_string = GoString::try_from(&verifying_key_c_str)?; - let result = + let verifies = unsafe { VerifyWithVK(rawr1cs_go_string, proof_go_string, verifying_key_go_string) }; - match result { + match verifies { 0 => Ok(false), 1 => Ok(true), _ => Err(GnarkBackendError::VerifyInvalidBoolError), } } -pub fn get_exact_circuit_size(circuit: &Circuit) -> Result { - let size: u32 = RawR1CS::num_constraints(circuit)? +pub fn get_exact_circuit_size(circuit: &acvm::Circuit) -> Result { + let size: u32 = num_constraints(circuit)? .try_into() .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?; Ok(size) } -pub fn preprocess(circuit: &Circuit) -> Result<(Vec, Vec), GnarkBackendError> { +pub fn preprocess(circuit: &acvm::Circuit) -> Result<(Vec, Vec), GnarkBackendError> { + let num_witnesses: usize = circuit + .num_vars() + .try_into() + .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?; + let values = vec![acvm::FieldElement::from(rand::random::()); num_witnesses - 1]; + + let rawr1cs = RawR1CS::new(circuit.clone(), values)?; + // Serialize to json and then convert to GoString - let circuit_json = serde_json::to_string(circuit) + let rawr1cs_json = serde_json::to_string(&rawr1cs) .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; - let circuit_c_str = CString::new(circuit_json) + let rawr1cs_c_str = CString::new(rawr1cs_json) .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; - let circuit_go_string = GoString::try_from(&circuit_c_str)?; + let rawr1cs_go_string = GoString::try_from(&rawr1cs_c_str)?; - let key_pair: KeyPair = unsafe { Preprocess(circuit_go_string) }; + let key_pair: KeyPair = unsafe { Preprocess(rawr1cs_go_string) }; let proving_key_c_str = unsafe { CStr::from_ptr(key_pair.proving_key) }; - let proving_key_bytes = proving_key_c_str + let proving_key_str = proving_key_c_str .to_str() - .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))? - .as_bytes(); + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + let decoded_proving_key = hex::decode(proving_key_str) + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; let verifying_key_c_str = unsafe { CStr::from_ptr(key_pair.verifying_key) }; - let verifying_key_bytes = verifying_key_c_str + let verifying_key_str = verifying_key_c_str .to_str() - .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))? - .as_bytes(); + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + let decoded_verifying_key = hex::decode(verifying_key_str) + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; - Ok((proving_key_bytes.to_vec(), verifying_key_bytes.to_vec())) + Ok((decoded_proving_key, decoded_verifying_key)) } #[cfg(test)] @@ -200,17 +185,17 @@ mod tests { #[test] fn test_go_string_from_cstring() { - let string = "This works".to_string(); + let string = "This works".to_owned(); let c_str = CString::new(string.clone()).unwrap(); let go_string = GoString::try_from(&c_str).unwrap(); let deserialized_c_str = unsafe { CStr::from_ptr(go_string.ptr) }; - let deserialized_string = deserialized_c_str.to_str().unwrap().to_string(); + let deserialized_string = deserialized_c_str.to_str().unwrap().to_owned(); assert_eq!(string, deserialized_string); } #[test] fn get_exact_circuit_size_should_return_zero_with_an_empty_circuit() { - let size = get_exact_circuit_size(&Circuit::default()).unwrap(); + let size = get_exact_circuit_size(&acvm::Circuit::default()).unwrap(); assert_eq!(size, 0); } } diff --git a/src/gnark_backend_wrapper/groth16/serialize.rs b/src/gnark_backend_wrapper/groth16/serialize.rs index da7a0d7..07511d2 100644 --- a/src/gnark_backend_wrapper/groth16/serialize.rs +++ b/src/gnark_backend_wrapper/groth16/serialize.rs @@ -1,73 +1,91 @@ -use super::acir_to_r1cs::{RawGate, RawR1CS}; -use acvm::acir::native_types::Witness; -use serde::{ser::SerializeStruct, Serialize}; +use crate::gnark_backend_wrapper as gnark_backend; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use serde::Deserialize; -impl Serialize for RawR1CS { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("RawR1CS", 4)?; +pub fn serialize_felt_unchecked(felt: &gnark_backend::Fr) -> Vec { + let mut serialized_felt = Vec::new(); + #[allow(clippy::unwrap_used)] + felt.serialize_uncompressed(&mut serialized_felt).unwrap(); + // Turn little-endian to big-endian. + serialized_felt.reverse(); + serialized_felt +} + +pub fn serialize_felt(felt: &gnark_backend::Fr, serializer: S) -> Result +where + S: serde::ser::Serializer, +{ + let mut serialized_felt = Vec::new(); + felt.serialize_uncompressed(&mut serialized_felt) + .map_err(serde::ser::Error::custom)?; + // Turn little-endian to big-endian. + serialized_felt.reverse(); + let encoded_coefficient = hex::encode(serialized_felt); + serializer.serialize_str(&encoded_coefficient) +} - let mut serializable_values: Vec = Vec::new(); - for value in &self.values { - let mut serialized_value = Vec::new(); - ark_serialize::CanonicalSerialize::serialize_uncompressed(value, &mut serialized_value) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; - serializable_values.push(hex::encode(serialized_value)); - } +pub fn serialize_felts(felts: &[gnark_backend::Fr], serializer: S) -> Result +where + S: serde::ser::Serializer, +{ + let mut buff: Vec = Vec::new(); + let n_felts: u32 = felts.len().try_into().map_err(serde::ser::Error::custom)?; + buff.extend_from_slice(&n_felts.to_be_bytes()); + buff.extend_from_slice( + &felts + .iter() + .flat_map(serialize_felt_unchecked) + .collect::>(), + ); + let encoded_buff = hex::encode(buff); + serializer.serialize_str(&encoded_buff) +} - s.serialize_field("gates", &self.gates)?; - s.serialize_field("public_inputs", &self.public_inputs)?; - s.serialize_field("values", &serializable_values)?; - s.serialize_field("num_variables", &self.num_variables)?; - s.end() - } +pub fn deserialize_felt<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let felt_bytes = String::deserialize(deserializer)?; + let mut decoded = hex::decode(felt_bytes).map_err(serde::de::Error::custom)?; + // Turn big-endian to little-endian. + decoded.reverse(); + gnark_backend::Fr::deserialize_uncompressed(decoded.as_slice()) + .map_err(serde::de::Error::custom) } -impl Serialize for RawGate { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("RawGate", 3)?; +pub fn deserialize_felts<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let serialized_felts = String::deserialize(deserializer)?; - let mut serializable_mul_terms: Vec<(String, Witness, Witness)> = Vec::new(); - for (coefficient, multiplier, multiplicand) in &self.mul_terms { - let mut serialized_coefficient = Vec::new(); - ark_serialize::CanonicalSerialize::serialize_uncompressed( - coefficient, - &mut serialized_coefficient, - ) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; - serializable_mul_terms.push(( - hex::encode(serialized_coefficient), - *multiplicand, - *multiplier, - )); - } + let mut decoded_felts = hex::decode(serialized_felts).map_err(serde::de::Error::custom)?; - let mut serializable_add_terms: Vec<(String, Witness)> = Vec::new(); - for (coefficient, sum) in &self.add_terms { - let mut serialized_coefficient = Vec::new(); - ark_serialize::CanonicalSerialize::serialize_uncompressed( - coefficient, - &mut serialized_coefficient, - ) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; - serializable_add_terms.push((hex::encode(serialized_coefficient), *sum)); - } + let n_felts: usize = u32::from_be_bytes( + decoded_felts + .get(..4) + .ok_or_else(|| serde::de::Error::custom("Error getting felts size"))? + .try_into() + .map_err(serde::de::Error::custom)?, + ) + .try_into() + .map_err(serde::de::Error::custom)?; + let mut deserialized_felts: Vec = Vec::with_capacity(n_felts); - let mut serializable_constant_term = Vec::new(); - ark_serialize::CanonicalSerialize::serialize_uncompressed( - &self.constant_term, - &mut serializable_constant_term, - ) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; + decoded_felts + .get_mut(4..) // Skip the vector length corresponding to the first four bytes. + .ok_or_else(|| serde::de::Error::custom("Error getting decoded felts"))? + .chunks_mut(32) + .try_for_each(|decoded_felt| { + // Turn big-endian to little-endian. + decoded_felt.reverse(); + // Here I reference after dereference because I had a mutable reference and I need a non-mutable one. + let felt: gnark_backend::Fr = + CanonicalDeserialize::deserialize_uncompressed(&*decoded_felt) + .map_err(serde::de::Error::custom)?; + deserialized_felts.push(felt); + Ok::<(), D::Error>(()) + })?; - s.serialize_field("mul_terms", &serializable_add_terms)?; - s.serialize_field("add_terms", &serializable_add_terms)?; - s.serialize_field("constant_term", &hex::encode(serializable_constant_term))?; - s.end() - } + Ok(deserialized_felts) } diff --git a/src/gnark_backend_wrapper/mod.rs b/src/gnark_backend_wrapper/mod.rs index 786d6e2..5e939a3 100644 --- a/src/gnark_backend_wrapper/mod.rs +++ b/src/gnark_backend_wrapper/mod.rs @@ -1,11 +1,69 @@ +mod errors; +pub use errors::GnarkBackendError; +mod c_go_structures; +use crate::acvm; +pub use c_go_structures::{GoString, KeyPair}; + +// Arkworks's types are generic for `Field` but Noir's types are concrete and +// its value depends on the feature flag. +cfg_if::cfg_if! { + if #[cfg(feature = "bn254")] { + pub use ark_bn254::{Bn254 as Curve, Fr}; + + // Converts a FieldElement to a Fr + // noir_field uses arkworks for bn254 + pub fn from_felt(felt: acvm::FieldElement) -> Fr { + felt.into_repr() + } + } else if #[cfg(feature = "bls12_381")] { + pub use ark_bls12_381::{Bls12_381 as Curve, Fr}; + + // Converts a FieldElement to a Fr + // noir_field uses arkworks for bls12_381 + pub fn from_felt(felt: FieldElement) -> Fr { + felt.into_repr() + } + } else { + compile_error!("please specify a field to compile with"); + } +} + cfg_if::cfg_if! { if #[cfg(feature = "groth16")] { mod groth16; + pub use groth16::{AddTerm, MulTerm, RawGate, RawR1CS}; pub use groth16::verify_with_meta; pub use groth16::prove_with_meta; pub use groth16::verify_with_vk; pub use groth16::prove_with_pk; pub use groth16::get_exact_circuit_size; pub use groth16::preprocess; + } else if #[cfg(feature = "plonk")] { + mod plonk; + pub use plonk::verify_with_meta; + pub use plonk::prove_with_meta; + pub use plonk::verify_with_vk; + pub use plonk::prove_with_pk; + pub use plonk::get_exact_circuit_size; + pub use plonk::preprocess; + } +} + +pub fn num_constraints(acir: &acvm::Circuit) -> Result { + // each multiplication term adds an extra constraint + let mut num_opcodes = acir.opcodes.len(); + + for opcode in acir.opcodes.iter() { + match opcode { + acvm::Opcode::Arithmetic(arith) => num_opcodes += arith.num_mul_terms() + 1, // plus one for the linear combination gate + acvm::Opcode::Directive(_) => (), + _ => { + return Err(GnarkBackendError::UnsupportedOpcodeError( + opcode.to_string(), + )) + } + } } + + Ok(num_opcodes) } diff --git a/src/gnark_backend_wrapper/plonk/mod.rs b/src/gnark_backend_wrapper/plonk/mod.rs new file mode 100644 index 0000000..dd91a8b --- /dev/null +++ b/src/gnark_backend_wrapper/plonk/mod.rs @@ -0,0 +1,228 @@ +use crate::acvm; +use std::collections::BTreeMap; +use std::ffi::{CStr, CString}; +use std::num::TryFromIntError; +use std::os::raw::{c_char, c_uchar}; + +use crate::gnark_backend_wrapper::c_go_structures::{GoString, KeyPair}; +use crate::gnark_backend_wrapper::errors::GnarkBackendError; +use crate::Gnark; + +extern "C" { + fn PlonkVerifyWithMeta(circuit: GoString, values: GoString, proof: GoString) -> c_uchar; + fn PlonkProveWithMeta(circuit: GoString, values: GoString) -> *const c_char; + fn PlonkVerifyWithVK(rawr1cs: GoString, proof: GoString, verifying_key: GoString) -> c_uchar; + fn PlonkProveWithPK( + circuit: GoString, + values: GoString, + proving_key: GoString, + ) -> *const c_char; + fn PlonkPreprocess(circuit: GoString, values: GoString) -> KeyPair; +} + +pub fn prove_with_meta( + circuit: acvm::Circuit, + values: Vec, +) -> Result, GnarkBackendError> { + // Serialize to json and then convert to GoString + let acir_json = serde_json::to_string(&circuit) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_c_str = CString::new(acir_json) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_go_string = GoString::try_from(&acir_c_str)?; + + let felts = values.iter().map(from_felt).collect(); + let felts_serialized = serialize_felts(felts).to_string(); + let felts_c_str = CString::new(felts_serialized) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let values_go_string = GoString::try_from(&felts_c_str)?; + + let result: *const c_char = unsafe { PlonkProveWithMeta(acir_go_string, values_go_string) }; + let c_str = unsafe { CStr::from_ptr(result) }; + let bytes = c_str + .to_str() + .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))? + .as_bytes(); + + Ok(bytes.to_vec()) +} + +pub fn prove_with_pk( + circuit: &acvm::Circuit, + values: Vec, + proving_key: &[u8], +) -> Result, GnarkBackendError> { + // Serialize to json and then convert to GoString + let acir_json = serde_json::to_string(&circuit) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_c_str = CString::new(acir_json) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_go_string = GoString::try_from(&acir_c_str)?; + + let felts = values.iter().map(from_felt).collect(); + let felts_serialized = serialize_felts(felts).to_string(); + let felts_c_str = CString::new(felts_serialized) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let values_go_string = GoString::try_from(&felts_c_str)?; + + let proving_key_serialized = hex::encode(proving_key); + let proving_key_c_str = CString::new(proving_key_serialized) + .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; + let proving_key_go_string = GoString::try_from(&proving_key_c_str) + .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; + + let proof: *const c_char = + unsafe { PlonkProveWithPK(circuit_go_string, values_go_string, proving_key_go_string) }; + let proof_c_str = unsafe { CStr::from_ptr(proof) }; + let proof_str = proof_c_str + .to_str() + .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))?; + let decoded_proof = hex::decode(proof_str) + .map_err(|e| GnarkBackendError::DeserializeProofError(e.to_string()))?; + + Ok(decoded_proof) +} + +pub fn verify_with_meta( + circuit: acvm::Circuit, + proof: &[u8], + public_inputs: &[acvm::FieldElement], +) -> Result { + // Serialize to json and then convert to GoString + let acir_json = serde_json::to_string(&circuit) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_c_str = CString::new(acir_json) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_go_string = GoString::try_from(&acir_c_str)?; + + let felts = values.iter().map(from_felt).collect(); + let felts_serialized = serialize_felts(felts).to_string(); + let felts_c_str = CString::new(felts_serialized) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let values_go_string = GoString::try_from(&felts_c_str)?; + + let serialized_proof = String::from_utf8(proof.to_vec()) + .map_err(|e| GnarkBackendError::SerializeProofError(e.to_string()))?; + let c_str = CString::new(serialized_proof) + .map_err(|e| GnarkBackendError::SerializeProofError(e.to_string()))?; + let go_string_proof = GoString::try_from(&c_str)?; + + let result = + unsafe { PlonkVerifyWithMeta(circuit_go_string, values_go_string, go_string_proof) }; + match result { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(GnarkBackendError::VerifyInvalidBoolError), + } +} + +pub fn verify_with_vk( + circuit: &acvm::Circuit, + proof: &[u8], + public_inputs: &[acvm::FieldElement], + verifying_key: &[u8], +) -> Result { + // Serialize to json and then convert to GoString + let acir_json = serde_json::to_string(&circuit) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_c_str = CString::new(acir_json) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_go_string = GoString::try_from(&acir_c_str)?; + + let felts = values.iter().map(from_felt).collect(); + let felts_serialized = serialize_felts(felts).to_string(); + let felts_c_str = CString::new(felts_serialized) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let values_go_string = GoString::try_from(&felts_c_str)?; + + let proof_serialized = hex::encode(proof); + let proof_c_str = CString::new(proof_serialized) + .map_err(|e| GnarkBackendError::SerializeProofError(e.to_string()))?; + let proof_go_string = GoString::try_from(&proof_c_str)?; + + let verifying_key_serialized = hex::encode(verifying_key); + let verifying_key_c_str = CString::new(verifying_key_serialized) + .map_err(|e| GnarkBackendError::SerializeKeyError(e.to_string()))?; + let verifying_key_go_string = GoString::try_from(&verifying_key_c_str)?; + + let verifies = unsafe { + PlonkVerifyWithVK( + circuit_go_string, + values_go_string, + proof_go_string, + verifying_key_go_string, + ) + }; + match verifies { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(GnarkBackendError::VerifyInvalidBoolError), + } +} + +pub fn get_exact_circuit_size(circuit: &acvm::Circuit) -> Result { + let size: u32 = num_constraints(circuit)? + .try_into() + .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?; + Ok(size) +} + +pub fn preprocess(circuit: &acvm::Circuit) -> Result<(Vec, Vec), GnarkBackendError> { + let num_witnesses: usize = circuit + .num_vars() + .try_into() + .map_err(|e: TryFromIntError| GnarkBackendError::Error(e.to_string()))?; + let values = vec![acvm::FieldElement::from(rand::random::()); num_witnesses - 1]; + + // Serialize to json and then convert to GoString + let acir_json = serde_json::to_string(&circuit) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_c_str = CString::new(acir_json) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let acir_go_string = GoString::try_from(&acir_c_str)?; + + let felts = values.iter().map(from_felt).collect(); + let felts_serialized = serialize_felts(felts).to_string(); + let felts_c_str = CString::new(felts_serialized) + .map_err(|e| GnarkBackendError::SerializeCircuitError(e.to_string()))?; + let values_go_string = GoString::try_from(&felts_c_str)?; + + let key_pair: KeyPair = unsafe { PlonkPreprocess(circuit_go_string, values_go_string) }; + + let proving_key_c_str = unsafe { CStr::from_ptr(key_pair.proving_key) }; + let proving_key_str = proving_key_c_str + .to_str() + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + let decoded_proving_key = hex::decode(proving_key_str) + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + + let verifying_key_c_str = unsafe { CStr::from_ptr(key_pair.verifying_key) }; + let verifying_key_str = verifying_key_c_str + .to_str() + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + let decoded_verifying_key = hex::decode(verifying_key_str) + .map_err(|e| GnarkBackendError::DeserializeKeyError(e.to_string()))?; + + Ok((decoded_proving_key, decoded_verifying_key)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_go_string_from_cstring() { + let string = "This works".to_owned(); + let c_str = CString::new(string.clone()).unwrap(); + let go_string = GoString::try_from(&c_str).unwrap(); + let deserialized_c_str = unsafe { CStr::from_ptr(go_string.ptr) }; + let deserialized_string = deserialized_c_str.to_str().unwrap().to_owned(); + assert_eq!(string, deserialized_string); + } + + #[test] + fn get_exact_circuit_size_should_return_zero_with_an_empty_circuit() { + let size = get_exact_circuit_size(&acvm::Circuit::default()).unwrap(); + assert_eq!(size, 0); + } +} diff --git a/src/lib.rs b/src/lib.rs index 2d30169..6745ef8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,9 +31,11 @@ clippy::let_underscore_must_use )] -mod acvm; +pub mod acvm; -pub mod acvm_interop; -pub use acvm_interop::Gnark; +pub mod backend; +pub use backend::Gnark; -mod gnark_backend_wrapper; +// TODO: This is exposed for testing only, we should find a way to not expose it for +// the users. +pub mod gnark_backend_wrapper; diff --git a/tests/serialization_tests.rs b/tests/serialization_tests.rs new file mode 100644 index 0000000..43f86f6 --- /dev/null +++ b/tests/serialization_tests.rs @@ -0,0 +1,474 @@ +use ::acvm::acir::circuit::directives::Directive; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use noir_backend_using_gnark::{ + acvm::{self, Circuit, Expression, Opcode, PublicInputs}, + gnark_backend_wrapper::{self, AddTerm, MulTerm, RawGate, RawR1CS}, +}; +use std::collections::BTreeSet; +use std::ffi; + +extern "C" { + fn IntegrationTestFeltSerialization( + felt: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestFeltsSerialization( + felts: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestU64Serialization(unsigned_integer: ffi::c_ulong) -> ffi::c_ulong; + fn IntegrationTestMulTermSerialization( + mul_term: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestMulTermsSerialization( + mul_terms: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestAddTermSerialization( + add_term: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestAddTermsSerialization( + add_terms: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestRawGateSerialization( + raw_gate: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestRawGatesSerialization( + raw_gates: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestRawR1CSSerialization( + raw_r1cs: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; + fn IntegrationTestCircuitSerialization( + circuit: gnark_backend_wrapper::GoString, + ) -> *const ffi::c_char; +} + +fn serialize_felt(felt: &gnark_backend_wrapper::Fr) -> Vec { + let mut serialized_felt = Vec::new(); + felt.serialize_uncompressed(&mut serialized_felt).unwrap(); + // Turn little-endian to big-endian. + serialized_felt.reverse(); + serialized_felt +} + +fn deserialize_felt(felt_bytes: &[u8]) -> gnark_backend_wrapper::Fr { + let mut decoded = hex::decode(felt_bytes).unwrap(); + // Turn big-endian to little-endian. + decoded.reverse(); + gnark_backend_wrapper::Fr::deserialize_uncompressed(decoded.as_slice()).unwrap() +} + +// This serialization mimics gnark's serialization of a field elements vector. +// The length of the vector is encoded as a u32 on the first 4 bytes. +fn serialize_felts(felts: &[gnark_backend_wrapper::Fr]) -> Vec { + let mut buff: Vec = Vec::new(); + let n_felts: u32 = felts.len().try_into().unwrap(); + buff.extend_from_slice(&n_felts.to_be_bytes()); + buff.extend_from_slice(&felts.iter().flat_map(serialize_felt).collect::>()); + buff +} + +#[test] +fn test_felt_serialization() { + // Sample a random felt. + let felt: gnark_backend_wrapper::Fr = rand::random(); + + println!("| RUST |\n{:?}", felt.0 .0); + + // Serialize the random felt. + let serialized_felt = serialize_felt(&felt); + + // Encode the felt. + let encoded_felt = hex::encode(serialized_felt); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(encoded_felt).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let pong: *const ffi::c_char = unsafe { IntegrationTestFeltSerialization(ping) }; + + // Prepare pong for Rust. + let go_pre_serialized_felt = unsafe { ffi::CStr::from_ptr(pong) }; + let go_serialized_felt = go_pre_serialized_felt.to_str().unwrap().as_bytes(); + + let go_felt = deserialize_felt(go_serialized_felt); + + assert_eq!(felt, go_felt) +} + +#[test] +fn test_felts_serialization() { + // Sample a random felt. + let felts: [gnark_backend_wrapper::Fr; 2] = rand::random(); + + println!( + "| RUST |\n{:?}", + felts.iter().map(|felt| felt.0 .0).collect::>() + ); + + // Serialize the random felts and pack them into one byte array. + let serialized_felts = serialize_felts(&felts); + + // Encode the packed felts. + let encoded_felts = hex::encode(serialized_felts); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(encoded_felts).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let pong: *const ffi::c_char = unsafe { IntegrationTestFeltsSerialization(ping) }; + + // Prepare pong for Rust. + let go_pre_serialized_felt = unsafe { ffi::CStr::from_ptr(pong) }; + let go_serialized_felt = go_pre_serialized_felt.to_str().unwrap().as_bytes(); + + // Decode and deserialize the unpacked felts. + let go_felts: Vec = hex::decode(go_serialized_felt).unwrap()[4..] // Skip the vector length corresponding to the first four bytes. + .chunks_mut(32) + .map(|go_decoded_felt| { + // Turn big-endian to little-endian. + go_decoded_felt.reverse(); + // Here I reference after dereference because I had a mutable reference and I need a non-mutable one. + let felt: gnark_backend_wrapper::Fr = + CanonicalDeserialize::deserialize_uncompressed(&*go_decoded_felt).unwrap(); + felt + }) + .collect(); + + assert_eq!(felts.to_vec(), go_felts) +} + +#[test] +fn test_u64_serialization() { + // Sample a random number. + let number: u64 = rand::random(); + + println!("| RUST |\n{:?}", number); + + // Prepare ping for Go. + let ping = number; + // Send and receive pong from Go. + let pong = unsafe { IntegrationTestU64Serialization(ping) }; + + assert_eq!(number, pong) +} + +#[test] +fn test_mul_term_serialization() { + // Sample random coefficient. + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + // Sample a random multiplicand. + let multiplicand = acvm::Witness::new(rand::random()); + // Sample a random multiplier. + let multiplier: acvm::Witness = acvm::Witness::new(rand::random()); + // Sample a random mul term. + let mul_term = MulTerm { + coefficient, + multiplicand, + multiplier, + }; + + println!("| RUST |"); + println!("{mul_term:?}"); + + // Serialize the mul term. + let serialized_mul_term = serde_json::to_string(&mul_term).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_mul_term).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestMulTermSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that mul_term and go_mul_term are the same (go_mul_term is + // the pong's deserialization) +} + +#[test] +fn test_mul_terms_serialization() { + // Sample random coefficient. + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + // Sample a random multiplicand. + let multiplicand = acvm::Witness::new(rand::random()); + // Sample a random multiplier. + let multiplier: acvm::Witness = acvm::Witness::new(rand::random()); + // Sample a random mul term. + let mul_terms = vec![ + MulTerm { + coefficient, + multiplicand, + multiplier, + }, + MulTerm { + coefficient, + multiplicand, + multiplier, + }, + ]; + + println!("| RUST |"); + println!("{mul_terms:?}"); + + // Serialize the mul term. + let serialized_mul_terms = serde_json::to_string(&mul_terms).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_mul_terms).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestMulTermsSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that mul_term and go_mul_term are the same (go_mul_term is + // the pong's deserialization) +} + +#[test] +fn test_add_term_serialization() { + // Sample random coefficient. + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + // Sample a random sum. + let sum = acvm::Witness::new(rand::random()); + // Sample a random mul term. + let add_term = AddTerm { coefficient, sum }; + + println!("| RUST |"); + println!("{add_term:?}"); + + // Serialize the mul term. + let serialized_add_term = serde_json::to_string(&add_term).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_add_term).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestAddTermSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_term and go_add_term are the same (go_add_term is + // the pong's deserialization) +} + +#[test] +fn test_add_terms_serialization() { + // Sample random coefficient. + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + // Sample a random sum. + let sum = acvm::Witness::new(rand::random()); + // Sample a random mul term. + let add_terms = vec![AddTerm { coefficient, sum }, AddTerm { coefficient, sum }]; + + println!("| RUST |"); + println!("{add_terms:?}"); + + // Serialize the mul term. + let serialized_add_terms = serde_json::to_string(&add_terms).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_add_terms).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestAddTermsSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_terms and go_add_terms are the same (go_add_terms is + // the pong's deserialization) +} + +#[test] +fn test_raw_gate_serialization() { + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + let multiplicand = acvm::Witness::new(rand::random()); + let multiplier: acvm::Witness = acvm::Witness::new(rand::random()); + let sum = acvm::Witness::new(rand::random()); + let add_term = AddTerm { coefficient, sum }; + let add_terms = vec![add_term, add_term]; + let mul_term = MulTerm { + coefficient, + multiplicand, + multiplier, + }; + let mul_terms = vec![mul_term, mul_term]; + let constant_term: gnark_backend_wrapper::Fr = rand::random(); + let raw_gate = RawGate { + mul_terms, + add_terms, + constant_term, + }; + + println!("| RUST |"); + println!("{raw_gate:?}"); + + // Serialize the raw gate. + let serialized_raw_gate = serde_json::to_string(&raw_gate).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_raw_gate).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestRawGateSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_terms and go_add_terms are the same (go_add_terms is + // the pong's deserialization) +} + +#[test] +fn test_raw_gates_serialization() { + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + let multiplicand = acvm::Witness::new(rand::random()); + let multiplier: acvm::Witness = acvm::Witness::new(rand::random()); + let sum = acvm::Witness::new(rand::random()); + let add_term = AddTerm { coefficient, sum }; + let add_terms = vec![add_term, add_term]; + let mul_term = MulTerm { + coefficient, + multiplicand, + multiplier, + }; + let mul_terms = vec![mul_term, mul_term]; + let constant_term: gnark_backend_wrapper::Fr = rand::random(); + let raw_gate = RawGate { + mul_terms, + add_terms, + constant_term, + }; + let raw_gates = vec![raw_gate.clone(), raw_gate]; + + println!("| RUST |"); + println!("{raw_gates:?}"); + + // Serialize the raw gate. + let serialized_raw_gates = serde_json::to_string(&raw_gates).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_raw_gates).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestRawGatesSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_terms and go_add_terms are the same (go_add_terms is + // the pong's deserialization) +} + +#[test] +fn test_raw_r1cs_serialization() { + let coefficient: gnark_backend_wrapper::Fr = rand::random(); + let multiplicand = acvm::Witness::new(rand::random()); + let multiplier: acvm::Witness = acvm::Witness::new(rand::random()); + let sum = acvm::Witness::new(rand::random()); + let add_term = AddTerm { coefficient, sum }; + let add_terms = vec![add_term, add_term]; + let mul_term = MulTerm { + coefficient, + multiplicand, + multiplier, + }; + let mul_terms = vec![mul_term, mul_term]; + let constant_term: gnark_backend_wrapper::Fr = rand::random(); + let raw_gate = RawGate { + mul_terms, + add_terms, + constant_term, + }; + let raw_gates = vec![raw_gate.clone(), raw_gate]; + let public_inputs = vec![ + acvm::Witness::new(rand::random()), + acvm::Witness::new(rand::random()), + ]; + let values: [gnark_backend_wrapper::Fr; 2] = rand::random(); + let num_constraints: u64 = rand::random(); + let num_variables: u64 = rand::random(); + let raw_r1cs = RawR1CS { + gates: raw_gates, + public_inputs, + values: values.to_vec(), + num_variables, + num_constraints, + }; + + println!("| RUST |"); + println!("{raw_r1cs:?}"); + + // Serialize the raw gate. + let serialized_raw_gates = serde_json::to_string(&raw_r1cs).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_raw_gates).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestRawR1CSSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_terms and go_add_terms are the same (go_add_terms is + // the pong's deserialization) +} + +#[test] +fn test_acir_serialization() { + let current_witness_index: u32 = rand::random(); + let mul_terms = vec![( + acvm::FieldElement::zero(), + acvm::Witness::new(rand::random()), + acvm::Witness::new(rand::random()), + )]; + let linear_combinations = vec![( + acvm::FieldElement::zero(), + acvm::Witness::new(rand::random()), + )]; + let q_c = acvm::FieldElement::zero(); + let arithmetic_expression = Expression { + mul_terms, + linear_combinations, + q_c, + }; + let arithmetic_opcode = Opcode::Arithmetic(arithmetic_expression); + let directive = Directive::Invert { + x: acvm::Witness::new(rand::random()), + result: acvm::Witness::new(rand::random()), + }; + let directive_opcode = Opcode::Directive(directive); + + let opcodes = vec![arithmetic_opcode, directive_opcode]; + let mut public_inputs_tree = BTreeSet::new(); + public_inputs_tree.insert(acvm::Witness::new(rand::random())); + public_inputs_tree.insert(acvm::Witness::new(rand::random())); + let public_inputs = PublicInputs(public_inputs_tree); + + let acir = Circuit { + current_witness_index, + opcodes, + public_inputs, + }; + + // Serialize the raw gate. + let serialized_acir = serde_json::to_string(&acir).unwrap(); + + // Prepare ping for Go. + let pre_ping = ffi::CString::new(serialized_acir).unwrap(); + let ping = gnark_backend_wrapper::GoString::try_from(&pre_ping).unwrap(); + + // Send and receive pong from Go. + let _pong: *const ffi::c_char = unsafe { IntegrationTestCircuitSerialization(ping) }; + + // TODO: + // * Prepare pong for Rust. + // * Assert that add_terms and go_add_terms are the same (go_add_terms is + // the pong's deserialization) +} diff --git a/tests/test_programs/priv_x_eq_pub_y/Nargo.toml b/tests/test_programs/priv_x_eq_pub_y/Nargo.toml new file mode 100644 index 0000000..e0b467c --- /dev/null +++ b/tests/test_programs/priv_x_eq_pub_y/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] \ No newline at end of file diff --git a/tests/test_programs/priv_x_eq_pub_y/Prover.toml b/tests/test_programs/priv_x_eq_pub_y/Prover.toml new file mode 100644 index 0000000..b38bd4e --- /dev/null +++ b/tests/test_programs/priv_x_eq_pub_y/Prover.toml @@ -0,0 +1,2 @@ +x = 10 +y = 10 diff --git a/tests/test_programs/priv_x_eq_pub_y/Verifier.toml b/tests/test_programs/priv_x_eq_pub_y/Verifier.toml new file mode 100644 index 0000000..13c53d7 --- /dev/null +++ b/tests/test_programs/priv_x_eq_pub_y/Verifier.toml @@ -0,0 +1 @@ +y = "0x000000000000000000000000000000000000000000000000000000000000000a" diff --git a/tests/test_programs/priv_x_eq_pub_y/src/main.nr b/tests/test_programs/priv_x_eq_pub_y/src/main.nr new file mode 100644 index 0000000..3bd6ddb --- /dev/null +++ b/tests/test_programs/priv_x_eq_pub_y/src/main.nr @@ -0,0 +1,3 @@ +fn main(x : Field, y : pub Field) { + constrain x == y; +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..a91874a --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,114 @@ +fn nargo_cmd() -> std::process::Command { + std::process::Command::new("nargo") +} + +fn nargo_execute(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("execute") + // .arg("[WITNESS_NAME]") + .output() +} + +fn nargo_test(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("test") + .output() +} + +fn nargo_check(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("check") + .output() +} + +fn nargo_gates(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("gates") + .output() +} + +fn nargo_compile(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("compile") + .arg("my_test_circuit") + .output() +} + +fn nargo_prove(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("prove") + .arg("my_test_proof") + .arg("my_test_circuit") + .output() +} + +fn nargo_verify(test_program_dir: &std::path::PathBuf) -> std::io::Result { + nargo_cmd() + .current_dir(test_program_dir) + .arg("verify") + .arg("my_test_proof") + .arg("my_test_circuit") + .output() +} + +fn test_program_dir_path(dir_name: &str) -> std::path::PathBuf { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join(format!("tests/test_programs/{dir_name}")) +} + +fn assert_nargo_cmd_works(cmd_name: &str, test_test_program_dir: &std::path::PathBuf) { + let cmd_output = match cmd_name { + "check" => nargo_check(test_test_program_dir), + "contract" => todo!(), + "compile" => nargo_compile(test_test_program_dir), + "new" => panic!("This cmd doesn't depend on the backend"), + "execute" => nargo_execute(test_test_program_dir), + "prove" => nargo_prove(test_test_program_dir), + "verify" => nargo_verify(test_test_program_dir), + "test" => nargo_test(test_test_program_dir), + "gates" => nargo_gates(test_test_program_dir), + e => panic!("{e} is not a valid nargo cmd"), + } + .unwrap(); + + assert!( + cmd_output.status.success(), + "stderr(nargo {cmd_name}): {}", + String::from_utf8(cmd_output.stderr).unwrap() + ); +} + +fn install_nargo() { + std::process::Command::new("make") + .arg("-C") + .arg( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .to_str() + .unwrap(), + ) + .arg("nargo") + .output() + .unwrap(); +} + +#[test] +fn test_integration() { + let test_test_program_dir = test_program_dir_path("priv_x_eq_pub_y"); + + // Ensure our nargo's fork is being used here. + install_nargo(); + + assert_nargo_cmd_works("check", &test_test_program_dir); + assert_nargo_cmd_works("compile", &test_test_program_dir); + assert_nargo_cmd_works("execute", &test_test_program_dir); + assert_nargo_cmd_works("prove", &test_test_program_dir); + assert_nargo_cmd_works("verify", &test_test_program_dir); + assert_nargo_cmd_works("test", &test_test_program_dir); + assert_nargo_cmd_works("gates", &test_test_program_dir); +}