diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 37f2f6c..dcb2f6b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ Before we can merge this PR, please make sure that all the following items have checked off. If any of the checklist items are not applicable, please leave them but write a little note why. -- [ ] Targeted PR against correct branch (master) +- [ ] Targeted PR against correct branch (main) - [ ] Linked to Github issue with discussion and accepted design OR have an explanation in the PR that describes this work. - [ ] Wrote unit tests - [ ] Updated relevant documentation in the code diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6c2bcd..27d0909 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ All other branches should be assumed to be miscellaneous feature development bra All downstream users of the library should be using tagged versions of the library pulled from cargo. ## How to work on a fork -Please skip this section if you're familiar with contributing to opensource github projects. +Please skip this section if you're familiar with contributing to open source github projects. First fork the repo from the github UI, and clone it locally. Then in the repo, you want to add the repo you forked from as a new remote. You do this as: diff --git a/README.md b/README.md index 355cc16..113b039 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ First, checkout the `main` branch in the repository. ### Exercise 1: Merkle Tree Example We'll design a simple circuit for checking a Merkle tree membership path for a given leaf. -Open [`merkle-tree-example/README.md`](./merkle-tree-example/src/README.md). +Open [`merkle-tree-example/README.md`](./merkle-tree-example/README.md). ### Exercise 2: Validating a single transaction diff --git a/merkle-tree-example/Cargo.toml b/merkle-tree-example/Cargo.toml index 45c772a..8075780 100644 --- a/merkle-tree-example/Cargo.toml +++ b/merkle-tree-example/Cargo.toml @@ -1,22 +1,21 @@ [package] name = "merkle-tree-example" -version = "0.3.0" +version = "0.5.0" authors = ["arkworks contributors"] edition = "2018" [dependencies] -ark-ff = { version = "^0.3.0", default-features = false } -ark-ec = { version = "^0.3.0", default-features = false } -ark-ed-on-bls12-381 = { version = "^0.3.0", features = ["r1cs"] } -ark-bls12-381 = { version = "^0.3.0", default-features = false } -ark-std = { version = "^0.3.0", default-features = false } -ark-relations = { version = "^0.3.0", default-features = false } +ark-ff = { version = "^0.5.0", default-features = false } +ark-ec = { version = "^0.5.0", default-features = false } +ark-ed-on-bls12-381 = { version = "^0.5.0", features = ["r1cs"] } +ark-bls12-381 = { version = "^0.5.0", default-features = false } +ark-std = { version = "^0.5.0", default-features = false } +ark-relations = { version = "^0.5.0", default-features = false } -ark-r1cs-std = { version = "^0.3.0", default-features = false } -ark-snark = { version = "^0.3.0", default-features = false } +ark-r1cs-std = { version = "^0.5.0", default-features = false } +ark-snark = { version = "^0.5.0", default-features = false } -ark-serialize = { version = "^0.3.0", default-features = false } - -ark-crypto-primitives = { version = "^0.3.0", default-features = true, features = [ "r1cs" ] } +ark-serialize = { version = "^0.5.0", default-features = false } +ark-crypto-primitives = { version = "^0.5.0", default-features = true, features = [ "r1cs" , "crh", "merkle_tree"] } tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } tracing-subscriber = { version = "0.2" } diff --git a/merkle-tree-example/src/README.md b/merkle-tree-example/README.md similarity index 94% rename from merkle-tree-example/src/README.md rename to merkle-tree-example/README.md index 6f037dd..bee66d8 100644 --- a/merkle-tree-example/src/README.md +++ b/merkle-tree-example/README.md @@ -36,10 +36,10 @@ Let's go over this incantation part-by-part. We similarly allocate the leaf as a public input variable, and allocate the parameters of the hash as "constants" in the constraint system. This means that these parameters are "baked" into the constraint system when it is created, and changing these parameters would result in a different constraint system. Finally, we allocate the membership path as a private witness variable. -Now, we must fill in the blanks by addng constraints to check the membership path. Go ahead and follow the hint in `constraints.rs` to complete this task. +Now, we must fill in the blanks by adding constraints to check the membership path. Go ahead and follow the hint in `constraints.rs` to complete this task. ## Testing our constraints Once we've written our path-checking constraints, we have to check that the resulting constraint system satisfies two properties: that it accepts a valid membership path, and that it rejects an invalid path. We perform these checks via two tests: `merkle_tree_constraints_correctness` and `merkle_tree_constraints_soundness`. Go ahead and look at those for an example of how to test constraint systems in practice. -This wraps up this part of the tutorial. Go to the `simple_payments` folder for the next step! \ No newline at end of file +This wraps up this part of the tutorial. Go to the `simple_payments` folder for the next step! diff --git a/merkle-tree-example/src/common.rs b/merkle-tree-example/src/common.rs index 9e7c978..5278e75 100644 --- a/merkle-tree-example/src/common.rs +++ b/merkle-tree-example/src/common.rs @@ -1,14 +1,16 @@ -use ark_crypto_primitives::crh::constraints::{CRHGadget, TwoToOneCRHGadget}; use ark_crypto_primitives::crh::injective_map::constraints::{ - PedersenCRHCompressorGadget, TECompressorGadget, + PedersenCRHCompressorGadget, PedersenTwoToOneCRHCompressorGadget, TECompressorGadget, }; +use ark_crypto_primitives::crh::injective_map::PedersenTwoToOneCRHCompressor; use ark_crypto_primitives::crh::{ injective_map::{PedersenCRHCompressor, TECompressor}, pedersen, }; +use ark_crypto_primitives::crh::{CRHSchemeGadget, TwoToOneCRHSchemeGadget}; use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsProjective}; -pub type TwoToOneHash = PedersenCRHCompressor; +pub type TwoToOneHash = + PedersenTwoToOneCRHCompressor; #[derive(Clone, PartialEq, Eq, Hash)] pub struct TwoToOneWindow; @@ -19,7 +21,6 @@ impl pedersen::Window for TwoToOneWindow { } pub type LeafHash = PedersenCRHCompressor; - #[derive(Clone, PartialEq, Eq, Hash)] pub struct LeafWindow; @@ -29,7 +30,7 @@ impl pedersen::Window for LeafWindow { const NUM_WINDOWS: usize = 144; } -pub type TwoToOneHashGadget = PedersenCRHCompressorGadget< +pub type TwoToOneHashGadget = PedersenTwoToOneCRHCompressorGadget< EdwardsProjective, TECompressor, TwoToOneWindow, @@ -45,8 +46,9 @@ pub type LeafHashGadget = PedersenCRHCompressorGadget< TECompressorGadget, >; -pub type LeafHashParamsVar = >::ParametersVar; +pub type LeafHashParamsVar = + >::ParametersVar; pub type TwoToOneHashParamsVar = - >::ParametersVar; + >::ParametersVar; pub type ConstraintF = ark_ed_on_bls12_381::Fq; diff --git a/merkle-tree-example/src/constraints.rs b/merkle-tree-example/src/constraints.rs index 90a9ce9..4f6c180 100644 --- a/merkle-tree-example/src/constraints.rs +++ b/merkle-tree-example/src/constraints.rs @@ -1,6 +1,6 @@ -use crate::common::*; +use crate::{common::*, MerkleConfig, MerkleConfigVar}; use crate::{Root, SimplePath}; -use ark_crypto_primitives::crh::{TwoToOneCRH, TwoToOneCRHGadget, CRH}; +use ark_crypto_primitives::crh::{CRHScheme, TwoToOneCRHScheme, TwoToOneCRHSchemeGadget}; use ark_crypto_primitives::merkle_tree::constraints::PathVar; use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; @@ -9,18 +9,18 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisE // just know that these are types that you can use.) /// The R1CS equivalent of the the Merkle tree root. -pub type RootVar = >::OutputVar; +pub type RootVar = + >::OutputVar; /// The R1CS equivalent of the the Merkle tree path. -pub type SimplePathVar = - PathVar; +pub type SimplePathVar = PathVar; //////////////////////////////////////////////////////////////////////////////// pub struct MerkleTreeVerification { // These are constants that will be embedded into the circuit - pub leaf_crh_params: ::Parameters, - pub two_to_one_crh_params: ::Parameters, + pub leaf_crh_params: ::Parameters, + pub two_to_one_crh_params: ::Parameters, // These are the public inputs to the circuit. pub root: Root, @@ -55,8 +55,8 @@ impl ConstraintSynthesizer for MerkleTreeVerification { // Now, we have to check membership. How do we do that? // Hint: look at https://github.com/arkworks-rs/crypto-primitives/blob/6be606259eab0aec010015e2cfd45e4f134cd9bf/src/merkle_tree/constraints.rs#L135 - let is_member = // TODO: FILL IN THE BLANK! - path.verify_membership( + // TODO: FILL IN THE BLANK! + let is_member = path.verify_membership( &leaf_crh_params, &two_to_one_crh_params, &root, @@ -80,15 +80,24 @@ fn merkle_tree_constraints_correctness() { let mut rng = ark_std::test_rng(); // First, let's sample the public parameters for the hash functions: - let leaf_crh_params = ::setup(&mut rng).unwrap(); - let two_to_one_crh_params = ::setup(&mut rng).unwrap(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_crh_params = ::setup(&mut rng).unwrap(); // Next, let's construct our tree. // This follows the API in https://github.com/arkworks-rs/crypto-primitives/blob/6be606259eab0aec010015e2cfd45e4f134cd9bf/src/merkle_tree/mod.rs#L156 let tree = crate::SimpleMerkleTree::new( &leaf_crh_params, &two_to_one_crh_params, - &[1u8, 2u8, 3u8, 10u8, 9u8, 17u8, 70u8, 45u8], // the i-th entry is the i-th leaf. + &[ + &[1u8][..], + &[2u8][..], + &[3u8][..], + &[10u8][..], + &[9u8][..], + &[17u8][..], + &[70u8][..], + &[45u8][..], + ], // the i-th entry is the i-th leaf. ) .unwrap(); @@ -141,15 +150,24 @@ fn merkle_tree_constraints_soundness() { let mut rng = ark_std::test_rng(); // First, let's sample the public parameters for the hash functions: - let leaf_crh_params = ::setup(&mut rng).unwrap(); - let two_to_one_crh_params = ::setup(&mut rng).unwrap(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_crh_params = ::setup(&mut rng).unwrap(); // Next, let's construct our tree. // This follows the API in https://github.com/arkworks-rs/crypto-primitives/blob/6be606259eab0aec010015e2cfd45e4f134cd9bf/src/merkle_tree/mod.rs#L156 let tree = crate::SimpleMerkleTree::new( &leaf_crh_params, &two_to_one_crh_params, - &[1u8, 2u8, 3u8, 10u8, 9u8, 17u8, 70u8, 45u8], // the i-th entry is the i-th leaf. + &[ + &[1u8][..], + &[2u8][..], + &[3u8][..], + &[10u8][..], + &[9u8][..], + &[17u8][..], + &[70u8][..], + &[45u8][..], + ], // the i-th entry is the i-th leaf. ) .unwrap(); @@ -157,7 +175,16 @@ fn merkle_tree_constraints_soundness() { let second_tree = crate::SimpleMerkleTree::new( &leaf_crh_params, &two_to_one_crh_params, - &[4u8, 2u8, 3u8, 10u8, 9u8, 17u8, 70u8, 45u8], // the i-th entry is the i-th leaf. + &[ + &[4u8][..], + &[2u8][..], + &[3u8][..], + &[10u8][..], + &[9u8][..], + &[17u8][..], + &[70u8][..], + &[45u8][..], + ], // the i-th entry is the i-th leaf. ) .unwrap(); @@ -192,4 +219,4 @@ fn merkle_tree_constraints_soundness() { let is_satisfied = cs.is_satisfied().unwrap(); // We expect this to fail! assert!(!is_satisfied); -} \ No newline at end of file +} diff --git a/merkle-tree-example/src/lib.rs b/merkle-tree-example/src/lib.rs index f9f5341..3427636 100644 --- a/merkle-tree-example/src/lib.rs +++ b/merkle-tree-example/src/lib.rs @@ -1,7 +1,11 @@ -use ark_crypto_primitives::crh::TwoToOneCRH; -use ark_crypto_primitives::merkle_tree::{Config, MerkleTree, Path}; +use ark_crypto_primitives::crh::{ + CRHScheme, CRHSchemeGadget, TwoToOneCRHScheme, TwoToOneCRHSchemeGadget, +}; +use ark_crypto_primitives::merkle_tree::constraints::{BytesVarDigestConverter, ConfigGadget}; +use ark_crypto_primitives::merkle_tree::{ByteDigestConverter, Config, MerkleTree, Path}; pub mod common; +use ark_r1cs_std::uint8::UInt8; use common::*; mod constraints; @@ -12,35 +16,61 @@ pub struct MerkleConfig; impl Config for MerkleConfig { // Our Merkle tree relies on two hashes: one to hash leaves, and one to hash pairs // of internal nodes. + type Leaf = [u8]; type LeafHash = LeafHash; type TwoToOneHash = TwoToOneHash; + type LeafDigest = ::Output; + type LeafInnerDigestConverter = ByteDigestConverter; + type InnerDigest = ::Output; } +struct MerkleConfigVar; +impl ConfigGadget for MerkleConfigVar { + type Leaf = LeafVar; + type LeafDigest = >::OutputVar; + type LeafInnerConverter = BytesVarDigestConverter; + type InnerDigest = + >::OutputVar; + type LeafHash = LeafHashGadget; + type TwoToOneHash = TwoToOneHashGadget; +} + +type LeafVar = [UInt8]; + /// A Merkle tree containing account information. pub type SimpleMerkleTree = MerkleTree; /// The root of the account Merkle tree. -pub type Root = ::Output; +pub type Root = ::Output; /// A membership proof for a given account. pub type SimplePath = Path; // Run this test via `cargo test --release test_merkle_tree`. #[test] fn test_merkle_tree() { - use ark_crypto_primitives::crh::CRH; + use ark_crypto_primitives::crh::CRHScheme; // Let's set up an RNG for use within tests. Note that this is *not* safe // for any production use. let mut rng = ark_std::test_rng(); // First, let's sample the public parameters for the hash functions: - let leaf_crh_params = ::setup(&mut rng).unwrap(); - let two_to_one_crh_params = ::setup(&mut rng).unwrap(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_crh_params = ::setup(&mut rng).unwrap(); // Next, let's construct our tree. // This follows the API in https://github.com/arkworks-rs/crypto-primitives/blob/6be606259eab0aec010015e2cfd45e4f134cd9bf/src/merkle_tree/mod.rs#L156 let tree = SimpleMerkleTree::new( &leaf_crh_params, &two_to_one_crh_params, - &[1u8, 2u8, 3u8, 10u8, 9u8, 17u8, 70u8, 45u8], // the i-th entry is the i-th leaf. + &[ + &[1u8][..], + &[2u8][..], + &[3u8][..], + &[10u8][..], + &[9u8][..], + &[17u8][..], + &[70u8][..], + &[45u8][..], + ], // the i-th entry is the i-th leaf. ) .unwrap(); @@ -56,7 +86,7 @@ fn test_merkle_tree() { &leaf_crh_params, &two_to_one_crh_params, &root, - &[9u8], // The claimed leaf + &[9u8][..], // The claimed leaf ) .unwrap(); assert!(result); diff --git a/rollup/Cargo.toml b/rollup/Cargo.toml index 159c831..7710830 100644 --- a/rollup/Cargo.toml +++ b/rollup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ark-rollup" -version = "0.3.0" +version = "0.5.0" authors = [ "arkworks contributors" ] description = "A SNARK-based rollup for a simple payments system" repository = "https://github.com/arkworks-rs/r1cs-tutorial" @@ -11,21 +11,22 @@ license = "MIT/Apache-2.0" edition = "2018" [dependencies] -ark-ff = { version = "^0.3.0", default-features = false } -ark-ec = { version = "^0.3.0", default-features = false } -ark-ed-on-bls12-381 = { version = "^0.3.0", features = ["r1cs"] } -ark-bls12-381 = { version = "^0.3.0" } -ark-std = { version = "^0.3.0", default-features = false } -ark-relations = { version = "^0.3.0", default-features = false, optional = true } +ark-ff = { version = "^0.5.0", default-features = false } +ark-ec = { version = "^0.5.0", default-features = false } +ark-ed-on-bls12-381 = { version = "^0.5.0", features = ["r1cs"] } +ark-bls12-381 = { version = "^0.5.0" } -ark-r1cs-std = { version = "^0.3.0", optional = true, default-features = false } -ark-snark = { version = "^0.3.0", default-features = false } -ark-groth16 = { version = "^0.3.0" } -ark-gm17 = { version = "^0.3.0" } +ark-std = { version = "^0.5.0", default-features = false } +ark-relations = { version = "^0.5.0", default-features = false, optional = true } -ark-serialize = { version = "^0.3.0", default-features = false } +ark-r1cs-std = { version = "^0.5.0", optional = true, default-features = false } +ark-snark = { version = "^0.5.0", default-features = false } +ark-groth16 = { version = "^0.5.0" } -ark-crypto-primitives = { version = "^0.3.0", default-features = true } + +ark-serialize = { version = "^0.5.0", default-features = false } + +ark-crypto-primitives = { version = "^0.5.0", default-features = true } ark-simple-payments = { path = "../simple-payments", default-features = true } blake2 = { version = "0.9" } digest = "0.9" diff --git a/rollup/README.md b/rollup/README.md index 1b9b241..e21beed 100644 --- a/rollup/README.md +++ b/rollup/README.md @@ -11,21 +11,21 @@ At a high level, the constraint system for batch verification works as follows: * Checks: For each transaction in the batch, check the validity of applying that transaction: - (1) Check a Merkle Tree path wrt initial root that demonstrates the existence of the sender's account. - (2) Check a Merkle Tree path wrt initial root that demonstrates the existence of the receiver's account. - (3) Verify the signature in the transaction with respect to the sender's public key. - (4) Verify that sender.balance >= tx.amont (i.e., sender has sufficient funds). - (5) Compute new balances for both the sender and the receiver. - (6) Check a Merkle Tree path wrt final root for the new sender balance. - (7) Check a Merkle Tree path wrt final root for the new receiver balance. + 1) Check a Merkle Tree path wrt initial root that demonstrates the existence of the sender's account. + 2) Check a Merkle Tree path wrt initial root that demonstrates the existence of the receiver's account. + 3) Verify the signature in the transaction with respect to the sender's public key. + 4) Verify that sender.balance >= tx.amount (i.e., sender has sufficient funds). + 5) Compute new balances for both the sender and the receiver. + 6) Check a Merkle Tree path wrt final root for the new sender balance. + 7) Check a Merkle Tree path wrt final root for the new receiver balance. To make it easier to write out this constraint system, we've provided gadget equivalents of the key data structures from `simple-payments`. Find these via `cargo doc --open --no-deps`. ## Verifying a single transaction -Our first task will be to verify the state transitions involved when applying a single transaction. Go to [`transaction.rs`](./src/transaction.rs) and fill in the blanks in the `validate` method, following the hints there. Use the pseudocode [above](#batch-verification) and the logic in `simple_payments::transaction::Transaction::validate` as guides. To check if your code works, run `cargo test single_tx_validity_test`. +Our first task will be to verify the state transitions involved when applying a single transaction. Go to [`transaction.rs`](./src/transaction.rs) and fill in the blanks in the `validate` method, following the hints there. Use the pseudocode [above](#batch-verification) and the logic in `simple_payments::transaction::Transaction::validate` as guides. To check if your code works, run `cargo test unary_rollup_validity_test`. ## Verifying a batch of transactions -Use the foregoing validation logic to verify a batch of transactions in the `generate_constraints` method in [`rollup.rs#148], and verify that your circuit works via `cargo test end_to_end`, and then test that you can generate a valid proof via `cargo test snark_verification`. \ No newline at end of file +Use the foregoing validation logic to verify a batch of transactions in the `generate_constraints` method in [`rollup.rs#148`], and verify that your circuit works via `cargo test single_tx_validity_test` and `cargo test end_to_end`, and then test that you can generate a valid proof via `cargo test snark_verification`. diff --git a/rollup/src/account.rs b/rollup/src/account.rs index 06a82e2..b2ea12e 100644 --- a/rollup/src/account.rs +++ b/rollup/src/account.rs @@ -1,8 +1,8 @@ use crate::ledger::*; use crate::ConstraintF; use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsProjective}; -use ark_r1cs_std::bits::{uint8::UInt8, ToBytesGadget}; use ark_r1cs_std::prelude::*; +use ark_r1cs_std::uint8::UInt8; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_simple_payments::account::*; use ark_simple_payments::signature::schnorr::constraints::*; @@ -47,12 +47,9 @@ impl AccountInformationVar { /// Convert the account information to bytes. #[tracing::instrument(target = "r1cs", skip(self))] pub fn to_bytes_le(&self) -> Vec> { - self.public_key - .to_bytes() - .unwrap() - .into_iter() - .chain(self.balance.to_bytes_le()) - .collect() + let mut result = self.public_key.to_bytes_le().unwrap(); + result.extend_from_slice(&self.balance.to_bytes_le()); + result } } @@ -68,7 +65,7 @@ impl AllocVar for AccountInformationVar { let cs = cs.into(); let public_key = AccountPublicKeyVar::new_variable(cs.clone(), || Ok(&info.public_key), mode)?; - let balance = AmountVar::new_variable(cs.clone(), || Ok(&info.balance), mode)?; + let balance = AmountVar::new_variable(cs, || Ok(&info.balance), mode)?; Ok(Self { public_key, balance, diff --git a/rollup/src/ledger.rs b/rollup/src/ledger.rs index abcb5bb..0299028 100644 --- a/rollup/src/ledger.rs +++ b/rollup/src/ledger.rs @@ -1,14 +1,8 @@ use crate::ConstraintF; -use ark_crypto_primitives::crh::injective_map::constraints::{ - PedersenCRHCompressorGadget, TECompressorGadget, -}; -use ark_crypto_primitives::crh::{ - constraints::{CRHGadget, TwoToOneCRHGadget}, - injective_map::TECompressor, -}; + +use ark_crypto_primitives::crh::{CRHSchemeGadget, TwoToOneCRHSchemeGadget}; use ark_crypto_primitives::merkle_tree::constraints::PathVar; use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsProjective}; -use ark_r1cs_std::bits::uint64::UInt64; use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_simple_payments::ledger::*; @@ -22,7 +16,7 @@ pub struct AmountVar(pub UInt64); impl AmountVar { #[tracing::instrument(target = "r1cs", skip(self))] pub fn to_bytes_le(&self) -> Vec> { - self.0.to_bytes().unwrap() + self.0.to_bytes_le().unwrap() } #[tracing::instrument(target = "r1cs", skip(self, other))] @@ -33,17 +27,17 @@ impl AmountVar { // converting the field elements to bits // and then checking if the 65th bit is 0. // TODO: Demonstrate via circuit profiling if this needs optimization. - let self_bits = self.0.to_bits_le(); - let self_fe = Boolean::le_bits_to_fp_var(&self_bits)?; - let other_bits = other.0.to_bits_le(); - let other_fe = Boolean::le_bits_to_fp_var(&other_bits)?; + let self_bits = self.0.to_bits_le().unwrap(); + let self_fe = Boolean::le_bits_to_fp(&self_bits)?; + let other_bits = other.0.to_bits_le().unwrap(); + let other_fe = Boolean::le_bits_to_fp(&other_bits)?; let res_fe = self_fe + other_fe; - let res_bz = res_fe.to_bytes()?; + let res_bz = res_fe.to_bytes_le()?; // Ensure 65th bit is 0 // implies 8th word (0-indexed) is 0 res_bz[8].enforce_equal(&UInt8::::constant(0))?; // Add sum - let result = UInt64::addmany(&[self.0.clone(), other.0.clone()])?; + let result = UInt64::wrapping_add_many(&[self.0.clone(), other.0.clone()])?; Ok(AmountVar(result)) } @@ -54,12 +48,12 @@ impl AmountVar { // We then cast the field element to bits, and ensure the top bits are 0. // We then convert these bits to a field element // TODO: Demonstrate via circuit profiling if this needs optimization. - let self_bits = self.0.to_bits_le(); - let self_fe = Boolean::le_bits_to_fp_var(&self_bits)?; - let other_bits = other.0.to_bits_le(); - let other_fe = Boolean::le_bits_to_fp_var(&other_bits)?; + let self_bits = self.0.to_bits_le().unwrap(); + let self_fe = Boolean::le_bits_to_fp(&self_bits)?; + let other_bits = other.0.to_bits_le().unwrap(); + let other_fe = Boolean::le_bits_to_fp(&other_bits)?; let res_fe = self_fe - other_fe; - let res_bz = res_fe.to_bytes()?; + let res_bz = res_fe.to_bytes_le()?; // Ensure top bit is 0 res_bz[res_bz.len() - 1].enforce_equal(&UInt8::::constant(0))?; // Convert to UInt64 @@ -79,28 +73,14 @@ impl AllocVar for AmountVar { } } -pub type TwoToOneHashGadget = PedersenCRHCompressorGadget< - EdwardsProjective, - TECompressor, - TwoToOneWindow, - EdwardsVar, - TECompressorGadget, ->; - -pub type LeafHashGadget = PedersenCRHCompressorGadget< - EdwardsProjective, - TECompressor, - LeafWindow, - EdwardsVar, - TECompressorGadget, ->; - pub type AccRootVar = - >::OutputVar; -pub type AccPathVar = PathVar; -pub type LeafHashParamsVar = >::ParametersVar; + >::OutputVar; + +pub type AccPathVar = PathVar; +pub type LeafHashParamsVar = + >::ParametersVar; pub type TwoToOneHashParamsVar = - >::ParametersVar; + >::ParametersVar; /// The parameters that are used in transaction creation and validation. pub struct ParametersVar { diff --git a/rollup/src/lib.rs b/rollup/src/lib.rs index 49c85de..f64b462 100644 --- a/rollup/src/lib.rs +++ b/rollup/src/lib.rs @@ -1,4 +1,4 @@ -pub type ConstraintF = ark_bls12_381::Fr; +pub type ConstraintF = ark_ed_on_bls12_381::Fq; pub mod account; pub mod ledger; diff --git a/rollup/src/rollup.rs b/rollup/src/rollup.rs index 12a2cc3..532833f 100644 --- a/rollup/src/rollup.rs +++ b/rollup/src/rollup.rs @@ -97,12 +97,12 @@ impl Rollup { let sender_id = tx.sender; let recipient_id = tx.recipient; let pre_tx_root = state.root(); - let sender_pre_acc_info = state.id_to_account_info.get(&sender_id)?.clone(); + let sender_pre_acc_info = *state.id_to_account_info.get(&sender_id)?; let sender_pre_path = state .account_merkle_tree .generate_proof(sender_id.0 as usize) .unwrap(); - let recipient_pre_acc_info = state.id_to_account_info.get(&recipient_id)?.clone(); + let recipient_pre_acc_info = *state.id_to_account_info.get(&recipient_id)?; let recipient_pre_path = state .account_merkle_tree .generate_proof(recipient_id.0 as usize) @@ -198,6 +198,7 @@ impl ConstraintSynthesizer for Rollup sender_pre_path.ok_or(SynthesisError::AssignmentMissing) })?; // ... and authentication path after the update. + // TODO: Fill in the following let sender_post_path = AccPathVar::new_witness(ark_relations::ns!(cs, "Sender Post-Path"), || { sender_post_path.ok_or(SynthesisError::AssignmentMissing) @@ -214,6 +215,7 @@ impl ConstraintSynthesizer for Rollup })?; // ... and authentication path after the update. + // TODO: Fill in the following let recipient_post_path = AccPathVar::new_witness(ark_relations::ns!(cs, "Recipient Post-Path"), || { recipient_post_path.ok_or(SynthesisError::AssignmentMissing) @@ -231,9 +233,10 @@ impl ConstraintSynthesizer for Rollup // Enforce that the state root after the previous transaction equals // the starting state root for this transaction - prev_root.enforce_equal(&pre_tx_root)?; // TODO: FILL IN THE BLANKS + prev_root.enforce_equal(&pre_tx_root)?; // Validate that the transaction signature and amount is correct. + // TODO: Uncomment this tx.validate( &ledger_params, &sender_acc_info, @@ -245,14 +248,14 @@ impl ConstraintSynthesizer for Rollup &pre_tx_root, &post_tx_root, )? - .enforce_equal(&Boolean::TRUE)?; // TODO: FILL IN THE BLANKS + .enforce_equal(&Boolean::TRUE)?; // Set the root for the next transaction. prev_root = post_tx_root; } // Check that the final root is consistent with the root computed after // applying all state transitions - prev_root.enforce_equal(&final_root)?; // TODO: FILL IN THE BLANKS + prev_root.enforce_equal(&final_root)?; Ok(()) } } @@ -266,6 +269,7 @@ mod test { use ark_simple_payments::account::AccountId; use ark_simple_payments::ledger::{Amount, Parameters, State}; use ark_simple_payments::transaction::Transaction; + use ark_std::rand::SeedableRng; use tracing_subscriber::layer::SubscriberExt; fn test_cs(rollup: Rollup) -> bool { @@ -310,6 +314,7 @@ mod test { .unwrap(); assert!(test_cs(rollup)); + let mut temp_state = state.clone(); let bad_tx = Transaction::create(&pp, alice_id, bob_id, Amount(5), &bob_sk, &mut rng); assert!(!bad_tx.validate(&pp, &temp_state)); assert!(matches!(temp_state.apply_transaction(&pp, &bad_tx), None)); @@ -456,7 +461,7 @@ mod test { // Use a circuit just to generate the circuit let circuit_defining_cs = build_two_tx_circuit(); - let mut rng = ark_std::test_rng(); + let mut rng = ark_std::rand::rngs::StdRng::from_seed([1; 32]); let (pk, vk) = Groth16::::circuit_specific_setup(circuit_defining_cs, &mut rng).unwrap(); @@ -468,8 +473,8 @@ mod test { circuit_to_verify_against.final_root.unwrap(), ]; - let proof = Groth16::prove(&pk, circuit_to_verify_against, &mut rng).unwrap(); - let valid_proof = Groth16::verify(&vk, &public_input, &proof).unwrap(); + let proof = Groth16::::prove(&pk, circuit_to_verify_against, &mut rng).unwrap(); + let valid_proof = Groth16::::verify(&vk, &public_input, &proof).unwrap(); assert!(valid_proof); // Use the same circuit but with different inputs to verify against @@ -481,8 +486,8 @@ mod test { circuit_to_verify_against.final_root.unwrap(), ]; - let proof = Groth16::prove(&pk, circuit_to_verify_against, &mut rng).unwrap(); - let valid_proof = Groth16::verify(&vk, &public_input, &proof).unwrap(); + let proof = Groth16::::prove(&pk, circuit_to_verify_against, &mut rng).unwrap(); + let valid_proof = Groth16::::verify(&vk, &public_input, &proof).unwrap(); assert!(!valid_proof); } } diff --git a/rollup/src/transaction.rs b/rollup/src/transaction.rs index c03c3ee..15091cd 100644 --- a/rollup/src/transaction.rs +++ b/rollup/src/transaction.rs @@ -1,9 +1,11 @@ use crate::account::{AccountIdVar, AccountInformationVar, AccountPublicKeyVar}; -use crate::ledger::{self, AccPathVar, AccRootVar, AmountVar}; +use crate::ledger::{self, AccPathVar, AccRootVar, AmountVar, ParametersVar}; use crate::ConstraintF; use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsProjective}; use ark_r1cs_std::prelude::*; -use ark_relations::r1cs::{Namespace, SynthesisError}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; +use ark_simple_payments::account::AccountInformation; +use ark_simple_payments::ledger::{AccPath, AccRoot, Parameters, State}; use ark_simple_payments::signature::schnorr::constraints::{ ParametersVar as SchnorrParamsVar, SchnorrSignatureVerifyGadget, SignatureVar, }; @@ -37,7 +39,7 @@ impl TransactionVar { let mut message = self.sender.to_bytes_le(); message.extend(self.recipient.to_bytes_le()); message.extend(self.amount.to_bytes_le()); - SchnorrSignatureVerifyGadget::verify(&pp, &pub_key, &message, &self.signature) + SchnorrSignatureVerifyGadget::verify(pp, pub_key, &message, &self.signature) } /// Check that the transaction is valid for the given ledger state. This checks @@ -47,6 +49,7 @@ impl TransactionVar { /// 2. Verify that the sender's account has sufficient balance to finance /// the transaction. /// 3. Verify that the recipient's account exists. + #[allow(clippy::too_many_arguments)] #[tracing::instrument( target = "r1cs", skip( @@ -75,55 +78,65 @@ impl TransactionVar { post_root: &AccRootVar, ) -> Result, SynthesisError> { // Verify the signature against the sender pubkey. + // TODO: FILL IN THE BLANK let sig_verifies = - self.verify_signature(¶meters.sig_params, &pre_sender_acc_info.public_key)?; + self.verify_signature(¶meters.sig_params, &pre_sender_acc_info.public_key)?; // Compute the new sender balance. let mut post_sender_acc_info = pre_sender_acc_info.clone(); - post_sender_acc_info.balance = post_sender_acc_info.balance.checked_sub(&self.amount)?; - // Compute the new receiver balance. + // TODO: Safely subtract amount sent from the sender's balance + post_sender_acc_info.balance = post_sender_acc_info.balance.checked_sub(&self.amount)?; + + // TODO: Compute the new receiver balance, ensure its overflow safe. let mut post_recipient_acc_info = pre_recipient_acc_info.clone(); - post_recipient_acc_info.balance = post_recipient_acc_info.balance.checked_add(&self.amount)?; + post_recipient_acc_info.balance = + post_recipient_acc_info.balance.checked_add(&self.amount)?; // Check that the pre-tx sender account information is correct with // respect to `pre_tx_root`, and that the post-tx sender account // information is correct with respect to `post_tx_root`. + // HINT: Use the path structs + // TODO: FILL IN THE FOLLOWING let sender_exists = pre_sender_path.verify_membership( ¶meters.leaf_crh_params, ¶meters.two_to_one_crh_params, &pre_root, &pre_sender_acc_info.to_bytes_le().as_slice(), - )?; + )?; let sender_updated_correctly = post_sender_path.verify_membership( ¶meters.leaf_crh_params, ¶meters.two_to_one_crh_params, &post_root, &post_sender_acc_info.to_bytes_le().as_slice(), - )?; + )?; // Check that the pre-tx recipient account information is correct with // respect to `pre_tx_root`, and that the post-tx recipient account // information is correct with respect to `post_tx_root`. + // TODO: FILL IN THE FOLLOWING let recipient_exists = pre_recipient_path.verify_membership( ¶meters.leaf_crh_params, ¶meters.two_to_one_crh_params, &pre_root, &pre_recipient_acc_info.to_bytes_le().as_slice(), - )?; + )?; let recipient_updated_correctly = post_recipient_path.verify_membership( ¶meters.leaf_crh_params, ¶meters.two_to_one_crh_params, &post_root, &post_recipient_acc_info.to_bytes_le().as_slice(), - )?; + )?; + + // TODO: Uncomment the following + let val = sender_exists + & sender_updated_correctly + & recipient_exists + & recipient_updated_correctly + & sig_verifies; - sender_exists - .and(&sender_updated_correctly)? - .and(&recipient_exists)? - .and(&recipient_updated_correctly)? - .and(&sig_verifies) + Ok(val) } } @@ -150,3 +163,217 @@ impl AllocVar for TransactionVar { }) } } + +pub struct UnaryRollup { + /// The ledger parameters. + pub ledger_params: Parameters, + /// The Merkle tree root before applying this batch of transactions. + pub initial_root: AccRoot, + /// The Merkle tree root after applying this batch of transactions. + pub final_root: AccRoot, + /// The current batch of transactions. + pub transaction: Transaction, + /// The sender's account information *before* applying the transaction. + pub sender_acc_info: AccountInformation, + /// The sender's authentication path, *before* applying the transaction. + pub sender_pre_path: AccPath, + /// The authentication path corresponding to the sender's account information *after* applying + /// the transactions. + pub sender_post_path: AccPath, + /// The recipient's account information *before* applying the transaction. + pub recv_acc_info: AccountInformation, + /// The recipient's authentication path, *before* applying the transaction. + pub recv_pre_path: AccPath, + /// The authentication path corresponding to the recipient's account information *after* + /// applying the transactions. + pub recv_post_path: AccPath, +} + +impl UnaryRollup { + pub fn with_state_and_transaction( + ledger_params: Parameters, + transaction: Transaction, + state: &mut State, + validate: bool, + ) -> Option { + if validate && !transaction.validate(&ledger_params, &*state) { + return None; + } + + let initial_root = state.root(); + let sender_id = transaction.sender; + let recipient_id = transaction.recipient; + + let sender_acc_info = *state.id_to_account_info.get(&sender_id)?; + let sender_pre_path = state + .account_merkle_tree + .generate_proof(sender_id.0 as usize) + .unwrap(); + + let recv_acc_info = *state.id_to_account_info.get(&recipient_id)?; + let recv_pre_path = state + .account_merkle_tree + .generate_proof(recipient_id.0 as usize) + .unwrap(); + + if validate { + state.apply_transaction(&ledger_params, &transaction)?; + } else { + let _ = state.apply_transaction(&ledger_params, &transaction); + } + + let final_root = state.root(); + let sender_post_path = state + .account_merkle_tree + .generate_proof(sender_id.0 as usize) + .unwrap(); + let recv_post_path = state + .account_merkle_tree + .generate_proof(recipient_id.0 as usize) + .unwrap(); + + Some(UnaryRollup { + ledger_params, + initial_root, + final_root, + transaction, + sender_acc_info, + sender_pre_path, + sender_post_path, + recv_acc_info, + recv_pre_path, + recv_post_path, + }) + } +} + +impl ConstraintSynthesizer for UnaryRollup { + fn generate_constraints( + self, + cs: ConstraintSystemRef, + ) -> Result<(), SynthesisError> { + // Declare the parameters as constants. + let ledger_params = ParametersVar::new_constant( + ark_relations::ns!(cs, "Ledger parameters"), + &self.ledger_params, + )?; + // Declare the initial root as a public input. + let initial_root = AccRootVar::new_input(ark_relations::ns!(cs, "Initial root"), || { + Ok(self.initial_root) + })?; + + // Declare the final root as a public input. + let final_root = + AccRootVar::new_input(ark_relations::ns!(cs, "Final root"), || Ok(self.final_root))?; + + // Declare transaction as a witness. + let tx = TransactionVar::new_witness(ark_relations::ns!(cs, "Transaction"), || { + Ok(self.transaction.clone()) + })?; + + // Declare the sender's initial account balance... + let sender_acc_info = AccountInformationVar::new_witness( + ark_relations::ns!(cs, "Sender Account Info"), + || Ok(self.sender_acc_info), + )?; + // ..., corresponding authentication path, ... + let sender_pre_path = + AccPathVar::new_witness(ark_relations::ns!(cs, "Sender Pre-Path"), || { + Ok(self.sender_pre_path.clone()) + })?; + // ... and authentication path after the update. + let sender_post_path = + AccPathVar::new_witness(ark_relations::ns!(cs, "Sender Post-Path"), || { + Ok(self.sender_post_path.clone()) + })?; + + // Declare the recipient's initial account balance... + let recipient_acc_info = AccountInformationVar::new_witness( + ark_relations::ns!(cs, "Recipient Account Info"), + || Ok(self.recv_acc_info), + )?; + // ..., corresponding authentication path, ... + let recipient_pre_path = + AccPathVar::new_witness(ark_relations::ns!(cs, "Recipient Pre-Path"), || { + Ok(self.recv_pre_path.clone()) + })?; + // ... and authentication path after the update. + let recipient_post_path = + AccPathVar::new_witness(ark_relations::ns!(cs, "Recipient Post-Path"), || { + Ok(self.recv_post_path.clone()) + })?; + + // Validate that the transaction signature and amount is correct. + tx.validate( + &ledger_params, + &sender_acc_info, + &sender_pre_path, + &sender_post_path, + &recipient_acc_info, + &recipient_pre_path, + &recipient_post_path, + &initial_root, + &final_root, + )? + .enforce_equal(&Boolean::TRUE) + } +} + +#[cfg(test)] +mod test { + use super::*; + use ark_relations::r1cs::{ + ConstraintLayer, ConstraintSynthesizer, ConstraintSystem, TracingMode::OnlyConstraints, + }; + + use ark_simple_payments::ledger::{Amount, Parameters, State}; + use ark_simple_payments::transaction::Transaction; + use tracing_subscriber::layer::SubscriberExt; + + fn test_cs(rollup: UnaryRollup) -> bool { + let mut layer = ConstraintLayer::default(); + layer.mode = OnlyConstraints; + let subscriber = tracing_subscriber::Registry::default().with(layer); + let _guard = tracing::subscriber::set_default(subscriber); + let cs = ConstraintSystem::new_ref(); + rollup.generate_constraints(cs.clone()).unwrap(); + cs.is_satisfied().unwrap() + } + + #[test] + fn unary_rollup_validity_test() { + let mut rng = ark_std::test_rng(); + let pp = Parameters::sample(&mut rng); + let mut state = State::new(32, &pp); + + // Let's make an account for Alice. + let (alice_id, _alice_pk, alice_sk) = + state.sample_keys_and_register(&pp, &mut rng).unwrap(); + // Let's give her some initial balance to start with. + state + .update_balance(alice_id, Amount(20)) + .expect("Alice's account should exist"); + // Let's make an account for Bob. + let (bob_id, _bob_pk, bob_sk) = state.sample_keys_and_register(&pp, &mut rng).unwrap(); + + // Alice wants to transfer 5 units to Bob. + let mut temp_state = state.clone(); + let tx1 = Transaction::create(&pp, alice_id, bob_id, Amount(5), &alice_sk, &mut rng); + assert!(tx1.validate(&pp, &temp_state)); + let rollup = + UnaryRollup::with_state_and_transaction(pp.clone(), tx1, &mut temp_state, true) + .unwrap(); + + assert!(test_cs(rollup)); + + let mut temp_state = state.clone(); + let bad_tx = Transaction::create(&pp, alice_id, bob_id, Amount(5), &bob_sk, &mut rng); + assert!(!bad_tx.validate(&pp, &temp_state)); + assert!(matches!(temp_state.apply_transaction(&pp, &bad_tx), None)); + let rollup = + UnaryRollup::with_state_and_transaction(pp.clone(), bad_tx, &mut temp_state, false) + .unwrap(); + + assert!(!test_cs(rollup)); + } +} diff --git a/simple-payments/Cargo.toml b/simple-payments/Cargo.toml index 1a9dd99..b5c1234 100644 --- a/simple-payments/Cargo.toml +++ b/simple-payments/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ark-simple-payments" -version = "0.3.0" +version = "0.5.0" authors = [ "arkworks contributors" ] description = "A simple payments system" repository = "https://github.com/arkworks-rs/r1cs-tutorial" @@ -10,24 +10,28 @@ include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] license = "MIT/Apache-2.0" edition = "2018" +[toolchain] +channel = "nightly" + [dependencies] -ark-ff = { version = "^0.3.0", default-features = false } -ark-ec = { version = "^0.3.0", default-features = false } -ark-ed-on-bls12-381 = { version = "^0.3.0", features = ["r1cs"] } -ark-bls12-381 = { version = "^0.3.0", default-features = false } -ark-std = { version = "^0.3.0", default-features = false } -ark-relations = { version = "^0.3.0", default-features = false, optional = true } +ark-ff = { version = "^0.5.0", default-features = false } +ark-ec = { version = "^0.5.0", default-features = false } +ark-ed-on-bls12-381 = { version = "^0.5.0", features = ["r1cs"] } +ark-bls12-381 = { version = "^0.5.0", default-features = false } +ark-std = { version = "^0.5.0", default-features = false } +ark-relations = { version = "^0.5.0", default-features = false, optional = true } -ark-r1cs-std = { version = "^0.3.0", optional = true, default-features = false } -ark-snark = { version = "^0.3.0", default-features = false } +ark-r1cs-std = { version = "^0.5.0", optional = true, default-features = false } +ark-snark = { version = "^0.5.0", default-features = false } -ark-serialize = { version = "^0.3.0", default-features = false } +ark-serialize = { version = "^0.5.0", default-features = false } -ark-crypto-primitives = { version = "^0.3.0", default-features = true } +ark-crypto-primitives = { version = "^0.5.0", default-features = true, features=["prf", "crh", "merkle_tree"] } blake2 = { version = "0.9" } digest = "0.9" derivative = { version = "2.0", features = ["use_core"] } -tracing = { version = "0.1", default-features = false, features = [ "attributes" ], optional = true } +tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } +tracing-subscriber = { version = "0.2" } [features] default = [ "std", "parallel", "r1cs" ] diff --git a/simple-payments/README.md b/simple-payments/README.md index 7d72366..f159627 100644 --- a/simple-payments/README.md +++ b/simple-payments/README.md @@ -14,12 +14,15 @@ To transfer value from their account to another account, the user first creates * Recipient's account identifier * Transaction amount * Signature on the previous three parts, using the signature public key associated with the sender's account. + The user then publishes this to the ledger, which applies the transaction via `ledger::State::apply_transaction`. + The latter method updates the ledger's information if the following conditions are satisfied: * The sender's account exists * The recipient's account exists * The sender's account contains a balance greater than or equal to the transaction amount * The signature is valid with respect to the public key stored in the sender's account + To enforce this logic, `Transaction::verify` performs the following steps on input a transaction `tx` and existing ledger state `State`. * Look up the `(SigPubKey, Balance)` tuple corresponding to the sender's ID in the Merkle tree in `State`. * Verify the transaction signature with respect to `SigPubKey`. @@ -39,6 +42,6 @@ We use a simple custom implementation of Schnorr signatures over the prime order Our implementation uses the Merkle tree of [`ark-crypto-primitives`](https://docs.rs/ark-crypto-primitives/0.3.0/ark_crypto_primitives/merkle_tree/index.html). This is the same tree that we saw in the `merkle-tree-example` step. In our system, the concrete underlying hash function is the Pedersen hash function, as implemented in the [`ark-crypto-primitives` crate](https://docs.rs/ark-crypto-primitives/0.3.0/ark_crypto_primitives/crh/pedersen/index.html). This hash is implemented over the prime-order subgroup of the Jubjub curve. -## Code walkthrough +## Code walk-through -To get an overview of important data structures as well as their associated methods, run `cargo doc --open --no-deps`. \ No newline at end of file +To get an overview of important data structures as well as their associated methods, run `cargo doc --open --no-deps`. diff --git a/simple-payments/src/account.rs b/simple-payments/src/account.rs index 375a9d1..23dbc2c 100644 --- a/simple-payments/src/account.rs +++ b/simple-payments/src/account.rs @@ -1,6 +1,7 @@ use crate::ledger::*; use crate::signature::schnorr; use ark_ed_on_bls12_381::EdwardsProjective; +use ark_serialize::CanonicalSerialize; /// Account public key used to verify transaction signatures. pub type AccountPublicKey = schnorr::PublicKey; @@ -37,6 +38,12 @@ pub struct AccountInformation { impl AccountInformation { /// Convert the account information to bytes. pub fn to_bytes_le(&self) -> Vec { - ark_ff::to_bytes![self.public_key, self.balance.to_bytes_le()].unwrap() + let mut public_key_bytes = Vec::new(); + self.public_key + .serialize_uncompressed(&mut public_key_bytes) + .unwrap(); + + public_key_bytes.extend_from_slice(&self.balance.to_bytes_le()); + public_key_bytes } -} \ No newline at end of file +} diff --git a/simple-payments/src/ledger.rs b/simple-payments/src/ledger.rs index 7ce590e..13ad948 100644 --- a/simple-payments/src/ledger.rs +++ b/simple-payments/src/ledger.rs @@ -1,12 +1,20 @@ use crate::account::{AccountId, AccountInformation, AccountPublicKey, AccountSecretKey}; use crate::signature::{schnorr, SignatureScheme}; use crate::transaction::Transaction; +use ark_crypto_primitives::crh::injective_map::constraints::{ + PedersenCRHCompressorGadget, PedersenTwoToOneCRHCompressorGadget, TECompressorGadget, +}; +use ark_crypto_primitives::crh::injective_map::PedersenTwoToOneCRHCompressor; use ark_crypto_primitives::crh::{ injective_map::{PedersenCRHCompressor, TECompressor}, - pedersen, TwoToOneCRH, CRH, + pedersen, CRHScheme, TwoToOneCRHScheme, }; -use ark_crypto_primitives::merkle_tree::{self, MerkleTree, Path}; +use ark_crypto_primitives::crh::{CRHSchemeGadget, TwoToOneCRHSchemeGadget}; +use ark_crypto_primitives::merkle_tree::constraints::{BytesVarDigestConverter, ConfigGadget}; +use ark_crypto_primitives::merkle_tree::{self, ByteDigestConverter, MerkleTree, Path}; +use ark_ed_on_bls12_381::constraints::EdwardsVar; use ark_ed_on_bls12_381::EdwardsProjective; +use ark_r1cs_std::uint8::UInt8; use ark_std::rand::Rng; use std::collections::HashMap; @@ -32,15 +40,15 @@ impl Amount { #[derive(Clone)] pub struct Parameters { pub sig_params: schnorr::Parameters, - pub leaf_crh_params: ::Parameters, - pub two_to_one_crh_params: ::Parameters, + pub leaf_crh_params: ::Parameters, + pub two_to_one_crh_params: ::Parameters, } impl Parameters { pub fn sample(rng: &mut R) -> Self { let sig_params = schnorr::Schnorr::setup(rng).unwrap(); - let leaf_crh_params = ::setup(rng).unwrap(); - let two_to_one_crh_params = ::setup(rng).unwrap(); + let leaf_crh_params = ::setup(rng).unwrap(); + let two_to_one_crh_params = ::setup(rng).unwrap(); Self { sig_params, leaf_crh_params, @@ -49,39 +57,72 @@ impl Parameters { } } -pub type TwoToOneHash = PedersenCRHCompressor; +pub type TwoToOneHash = + PedersenTwoToOneCRHCompressor; + +pub type TwoToOneHashGadget = PedersenTwoToOneCRHCompressorGadget< + EdwardsProjective, + TECompressor, + TwoToOneWindow, + EdwardsVar, + TECompressorGadget, +>; #[derive(Clone, PartialEq, Eq, Hash)] pub struct TwoToOneWindow; // `WINDOW_SIZE * NUM_WINDOWS` = 2 * 256 bits = enough for hashing two outputs. impl pedersen::Window for TwoToOneWindow { - const WINDOW_SIZE: usize = 128; - const NUM_WINDOWS: usize = 4; + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 128; } pub type LeafHash = PedersenCRHCompressor; +pub type LeafHashGadget = PedersenCRHCompressorGadget< + EdwardsProjective, + TECompressor, + LeafWindow, + EdwardsVar, + TECompressorGadget, +>; #[derive(Clone, PartialEq, Eq, Hash)] pub struct LeafWindow; // `WINDOW_SIZE * NUM_WINDOWS` = 2 * 256 bits = enough for hashing two outputs. impl pedersen::Window for LeafWindow { - const WINDOW_SIZE: usize = 144; - const NUM_WINDOWS: usize = 4; + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 144; } +pub type ConstraintF = ark_ed_on_bls12_381::Fq; +type LeafVar = [UInt8]; #[derive(Clone)] pub struct MerkleConfig; impl merkle_tree::Config for MerkleConfig { + type Leaf = [u8]; type LeafHash = LeafHash; type TwoToOneHash = TwoToOneHash; + type LeafDigest = ::Output; + type LeafInnerDigestConverter = ByteDigestConverter; + type InnerDigest = ::Output; +} + +pub struct MerkleConfigVar; +impl ConfigGadget for MerkleConfigVar { + type Leaf = LeafVar; + type LeafDigest = >::OutputVar; + type LeafInnerConverter = BytesVarDigestConverter; + type InnerDigest = + >::OutputVar; + type LeafHash = LeafHashGadget; + type TwoToOneHash = TwoToOneHashGadget; } /// A Merkle tree containing account information. pub type AccMerkleTree = MerkleTree; /// The root of the account Merkle tree. -pub type AccRoot = ::Output; +pub type AccRoot = ::Output; /// A membership proof for a given account. pub type AccPath = Path; diff --git a/simple-payments/src/random_oracle/blake2s/constraints.rs b/simple-payments/src/random_oracle/blake2s/constraints.rs index 6e9a198..f853d07 100644 --- a/simple-payments/src/random_oracle/blake2s/constraints.rs +++ b/simple-payments/src/random_oracle/blake2s/constraints.rs @@ -26,8 +26,8 @@ impl RandomOracleGadget for ROGadget { input_bits.extend_from_slice(&byte.to_bits_le()?); } let mut result = Vec::new(); - for int in evaluate_blake2s(&input_bits)?.into_iter() { - let chunk = int.to_bytes()?; + for int in evaluate_blake2s(&input_bits)?.iter() { + let chunk = int.to_bytes_le().unwrap(); result.extend_from_slice(&chunk); } Ok(OutputVar(result)) diff --git a/simple-payments/src/random_oracle/mod.rs b/simple-payments/src/random_oracle/mod.rs index 200ac2e..dea53fa 100644 --- a/simple-payments/src/random_oracle/mod.rs +++ b/simple-payments/src/random_oracle/mod.rs @@ -1,4 +1,3 @@ -use ark_ff::bytes::ToBytes; use ark_std::hash::Hash; use ark_std::rand::Rng; @@ -13,7 +12,7 @@ pub use constraints::*; /// Interface to a RandomOracle pub trait RandomOracle { - type Output: ToBytes + Clone + Eq + core::fmt::Debug + Hash + Default; + type Output: Clone + Eq + core::fmt::Debug + Hash + Default; type Parameters: Clone + Default; fn setup(r: &mut R) -> Result; diff --git a/simple-payments/src/signature/constraints.rs b/simple-payments/src/signature/constraints.rs index 5fc6f36..fdd0773 100644 --- a/simple-payments/src/signature/constraints.rs +++ b/simple-payments/src/signature/constraints.rs @@ -1,9 +1,8 @@ +use crate::signature::SignatureScheme; use ark_ff::Field; use ark_r1cs_std::prelude::*; use ark_relations::r1cs::SynthesisError; -use crate::signature::SignatureScheme; - pub trait SigVerifyGadget { type ParametersVar: AllocVar + Clone; @@ -38,13 +37,15 @@ pub trait SigRandomizePkGadget { #[cfg(test)] mod test { use crate::signature::{schnorr, schnorr::constraints::*, *}; - use ark_ec::ProjectiveCurve; + + use ark_ec::CurveGroup; use ark_ed_on_bls12_381::constraints::EdwardsVar as JubJubVar; use ark_ed_on_bls12_381::EdwardsProjective as JubJub; use ark_ff::PrimeField; use ark_r1cs_std::prelude::*; - use ark_relations::r1cs::ConstraintSystem; + use ark_relations::r1cs::{ConstraintLayer, ConstraintSystem, TracingMode}; use ark_std::test_rng; + use tracing_subscriber::layer::SubscriberExt; fn sign_and_verify>( message: &[u8], @@ -55,6 +56,11 @@ mod test { let sig = S::sign(¶meters, &sk, &message, rng).unwrap(); assert!(S::verify(¶meters, &pk, &message, &sig).unwrap()); + let mut layer = ConstraintLayer::default(); + layer.mode = TracingMode::All; + let subscriber = tracing_subscriber::Registry::default().with(layer); + let _guard = tracing::subscriber::set_default(subscriber); + let cs = ConstraintSystem::::new_ref(); let parameters_var = SG::ParametersVar::new_constant(cs.clone(), parameters).unwrap(); @@ -67,6 +73,7 @@ mod test { let valid_sig_var = SG::verify(¶meters_var, &pk_var, &msg_var, &signature_var).unwrap(); valid_sig_var.enforce_equal(&Boolean::::TRUE).unwrap(); + assert!(cs.is_satisfied().unwrap()); } @@ -79,14 +86,15 @@ mod test { } #[test] - fn schnorr_signature_test() { - type F = ::BaseField; + fn schnorr_signature_test_constraints() { + type F = ::BaseField; let message = "Hi, I am a Schnorr signature!"; sign_and_verify::< F, schnorr::Schnorr, SchnorrSignatureVerifyGadget, >(message.as_bytes()); + failed_verification::>( message.as_bytes(), "Bad message".as_bytes(), diff --git a/simple-payments/src/signature/mod.rs b/simple-payments/src/signature/mod.rs index f380713..07be106 100644 --- a/simple-payments/src/signature/mod.rs +++ b/simple-payments/src/signature/mod.rs @@ -1,5 +1,4 @@ use ark_crypto_primitives::Error; -use ark_ff::bytes::ToBytes; use ark_std::hash::Hash; use ark_std::rand::Rng; @@ -12,8 +11,8 @@ pub mod schnorr; pub trait SignatureScheme { type Parameters: Clone + Send + Sync; - type PublicKey: ToBytes + Hash + Eq + Clone + Default + Send + Sync; - type SecretKey: ToBytes + Clone + Default; + type PublicKey: Hash + Eq + Clone + Default + Send + Sync; + type SecretKey: Clone + Default; type Signature: Clone + Default + Send + Sync; fn setup(rng: &mut R) -> Result; diff --git a/simple-payments/src/signature/schnorr/constraints.rs b/simple-payments/src/signature/schnorr/constraints.rs index 6927a8f..6e44f6f 100644 --- a/simple-payments/src/signature/schnorr/constraints.rs +++ b/simple-payments/src/signature/schnorr/constraints.rs @@ -1,8 +1,8 @@ -use ark_ec::ProjectiveCurve; -use ark_ff::{to_bytes, Field}; -use ark_r1cs_std::{bits::uint8::UInt8, prelude::*}; -use ark_relations::r1cs::ConstraintSystemRef; +use ark_ec::CurveGroup; +use ark_ff::Field; +use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{Namespace, SynthesisError}; +use ark_serialize::CanonicalSerialize; use ark_std::vec::Vec; use crate::random_oracle::blake2s::constraints::{ParametersVar as B2SParamsVar, ROGadget}; @@ -15,10 +15,10 @@ use core::{borrow::Borrow, marker::PhantomData}; use crate::signature::schnorr::{Parameters, PublicKey, Schnorr, Signature}; -type ConstraintF = <::BaseField as Field>::BasePrimeField; +type ConstraintF = <::BaseField as Field>::BasePrimeField; #[derive(Clone)] -pub struct ParametersVar>> +pub struct ParametersVar>> where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -29,10 +29,10 @@ where #[derive(Derivative)] #[derivative( - Debug(bound = "C: ProjectiveCurve, GC: CurveVar>"), - Clone(bound = "C: ProjectiveCurve, GC: CurveVar>") + Debug(bound = "C: CurveGroup, GC: CurveVar>"), + Clone(bound = "C: CurveGroup, GC: CurveVar>") )] -pub struct PublicKeyVar>> +pub struct PublicKeyVar>> where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -43,10 +43,10 @@ where #[derive(Derivative)] #[derivative( - Debug(bound = "C: ProjectiveCurve, GC: CurveVar>"), - Clone(bound = "C: ProjectiveCurve, GC: CurveVar>") + Debug(bound = "C: CurveGroup, GC: CurveVar>"), + Clone(bound = "C: CurveGroup, GC: CurveVar>") )] -pub struct SignatureVar>> +pub struct SignatureVar>> where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -56,7 +56,7 @@ where _group: PhantomData, } -pub struct SchnorrSignatureVerifyGadget>> +pub struct SchnorrSignatureVerifyGadget>> where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -68,7 +68,7 @@ where impl SigVerifyGadget, ConstraintF> for SchnorrSignatureVerifyGadget where - C: ProjectiveCurve, + C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -84,9 +84,11 @@ where ) -> Result>, SynthesisError> { let prover_response = signature.prover_response.clone(); let verifier_challenge = signature.verifier_challenge.clone(); + let mut claimed_prover_commitment = parameters .generator .scalar_mul_le(prover_response.to_bits_le()?.iter())?; + let public_key_times_verifier_challenge = public_key .pub_key .scalar_mul_le(verifier_challenge.to_bits_le()?.iter())?; @@ -96,14 +98,14 @@ where if parameters.salt.is_some() { hash_input.extend_from_slice(parameters.salt.as_ref().unwrap()); } - hash_input.extend_from_slice(&public_key.pub_key.to_bytes()?); - hash_input.extend_from_slice(&claimed_prover_commitment.to_bytes()?); + + let pub_key_bytes = public_key.pub_key.to_bytes_le().unwrap(); + + hash_input.extend_from_slice(pub_key_bytes.as_slice()); + hash_input.extend_from_slice(claimed_prover_commitment.to_bytes_le().unwrap().as_slice()); hash_input.extend_from_slice(message); - let b2s_params = >>::new_constant( - ConstraintSystemRef::None, - (), - )?; + let b2s_params = B2SParamsVar; let obtained_verifier_challenge = ROGadget::evaluate(&b2s_params, &hash_input)?.0; obtained_verifier_challenge.is_eq(&verifier_challenge.to_vec()) @@ -112,7 +114,7 @@ where impl AllocVar, ConstraintF> for ParametersVar where - C: ProjectiveCurve, + C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -130,7 +132,7 @@ where for i in 0..32 { constraint_salt.push(UInt8::>::new_variable( cs.clone(), - || Ok(native_salt.unwrap()[i].clone()), + || Ok(native_salt.unwrap()[i]), mode, )?); } @@ -152,7 +154,7 @@ where impl AllocVar, ConstraintF> for PublicKeyVar where - C: ProjectiveCurve, + C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -171,7 +173,8 @@ where impl AllocVar, ConstraintF> for SignatureVar where - C: ProjectiveCurve, + C: CurveGroup, + C::ScalarField: CanonicalSerialize, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -182,21 +185,25 @@ where ) -> Result { f().and_then(|val| { let cs = cs.into(); - let response_bytes = to_bytes![val.borrow().prover_response].unwrap(); + let mut response_bytes = Vec::new(); + val.borrow() + .prover_response + .serialize_uncompressed(&mut response_bytes) + .unwrap(); let challenge_bytes = val.borrow().verifier_challenge; let mut prover_response = Vec::>>::new(); let mut verifier_challenge = Vec::>>::new(); - for i in 0..response_bytes.len() { + for byte in &response_bytes { prover_response.push(UInt8::>::new_variable( cs.clone(), - || Ok(response_bytes[i].clone()), + || Ok(byte), mode, )?); } - for i in 0..32 { + for byte in &challenge_bytes { verifier_challenge.push(UInt8::>::new_variable( cs.clone(), - || Ok(challenge_bytes[i].clone()), + || Ok(byte), mode, )?); } @@ -211,7 +218,7 @@ where impl EqGadget> for PublicKeyVar where - C: ProjectiveCurve, + C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { @@ -243,11 +250,11 @@ where impl ToBytesGadget> for PublicKeyVar where - C: ProjectiveCurve, + C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - fn to_bytes(&self) -> Result>>, SynthesisError> { - self.pub_key.to_bytes() + fn to_bytes_le(&self) -> Result>>, SynthesisError> { + self.pub_key.to_bytes_le() } } diff --git a/simple-payments/src/signature/schnorr/mod.rs b/simple-payments/src/signature/schnorr/mod.rs index daaa4d9..a58417a 100644 --- a/simple-payments/src/signature/schnorr/mod.rs +++ b/simple-payments/src/signature/schnorr/mod.rs @@ -1,12 +1,11 @@ use super::SignatureScheme; use ark_crypto_primitives::Error; -use ark_ec::{AffineCurve, ProjectiveCurve}; +use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{ - bytes::ToBytes, fields::{Field, PrimeField}, - to_bytes, ToConstraintField, UniformRand, + ToConstraintField, UniformRand, }; -use ark_std::io::{Result as IoResult, Write}; +use ark_serialize::CanonicalSerialize; use ark_std::rand::Rng; use ark_std::{hash::Hash, marker::PhantomData, vec::Vec}; use blake2::Blake2s; @@ -16,39 +15,32 @@ use derivative::Derivative; #[cfg(feature = "r1cs")] pub mod constraints; -pub struct Schnorr { +pub struct Schnorr { _group: PhantomData, } #[derive(Derivative)] -#[derivative(Clone(bound = "C: ProjectiveCurve"), Debug)] -pub struct Parameters { +#[derivative(Clone(bound = "C: CurveGroup"), Debug)] +pub struct Parameters { pub generator: C::Affine, pub salt: Option<[u8; 32]>, } -pub type PublicKey = ::Affine; +pub type PublicKey = ::Affine; #[derive(Clone, Default, Debug)] -pub struct SecretKey { +pub struct SecretKey { pub secret_key: C::ScalarField, pub public_key: PublicKey, } -impl ToBytes for SecretKey { - #[inline] - fn write(&self, writer: W) -> IoResult<()> { - self.secret_key.write(writer) - } -} - #[derive(Clone, Default, Debug)] -pub struct Signature { +pub struct Signature { pub prover_response: C::ScalarField, pub verifier_challenge: [u8; 32], } -impl SignatureScheme for Schnorr +impl SignatureScheme for Schnorr where C::ScalarField: PrimeField, { @@ -61,7 +53,7 @@ where // let setup_time = start_timer!(|| "SchnorrSig::Setup"); let salt = None; - let generator = C::prime_subgroup_generator().into(); + let generator = C::generator().into(); // end_timer!(setup_time); Ok(Parameters { generator, salt }) @@ -76,7 +68,10 @@ where // Secret is a random scalar x // the pubkey is y = xG let secret_key = C::ScalarField::rand(rng); - let public_key = parameters.generator.mul(secret_key).into(); + let public_key = parameters + .generator + .mul_bigint(secret_key.into_bigint()) + .into(); // end_timer!(keygen_time); Ok(( @@ -101,7 +96,10 @@ where let random_scalar: C::ScalarField = C::ScalarField::rand(rng); // Commit to the random scalar via r := k ยท G. // This is the prover's first msg in the Sigma protocol. - let prover_commitment = parameters.generator.mul(random_scalar).into_affine(); + let prover_commitment = parameters + .generator + .mul_bigint(random_scalar.into_bigint()) + .into_affine(); // Hash everything to get verifier challenge. // e := H(salt || pubkey || r || msg); @@ -109,8 +107,16 @@ where if parameters.salt != None { hash_input.extend_from_slice(¶meters.salt.unwrap()); } - hash_input.extend_from_slice(&to_bytes![sk.public_key]?); - hash_input.extend_from_slice(&to_bytes![prover_commitment]?); + + let mut pk_bytes = Vec::new(); + sk.public_key.serialize_uncompressed(&mut pk_bytes).unwrap(); + + hash_input.extend_from_slice(&pk_bytes); + let mut prover_commitment_bytes = Vec::new(); + prover_commitment + .serialize_uncompressed(&mut prover_commitment_bytes) + .unwrap(); + hash_input.extend_from_slice(&prover_commitment_bytes); hash_input.extend_from_slice(message); let hash_digest = Blake2s::digest(&hash_input); @@ -150,8 +156,11 @@ where // sG = kG - eY // kG = sG + eY // so we first solve for kG. - let mut claimed_prover_commitment = parameters.generator.mul(*prover_response); - let public_key_times_verifier_challenge = pk.mul(verifier_challenge_fe); + let mut claimed_prover_commitment = parameters + .generator + .mul_bigint(prover_response.into_bigint()); + let public_key_times_verifier_challenge = + pk.mul_bigint(verifier_challenge_fe.into_bigint()); claimed_prover_commitment += &public_key_times_verifier_challenge; let claimed_prover_commitment = claimed_prover_commitment.into_affine(); @@ -160,9 +169,15 @@ where if parameters.salt != None { hash_input.extend_from_slice(¶meters.salt.unwrap()); } - hash_input.extend_from_slice(&to_bytes![pk]?); - hash_input.extend_from_slice(&to_bytes![claimed_prover_commitment]?); - hash_input.extend_from_slice(&message); + let mut pk_bytes = Vec::new(); + pk.serialize_uncompressed(&mut pk_bytes).unwrap(); + hash_input.extend_from_slice(&pk_bytes); + let mut prover_commitment_bytes = Vec::new(); + claimed_prover_commitment + .serialize_uncompressed(&mut prover_commitment_bytes) + .unwrap(); + hash_input.extend_from_slice(&prover_commitment_bytes); + hash_input.extend_from_slice(message); // cast the hash output to get e let obtained_verifier_challenge = &Blake2s::digest(&hash_input)[..]; @@ -184,11 +199,13 @@ pub fn bytes_to_bits(bytes: &[u8]) -> Vec { bits } -impl> +impl> ToConstraintField for Parameters +where + C::Affine: ToConstraintField, { #[inline] fn to_field_elements(&self) -> Option> { - self.generator.into_projective().to_field_elements() + self.generator.to_field_elements() } } diff --git a/simple-payments/src/transaction.rs b/simple-payments/src/transaction.rs index a34f34d..ee3791c 100644 --- a/simple-payments/src/transaction.rs +++ b/simple-payments/src/transaction.rs @@ -33,7 +33,7 @@ impl Transaction { let mut message = self.sender.to_bytes_le(); message.extend(self.recipient.to_bytes_le()); message.extend(self.amount.to_bytes_le()); - Schnorr::verify(&pp, &pub_key, &message, &self.signature).unwrap() + Schnorr::verify(pp, pub_key, &message, &self.signature).unwrap() } /// Check that the transaction is valid for the given ledger state. This checks @@ -53,11 +53,12 @@ impl Transaction { .account_merkle_tree .generate_proof(self.sender.0 as usize) .expect("path should exist"); + path.verify( ¶meters.leaf_crh_params, ¶meters.two_to_one_crh_params, &state.account_merkle_tree.root(), - &sender_acc_info.to_bytes_le(), + sender_acc_info.to_bytes_le(), ) .unwrap() }; @@ -87,7 +88,7 @@ impl Transaction { let mut message = sender.to_bytes_le(); message.extend(recipient.to_bytes_le()); message.extend(amount.to_bytes_le()); - let signature = Schnorr::sign(¶meters.sig_params, &sender_sk, &message, rng).unwrap(); + let signature = Schnorr::sign(¶meters.sig_params, sender_sk, &message, rng).unwrap(); Self { sender, recipient,