Commit 16e13b9a authored by John Doe's avatar John Doe
Browse files

Recursion.

parent e862f20f
No related merge requests found
Pipeline #38 canceled with stages
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.env
.tmp
yarn.lock
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# artifacts
proofs
# other
.vscode
crs
.DS_Store
artifacts
cache
.next
# cached local data
outputs.json
batch.json
\ No newline at end of file
# Add files here to ignore them from prettier formatting
/dist
/coverage
{
"arrowParens": "avoid",
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"proseWrap": "always"
}
# Momiji Batching Test 1
# Momiji Testnet v2.0
In the second release of the Momiji testnet, we have made the following upgrades
- Circuits built using Nargo 11.0 and bb.js 0.7.2
- Separate transaction batching and publishing in `TechnicalPreview.sol`
- Recursive proof generation for batched transactions, with final verification done on-chain using `plonk_vk.sol`
- Expansion of withdrawals up to 16 recipient addresses
## Recursive proofs with Noir
This testnet update features recursive proving in Noir, below is a general outline of the steps taken in generating and verifying recursive proofs on chain
- The user creates an inner proof using the compiled transaction circuit in `./circuits/main/`
- This proof is then serialized into field elements and passed as an input to the recursive prover in `./circuits/recursion`
- The outer proof and the aggregation object generated by the recursive circuit are passed to `plonk_vk.sol` for final verification
## Getting Started
1. [Install nargo](https://noir-lang.org/getting_started/nargo_installation#option-1-noirup) version 0.11.0 with `noirup -v 0.11.0`
2. Install dependencies with
```bash
yarn
```
## Testing
The [example test file](./test/index.ts) executes a deposit and withdrawal on-chain in a typescript `node.js` environment.
You can run the tests with:
```sh
yarn bbjs
yarn test
```
[package]
name = "main"
type = "bin"
authors = [""]
compiler_version = "0.9.0"
[dependencies]
This diff is collapsed.
use dep::std;
global state_depth = 9;
global utxo_depth = 4;
global batch_depth = 4;
fn main(
current_root: pub Field,
deposit: pub Field,
withdrawals: pub [Field; 16],
commitment_out: pub [Field; 16],
recipients: pub [Field; 16],
oracle: pub Field,
old_root_proof: [Field; 16],
nullifier_hashes: pub [Field; 16],
secrets: [Field; 16],
utxo_in: [Field; 48],
utxo_out: [Field; 48],
indexes: [Field; 48],
hash_path: [Field; 272],
) -> pub Field {
let trees: Field = 4;
let mut sum_in: Field = deposit;
let mut sum_out: Field = withdrawals.reduce(|a, b| a + b);
for i in 0..16 {
if (utxo_in[i*3 + 1] != 0) {
let owner = std::hash::pedersen([secrets[i]]);
assert(owner[0] == utxo_in[i*3 + 0]);
assert(nullifier_hashes[i] == std::hash::pedersen([secrets[i], secrets[i]])[0]);
let commitment_in = std::hash::pedersen([utxo_in[i*3 + 0], utxo_in[i*3 + 1], utxo_in[i*3 + 2]])[0];
let mut hash_path_utxo: [Field; utxo_depth] = [0; utxo_depth];
let mut hash_path_tx: [Field; batch_depth] = [0; batch_depth];
let mut hash_path_historic: [Field; state_depth] = [0; state_depth];
for j in 0..4 {
hash_path_utxo[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + j];
hash_path_tx[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + j];
}
for l in 0..state_depth {
hash_path_historic[l] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + batch_depth + l];
}
let index_utxo = indexes[trees * i + 0];
let index_tx = indexes[trees * i + 1];
let index_historic = indexes[trees * i + 2];
let utxo_root = std::merkle::compute_merkle_root(
commitment_in,
index_utxo,
hash_path_utxo
);
let tx_root = std::merkle::compute_merkle_root(
utxo_root,
index_tx,
hash_path_tx
);
let leaf_batch = std::hash::pedersen([tx_root, oracle])[0];
let leaf_historic = std::hash::pedersen([leaf_batch, old_root_proof[i]])[0];
let historic_root = std::merkle::compute_merkle_root(
leaf_historic,
index_historic,
hash_path_historic
);
assert(historic_root == current_root);
sum_in += utxo_in[i*3 + 1];
}
}
for k in 0..16 {
if (utxo_out[k*3 + 1] != 0) {
let commitment_out_calc = std::hash::pedersen([utxo_out[k*3 + 0], utxo_out[k*3 + 1], utxo_out[k*3 + 2]]);
assert(commitment_out_calc[0] == commitment_out[k]);
sum_out += utxo_out[k*3 + 1];
}
else {
let zero_hash = 0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725 as Field;
assert(commitment_out[k] == zero_hash);
}
}
assert(sum_in == sum_out);
let utxo_root_calc: Field = pedersen_tree_four(commitment_out);
utxo_root_calc
}
fn pedersen_tree_four(leaves: [Field; 16]) -> Field {
let mut tx_tree: [Field; 16] = leaves;
for l in 0..8 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
for l in 0..4 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
for l in 0..2 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
std::hash::pedersen([tx_tree[0], tx_tree[1]])[0]
}
\ No newline at end of file
This diff is collapsed.
[package]
name = "recursion"
type = "bin"
authors = [""]
compiler_version = "0.9.0"
[dependencies]
This diff is collapsed.
use dep::std;
fn main(
verification_key : [Field; 114],
proof : [Field; 161],
public_inputs : [Field; 68],
input_aggregation_object: [Field; 16],
key_hash : pub Field,
tx_ids: pub [Field; 16],
old_root: pub Field,
new_root: pub Field,
oracle: pub Field
) -> pub [Field; 16] {
let tx_root_calc: Field = pedersen_tree_four(tx_ids);
assert(oracle == std::hash::pedersen([0])[0]);
let batch_root_calc: Field = std::hash::pedersen([tx_root_calc, oracle])[0];
let new_root_calc: Field = std::hash::pedersen([batch_root_calc, old_root])[0];
assert(new_root == new_root_calc);
let vk: [Field] = verification_key;
let p: [Field] = proof;
let pi: [Field] = public_inputs;
std::verify_proof(
vk,
p,
pi,
key_hash,
input_aggregation_object
)
}
fn pedersen_tree_four(leaves: [Field; 16]) -> Field {
let mut tx_tree: [Field; 16] = leaves;
for l in 0..8 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
for l in 0..4 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
for l in 0..2 { tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0]; }
std::hash::pedersen([tx_tree[0], tx_tree[1]])[0]
}
\ No newline at end of file
{"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"verification_key","type":{"kind":"array","length":114,"type":{"kind":"field"}},"visibility":"private"},{"name":"proof","type":{"kind":"array","length":161,"type":{"kind":"field"}},"visibility":"private"},{"name":"public_inputs","type":{"kind":"array","length":68,"type":{"kind":"field"}},"visibility":"private"},{"name":"input_aggregation_object","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"private"},{"name":"key_hash","type":{"kind":"field"},"visibility":"public"},{"name":"tx_ids","type":{"kind":"array","length":16,"type":{"kind":"field"}},"visibility":"public"},{"name":"old_root","type":{"kind":"field"},"visibility":"public"},{"name":"new_root","type":{"kind":"field"},"visibility":"public"},{"name":"oracle","type":{"kind":"field"},"visibility":"public"}],"param_witnesses":{"input_aggregation_object":[344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359],"key_hash":[360],"new_root":[378],"old_root":[377],"oracle":[379],"proof":[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275],"public_inputs":[276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343],"tx_ids":[361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376],"verification_key":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114]},"return_type":{"kind":"array","length":16,"type":{"kind":"field"}},"return_witnesses":[417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432]},"bytecode":"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"}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IElasticERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
// Additional mint and burn functions
function burn(address account, uint256 amount) external;
function mint(address account, uint256 amount) external;
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./Interfaces/IElasticERC20.sol";
struct Transaction {
bytes32 id;
bytes32[16] commitments;
bytes32[16] nullifier_hashes;
bytes32[16] recipients;
bytes32[16] withdrawals;
bytes32 deposit;
bytes32[161] proof;
bytes32[16] aggregation_object;
}
struct BatchPublicInputs {
bytes32 key_hash;
bytes32 oracle;
bytes32 old_root;
bytes32 new_root;
Transaction[] transactions;
}
interface IVerifier {
function verify(
bytes calldata,
bytes32[] calldata
) external view returns (bool);
function getVerificationKeyHash() external pure returns (bytes32);
}
contract TechnicalPreview {
IVerifier public verifier;
IElasticERC20 public token;
bytes32[] public validRoots;
bytes32 public keyHash;
uint256 public MAX_FIELD_SIZE = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000;
bytes32 public ZERO_VALUE = 0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720;
mapping(bytes32 => bytes32) public utxoPrevRoots;
mapping(bytes32 => bool) public nullifierHashes;
mapping(bytes32 => bool) public commitments;
mapping(bytes32 => bytes32[]) public utxo;
uint256 public MAX_ITEMS = 16;
mapping(uint256 => Transaction[]) public batch;
uint256 public batchNumber = 0;
bytes32 public merkleRoot = ZERO_VALUE;
constructor(IVerifier _verifier, address _token, bytes32 _keyHash) {
verifier = _verifier;
token = IElasticERC20(_token);
validRoots.push(merkleRoot);
keyHash = _keyHash;
}
function getLatestAggregationObject() public view returns (bytes32[16] memory) {
return batch[batchNumber][batch[batchNumber].length - 1].aggregation_object;
}
function getSpentNullifiers(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) {
uint256 nullifierHashesLength = _nullifierHashes.length;
spent = new bool[](nullifierHashesLength);
for (uint256 i; i < nullifierHashesLength; i++) {
if (isSpent(_nullifierHashes[i])) {
spent[i] = true;
}
}
}
function getCommitment(bytes32 _commitment) public view returns (bool) {
return commitments[_commitment];
}
function getUtxoFromRoot(bytes32 _root) public view returns (bytes32[] memory) {
return utxo[_root];
}
function getRootFromUtxo(bytes32 _utxo) public view returns (bytes32) {
return utxoPrevRoots[_utxo];
}
function getValidRoots() public view returns (bytes32[] memory) {
return validRoots;
}
function getCurrentBatch() public view returns (Transaction[] memory) {
return batch[batchNumber];
}
function enqueue(Transaction calldata _tx) public {
require(MAX_ITEMS > batch[batchNumber].length, "queue is full");
batch[batchNumber].push(_tx);
for (uint256 i = 0; i < 16; i++) {
if (_tx.commitments[i] != ZERO_VALUE) {
require(!commitments[_tx.commitments[i]], "commitment exists");
commitments[_tx.commitments[i]] = true;
}
}
}
function publish(
bytes calldata _proof,
BatchPublicInputs calldata _batch
) public payable {
require(uint256(_batch.old_root) == uint256(merkleRoot), "invalid root");
BatchPublicInputs memory batchPublicInputs = _batch;
batchPublicInputs.transactions = batch[batchNumber];
for (uint256 i = 0; i < batchPublicInputs.transactions.length; i++) {
for (uint256 j = 0; j < 16; j++) {
if (batchPublicInputs.transactions[i].nullifier_hashes[j] != ZERO_VALUE) {
require(!nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]], "nullifier spent");
nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]] = true;
}
if (batchPublicInputs.transactions[i].commitments[j] != ZERO_VALUE) {
utxo[batchPublicInputs.old_root].push(batchPublicInputs.transactions[i].commitments[j]);
utxoPrevRoots[batchPublicInputs.transactions[i].commitments[j]] = batchPublicInputs.old_root;
}
if (batchPublicInputs.transactions[i].recipients[j] != ZERO_VALUE) {
token.mint(address(uint160(uint256(batchPublicInputs.transactions[i].recipients[j]))), uint256(batchPublicInputs.transactions[i].withdrawals[j]));
}
}
}
validRoots.push(batchPublicInputs.new_root);
merkleRoot = batchPublicInputs.new_root;
batchNumber++;
require(verifier.verify(_proof, prepareBatchPublicInputs(_batch)), "invalid proof");
}
function dropQueue(bytes calldata _preimage) public {
require(keccak256(_preimage) > 0xff00000000000000000000000000000000000000000000000000000000000000);
for (uint256 i = 0; i < MAX_ITEMS; i++) {
for (uint256 j = 0; j < 16; j++) {
commitments[batch[batchNumber][i].commitments[j]] = false;
}
}
batchNumber++;
}
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
return nullifierHashes[_nullifierHash];
}
function verifyProof(
bytes calldata _proof,
bytes32[] memory _publicInputs
) public view returns (bool) {
return verifier.verify(_proof, _publicInputs);
}
function prepareBatchPublicInputs(BatchPublicInputs memory input) public view returns (bytes32[] memory) {
bytes32[] memory flatArray = new bytes32[](36);
uint256 idx = 0;
flatArray[idx++] = keyHash;
for (uint256 i = 0; i < 16; i++) {
if (i < input.transactions.length) {
flatArray[idx++] = input.transactions[i].id;
} else {
flatArray[idx++] = ZERO_VALUE;
}
}
flatArray[idx++] = input.old_root;
flatArray[idx++] = input.new_root;
flatArray[idx++] = input.oracle;
for (uint256 i = 0; i < 16; i++ ) {
flatArray[idx++] = input.transactions[(input.transactions.length - 1)].aggregation_object[i];
}
for (uint256 i = 0; i < flatArray.length; i++) {
require(uint256(flatArray[i]) < 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, "too large!");
}
return flatArray;
}
}
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract XFTMock is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
constructor(string memory _vk) ERC20(_vk, _vk) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
_mint(to, amount);
}
function burn(address from, uint256 amount) public {
require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner");
_burn(from, amount);
}
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment