Scroll is zero-knowledge (ZK) Ethereum Virtual Machine (EVM) layer 2 that launched in October 2023. Its aim is to scale Ethereum by processing transactions off-chain and generating proofs of their validity. These proofs are then sent to the Ethereum mainnet for verification and inclusion.
It is an an open source, bytecode-compatible which allows developers to deploy pre-existing smart contracts from Ethereum directly on Scroll without modifying the underlying code which helps to achieve an identical development and use experience to Ethereum’s virtual machine and tools with aim of use zk-rollups to tackle Ethereum’s scalability issues.
How Scroll Works
Scroll chain is composed of three layers as mentioned in the above diagram :
Settlement Layer : It enables users and dapps to transfer assets and messages between Ethereum and Scroll, checks validity proofs, and provides data availability and sorting for the core Scroll chain. We implement the rollup contract and bridge onto Ethereum, which we'll use as the Settlement Layer.
Sequencing layer : It is made up of an execution node that carries out the transactions submitted to the L1 bridge contract and the Scroll sequencer, producing L2 blocks, and a rollup node that aggregates transactions, posts block and transaction data to Ethereum for data availability, and sends validity proofs to Ethereum for finality.
Proving Layer : It is made up of a pool of provers that produce the zkEVM validity proofs needed to verify the accuracy of L2 transactions, and an admin who distributes proving tasks to provers and sends the proofs to the Rollup Node for Ethereum finalisation.
Noir
Noir, a domain-specific language designed for SNARK proving systems, distinguishes out due to its simplicity, versatility, and powerful features. It first compiles to an adaptive intermediate language called ACIR.
It simplifies the construction of Solidity contracts that communicate with SNARK systems. Use the nargo codegen-verifier tool to efficiently generate verifier contracts. Noir presently includes a command for creating a Solidity contract that verifies your Noir programme.
Benefits of Using Noir
Multiple Capabilities : Noir stands out for its simplicity, versatility, and powerful features, providing developers with a flexible toolkit for cryptographic programming.
Modular Design : Noir's revolutionary design separates the programming language from the backend, following the modular idea of LLVM. This separation facilitates connection with many backend systems, giving developers more flexibility in selecting and customising proving systems.
Efficient Solidity Contract Creation : Noir makes it easier for Solidity developers to create contracts that interface with SNARK systems. The nargo codegen-verifier tool efficiently generates verifier contracts, and future releases seek to modularize this process for even greater simplicity of usage.
Flexibility : Noir's compilation to a proof-agnostic intermediate language provides unprecedented flexibility for protocol engineers. This enables the integration of various proving systems, allowing engineers to customise the proving system to their individual needs and technology stack.
Custom System Integration : Noir's ability to integrate bespoke proving system backends and smart contract interfaces is useful for blockchain developers. This guarantees easy integration with blockchain architectures, overcomes environmental limits, and expands project innovation potential.
Zk Benfits in Contracts
The field of smart contracts has undergone exciting developments thanks to zero-knowledge proofs (ZKPs), which provide a powerful combination of security, scalability, and privacy. The following are some main advantages of smart contracts that use ZK proofs:
Enhanced transparency : Transactions that are confidential , ZKPs let users demonstrate that they meet specific requirements (such age or income) without disclosing the actual underlying data. This maintains regulatory compliance while safeguarding private data.
Enhanced Scalability : Diminished on-chain data , ZKPs significantly decrease the quantity of data maintained on the blockchain by proving operations off-chain and providing only a brief proof on-chain. Lower fees and quicker transaction processing are the results of this.
Enhanced Safety : ZKPs can be used to demonstrate that a user possesses particular assets or meets eligibility conditions before to interacting with a smart contract, hence preventing fraud. This reduces the possibility of fraud and illegal access.
Zk Proof System in Scroll
Scroll utilizes a Hierarchical Zero-Knowledge Proof System. The system contains 2 separate layers adopting different zero-knowledge proof systems.
The first layer generates proofs for different dApp to ensure fast
proving with short proving keys, and high scalability by using low complexity proofs.
The first requirement enables people to efficiently construct proofs even on their own. This enables privacy-preserving smart contracts in the future with significantly less proving work on the user's part. Users can also outsource proof generation to miners, who will generate proofs effectively without needing to keep big proving keys for multiple DApps. The second and third conditions allow all smart contract logic to be maintained without a trusted setup, regardless of how complex the calculation is. The use of a zero-knowledge proof system in the first layer may sacrifice verification time and proof size in order to achieve these requirements.The second layer then generates proofs from the proofs made in the first layer. Essentially, this layer acts as a wrapping layer. This results in shorter
proofs and a more EVM-compatible proving system.
These two requirements improve on-chain verification efficiency. The wrapping layer serves as a universal on-chain verifier. With this wrapping, the on-chain verification smart contract might become common for a variety of DApps. Miners will create proofs for this layer using a universal proving key. The use of a zero-knowledge proof system may sacrifice proving time in order to meet these requirements.
ZK Circuit Solidity with Noir
Ethereum is one of the well-known blockchain systems that may be used to write smart contracts using Solidity, a high-level programming language.
Building a Quadratic Voting using Zk Proofs
It's time to put the concepts into practice now that you have a solid understanding of them.
Head over to the git repository to get the starter file for your ZK-proof quadratic voting contract which is divided into 2 sub components where one can cast vote and other can count votes.
Note: You can either choose to run the application locally or on GitHub codespace.
Now you have a running workspace with your starter code:
The
noir/cast-ballot/src/main.nr
file is where you have your major thenargo
code that will help one user to cast and generates your zk-proofing smart contract for verifying your voting smart contract and vote the specific user .First, run the command
yarn
to initialize a basic node environment.Next, run the command
curl -L
https://raw.githubusercontent.com/noir-lang/noirup/main/install
| bash
in your terminal.
to install nargo.Reopen a new terminal and run the command
noirup
, thennoirup -v 1.0.0
, to install thenargo version 1.0.0
, as it is the latest confirmed version confirmed to be compatible with the code in this tutorial.Note: Run the code
noir/cast-ballot/src/main.nr
to confirm your running nargo on the rightversion ~1.0.0
use dep::std; global CANDIDATE_COUNT = 10; fn main( token_budget: pub u32, // budget of max votes to enforce votes: [u32;CANDIDATE_COUNT], // ballot of user representing it's votes for each candidate secret: Field // for shielding the commitment from brute-force attacks ) -> pub Field{ check_within_budget(token_budget, votes); calculate_ballot_commitment(votes, secret) } //The proof that we will generate ensures the user respected the token_budget //because we have an explicit constrain here fn check_within_budget(token_budget: u32, votes: [u32;CANDIDATE_COUNT]) { let mut sum: u32 = 0; for i in 0..CANDIDATE_COUNT { sum = sum + votes[i] * votes[i]; } constrain sum <= token_budget; } // Hashes the content of a ballot using pedersen and returns that value fn calculate_ballot_commitment(votes: [u32; CANDIDATE_COUNT], secret: Field) -> Field{ let mut input = [0; CANDIDATE_COUNT + 1]; input[0] = secret; for i in 0..CANDIDATE_COUNT { input[i+1] = votes[i] as Field; } let commitment = std::hash::pedersen(input)[0]; commitment
And if u want to migrate to counting of votes , then the path location is
noir/count-votes/src/main.nr
and repeat the above steps .use dep::std; global CANDIDATE_COUNT = 10; global VOTER_COUNT = 2; fn main( commitments: [Field; VOTER_COUNT], // public commitments for each voter secrets: [Field; VOTER_COUNT], // Each user's ballot secret all_votes: [u32; VOTER_COUNT * CANDIDATE_COUNT] //Flattened array of user's votes. Votes are adjacent for each user ) -> pub [u32; CANDIDATE_COUNT] { check_commitments(commitments, secrets, all_votes); let result = sum_votes(all_votes); result } // Iterates over the flattened array of votes and returns an array // with the sum of votes for each candidate fn sum_votes(all_votes: [u32; VOTER_COUNT * CANDIDATE_COUNT]) -> [u32; CANDIDATE_COUNT] { let mut result = [0; CANDIDATE_COUNT]; for i in 0..VOTER_COUNT { for j in 0..CANDIDATE_COUNT { result[j] = result[j] + all_votes[CANDIDATE_COUNT * i + j]; } }; result } // Recalculate commitment for each user vote and compare it to the provided commitment fn check_commitments( commitments: [Field; VOTER_COUNT], secrets: [Field; VOTER_COUNT], all_votes: [u32; VOTER_COUNT * CANDIDATE_COUNT] ) { let mut input = [0; CANDIDATE_COUNT + 1]; for i in 0..VOTER_COUNT { //get secret for voter i input[0] = secrets[i]; for j in 0..CANDIDATE_COUNT { input[j+1] = all_votes[CANDIDATE_COUNT * i + j] as Field; } // calculate commitment for voter i let commitment = std::hash::pedersen(input)[0]; // verify commitment against input constrain commitment == commitments[i]; }; }
Specifications of Multiple Function
Casting of votes
Defines constants and functions:
CANDIDATE_COUNT
: Sets the number of candidates in the election to 10.main
: Takes three inputs:token_budget
: The maximum number of votes allowed per user.votes
: An array representing the user's votes for each candidate.secret
: A secret value used for privacy protection.
check_within_budget
: Verifies that the total number of votes cast doesn't exceed thetoken_budget
.calculate_ballot_commitment
: Creates a commitment to the user's vote in a way that hides the specific votes but allows verification that the budget constraint was met.
Main function:
Calls
check_within_budget
to ensure the vote adheres to the budget limit.Calls
calculate_ballot_commitment
to create a commitment to the vote.Returns the created commitment.
check_within_budget
function:
Calculates the sum of squared votes (not the actual number of votes).
Uses a mathematical statement (
constrain
) to verify that the sum is less than or equal to thetoken_budget
.
calculate_ballot_commitment
function:
Combines the
secret
value with the votes (converted to a specific data type) into a single array.Uses a cryptographic hash function called
pedersen
to create a commitment from the combined array.Returns the generated commitment.
- Counting of Votes
Imports and Globals:
dep::std
: Imports standard library functions (likely using a custom library nameddep
).CANDIDATE_COUNT
: Defines the number of candidates in the election.VOTER_COUNT
: Defines the number of voters participating.
Main Function:
Takes three arguments:
commitments
: An array of public commitments for each voter.secrets
: An array of secret ballots for each voter.all_votes
: A flattened array containing all votes for each candidate, organized by voter.
Calls
check_commitments
to verify the integrity of the votes.Calls
sum_votes
to count the votes for each candidate.Returns the final vote count for each candidate.
Sum_Votes Function:
Takes the flattened array of votes as input.
Initializes an array
result
with zeros for each candidate.Iterates through each voter and candidate:
- Adds the corresponding vote from
all_votes
to theresult
for that candidate.
- Adds the corresponding vote from
Returns the array
result
containing the summed votes for each candidate.
Check_Commitments Function:
Takes the commitments, secrets, and all votes as input.
Initializes an array
input
to store the secret and each vote for a specific voter.Iterates through each voter:
Sets
input[0]
to the voter's secret from thesecrets
array.Sets
input[j+1]
to the corresponding vote for each candidate (j) from theall_votes
array, converting it to aField
type.Calculates a commitment for the voter using a
pedersen
hashing function on theinput
array.Constrains the calculated commitment to match the provided commitment for that voter in the
commitments
array.
This ensures that the votes used for counting match the commitments previously published by each voter, upholding privacy while verifying the integrity of their votes.
Deploying on Scroll
To deploy your contract on the scroll sapolia test network
Head over to the link to get scroll sapolia faucets (https://sepoliafaucet.com/)
Run forge
create --rpc-url forge create --rpc-url
https://alpha-rpc.scroll.io/l2
src/zkVoting.sol:Voting --private-key <enter your PRIVATE_KEY>
, to install your private key and scroll's rpc URL in your environment.Finally, run the command
forge script scripts/Voting.s.sol:DeploymentScript --rpc-url
https://sepolia-rpc.scroll.io
--broadcast --verify -vvvv
to deploy to scroll and you will get a response like the image below.
Conclusion
ZK technology presents an achievable approach for resolving blockchain scaling issues, and Scroll is essential to the integration of this technology into Ethereum to ensure that ZKPs are widely adopted, however, and to solve the technological challenges, more research and development are required.
Other ZK-rollups methods are under development, each with advantages and disadvantages of its own. Ethereum's long-term scalability is likely to depend on a variety of strategies, such as ZK-rollups, more layer-2 solutions, and perhaps updates to the Ethereum protocol itself.