Commit 1f63d62c authored by John Doe's avatar John Doe
Browse files

Sepolia Testnet Launch

parent ed26b9da
No related merge requests found
Pipeline #35 canceled with stages
node_modules
notes.sqlite
package-lock.json
txs.db
.env
\ No newline at end of file
[package]
name = "main"
type = "bin"
authors = [""]
compiler_version = "0.10.3"
[dependencies]
\ No newline at end of file
# Momiji Sepolia 1
import inquirer from 'inquirer';
import { deposit, transact, balance } from '../script/zk.js'
import { ethers } from 'ethers';
let trees;
let data;
export const serviceController = {
error: "",
balance: balance,
deposit: async () => {
let amountPrompt = await inquirer.prompt({
type: "input",
name: "amount",
message: "Enter the amount of XFT to deposit: "
})
if (isNaN(amountPrompt.amount)) {
let errorMsg = "No amount specified";
serviceController.error = errorMsg;
console.log(errorMsg);
return;
}
const arg = { amount: ethers.parseEther(amountPrompt.amount) };
[trees, data] = await deposit(arg);
},
withdraw: async () => {
let amountPrompt = await inquirer.prompt({
type: "input",
name: "amount",
message: "Enter the amount of XFT to withdraw: "
})
if (isNaN(amountPrompt.amount)) {
let errorMsg = "No amount specified";
serviceController.error = errorMsg;
console.log(errorMsg);
return;
}
const args = { amount: ethers.parseEther(amountPrompt.amount), trees: trees, data: data };
await transact(args);
}
}
export const commandController = {
'd': serviceController.deposit,
'w': serviceController.withdraw,
'r': () => true,
'exit': process.exit
}
\ No newline at end of file
{
"_format": "hh-sol-artifact-1",
"contractName": "TechnicalPreview",
"sourceName": "contracts/TechnicalPreview.sol",
"abi": [
{
"inputs": [
{
"internalType": "contract IVerifier",
"name": "_verifier",
"type": "address"
},
{
"internalType": "address",
"name": "_token",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "MAX_FIELD_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "commitments",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "bytes32[16]",
"name": "tx_in",
"type": "bytes32[16]"
},
{
"internalType": "bytes32",
"name": "current_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "amount_public_in",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "amount_public_out",
"type": "bytes32"
},
{
"internalType": "bytes32[16]",
"name": "commitment_out",
"type": "bytes32[16]"
},
{
"internalType": "bytes32",
"name": "recipient",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "oracle",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "old_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "new_root",
"type": "bytes32"
},
{
"internalType": "bytes32[16]",
"name": "nullifier_hashes",
"type": "bytes32[16]"
}
],
"internalType": "struct BatchPublicInputs",
"name": "input",
"type": "tuple"
}
],
"name": "flattenBatchPublicInputs",
"outputs": [
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_commitment",
"type": "bytes32"
}
],
"name": "getCommitment",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_utxo",
"type": "bytes32"
}
],
"name": "getRootFromUtxo",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_nullifierHashes",
"type": "bytes32[]"
}
],
"name": "getSpentNullifiers",
"outputs": [
{
"internalType": "bool[]",
"name": "spent",
"type": "bool[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
}
],
"name": "getUtxoFromRoot",
"outputs": [
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getValidRoots",
"outputs": [
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_nullifierHash",
"type": "bytes32"
}
],
"name": "isSpent",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "merkleRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "nullifierHashes",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"components": [
{
"internalType": "bytes32[16]",
"name": "tx_in",
"type": "bytes32[16]"
},
{
"internalType": "bytes32",
"name": "current_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "amount_public_in",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "amount_public_out",
"type": "bytes32"
},
{
"internalType": "bytes32[16]",
"name": "commitment_out",
"type": "bytes32[16]"
},
{
"internalType": "bytes32",
"name": "recipient",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "oracle",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "old_root",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "new_root",
"type": "bytes32"
},
{
"internalType": "bytes32[16]",
"name": "nullifier_hashes",
"type": "bytes32[16]"
}
],
"internalType": "struct BatchPublicInputs",
"name": "_batch",
"type": "tuple"
}
],
"name": "publish",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "utxo",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "utxoPrevRoots",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "validRoots",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "verifier",
"outputs": [
{
"internalType": "contract IVerifier",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32[]",
"name": "_publicInputs",
"type": "bytes32[]"
}
],
"name": "verifyProof",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
}
\ No newline at end of file
{
"_format": "hh-sol-artifact-1",
"contractName": "UltraVerifier",
"sourceName": "contracts/plonk_vk.sol",
"abi": [
{
"inputs": [],
"name": "EC_SCALAR_MUL_FAILURE",
"type": "error"
},
{
"inputs": [],
"name": "MOD_EXP_FAILURE",
"type": "error"
},
{
"inputs": [],
"name": "PROOF_FAILURE",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "expected",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "actual",
"type": "uint256"
}
],
"name": "PUBLIC_INPUT_COUNT_INVALID",
"type": "error"
},
{
"inputs": [],
"name": "PUBLIC_INPUT_GE_P",
"type": "error"
},
{
"inputs": [],
"name": "PUBLIC_INPUT_INVALID_BN128_G1_POINT",
"type": "error"
},
{
"inputs": [],
"name": "getVerificationKeyHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
},
{
"internalType": "bytes32[]",
"name": "_publicInputs",
"type": "bytes32[]"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
}
import inquirer from 'inquirer';
import { serviceController, commandController } from './controllers/services.mjs'
const mainMenu = async () => {
let answers = await inquirer.prompt([{
type: "list",
name: "menu",
message: `
Momiji CLI (PureL1Rollup Sepolia Technical Preview #1)
Balance: ${await serviceController.balance()}
\x1b[31m${serviceController.error}\x1b[0m
Select an option:\n`,
choices: [
{
value: 'd',
name: "\tGENERATE ZKXFT",
short: "\n\tShifting to zkXFT"
},
{
value: 'w',
name: "\tREDEEM ZKXFT FOR XFT",
short: "\n\tShifting back to XFT"
},
{
value: 'exit',
name: "\tEXIT\n",
}
]
}])
serviceController.error = "";
await commandController[answers.menu]();
mainMenu();
}
mainMenu();
\ No newline at end of file
{
"name": "momiji-cli",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@aztec/bb.js": "0.3.6",
"@ethersproject/abi": "^5.6.4",
"@ethersproject/providers": "^5.6.8",
"@noir-lang/noir-source-resolver": "1.1.1",
"bigint-buffer": "^1.1.5",
"circomlibjs": "^0.1.7",
"dotenv": "^16.3.1",
"ethers": "^6.6.7",
"inquirer": "^9.2.10",
"keccak256": "^1.0.6",
"sequelize": "^6.32.0",
"sqlite3": "^5.1.6"
}
}
// @ts-ignore -- no types
import { Fr } from '@aztec/bb.js/dest/node/types/index.js';
// @ts-ignore -- no types
export function pedersenLeftRight(
barretenberg,
left,
right) {
let leftBuffer = Fr.fromBufferReduce(Buffer.from(left.slice(2), 'hex'));
let rightBuffer = Fr.fromBufferReduce(Buffer.from(right.slice(2), 'hex'));
let hashRes = barretenberg.pedersenPlookupCompressFields(leftBuffer, rightBuffer);
return hashRes.toString('hex')
}
export class MerkleTree {
zeroValue = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; // sha256("Momiji")
levels;
hashLeftRight;
storage;
zeros;
totalLeaves;
barretenberg;
constructor(
levels,
barretenberg,
defaultLeaves = [],
hashLeftRight = pedersenLeftRight) {
this.levels = levels;
this.hashLeftRight = hashLeftRight;
this.storage = new Map();
this.zeros = [];
this.totalLeaves = 0;
this.barretenberg = barretenberg;
// build zeros depends on tree levels
let currentZero = this.zeroValue;
this.zeros.push(currentZero);
for (let i = 0; i < levels; i++) {
currentZero = this.hashLeftRight(barretenberg, currentZero, currentZero);
this.zeros.push(currentZero);
}
if (defaultLeaves.length > 0) {
this.totalLeaves = defaultLeaves.length;
// store leaves with key value pair
let level = 0;
defaultLeaves.forEach((leaf, index) => {
this.storage.set(MerkleTree.indexToKey(level, index), leaf);
});
// build tree with initial leaves
level++;
let numberOfNodesInLevel = Math.ceil(this.totalLeaves / 2);
for (level; level <= this.levels; level++) {
for (let i = 0; i < numberOfNodesInLevel; i++) {
const leftKey = MerkleTree.indexToKey(level - 1, 2 * i);
const rightKey = MerkleTree.indexToKey(level - 1, 2 * i + 1);
const left = this.storage.get(leftKey);
const right = this.storage.get(rightKey) || this.zeros[level - 1];
if (!left) throw new Error("leftKey not found");
const node = this.hashLeftRight(barretenberg, left, right);
this.storage.set(MerkleTree.indexToKey(level, i), node);
}
numberOfNodesInLevel = Math.ceil(numberOfNodesInLevel / 2);
}
}
}
static indexToKey(level, index) {
return `${level}-${index}`;
}
getIndex(leaf) {
for (const [key, value] of this.storage) {
if (value === leaf) {
return Number(key.split("-")[1]);
}
}
return -1;
}
root() {
return this.storage.get(MerkleTree.indexToKey(this.levels, 0)) || this.zeros[this.levels];
}
proof(indexOfLeaf) {
let pathElements = [];
let pathIndices = [];
const leaf = this.storage.get(MerkleTree.indexToKey(0, indexOfLeaf));
if (!leaf) throw new Error("leaf not found");
// store sibling into pathElements and target's indices into pathIndices
const handleIndex = (level, currentIndex, siblingIndex) => {
const siblingValue = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
pathElements.push(siblingValue);
pathIndices.push(currentIndex % 2);
};
this.traverse(indexOfLeaf, handleIndex);
return {
root: this.root(),
pathElements,
pathIndices,
leaf: leaf,
};
}
insert(leaf) {
const index = this.totalLeaves;
this.update(index, leaf, true);
this.totalLeaves++;
}
update(index, newLeaf, isInsert = false) {
if (!isInsert && index >= this.totalLeaves) {
throw Error("Use insert method for new elements.");
} else if (isInsert && index < this.totalLeaves) {
throw Error("Use update method for existing elements.");
}
let keyValueToStore = [];
let currentElement = newLeaf;
const handleIndex = (level, currentIndex, siblingIndex) => {
const siblingElement = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
let left;
let right;
if (currentIndex % 2 === 0) {
left = currentElement;
right = siblingElement;
} else {
left = siblingElement;
right = currentElement;
}
keyValueToStore.push({
key: MerkleTree.indexToKey(level, currentIndex),
value: currentElement,
});
currentElement = this.hashLeftRight(this.barretenberg, left, right);
};
this.traverse(index, handleIndex);
// push root to the end
keyValueToStore.push({
key: MerkleTree.indexToKey(this.levels, 0),
value: currentElement,
});
keyValueToStore.forEach(o => {
this.storage.set(o.key, o.value);
});
}
// traverse from leaf to root with handler for target node and sibling node
traverse(indexOfLeaf, handler) {
let currentIndex = indexOfLeaf;
for (let i = 0; i < this.levels; i++) {
let siblingIndex;
if (currentIndex % 2 === 0) {
siblingIndex = currentIndex + 1;
} else {
siblingIndex = currentIndex - 1;
}
handler(i, currentIndex, siblingIndex);
currentIndex = Math.floor(currentIndex / 2);
}
}
}
\ No newline at end of file
import { randomBytes } from 'crypto'
import { readFileSync } from 'fs';
import { Fr } from '@aztec/bb.js/dest/node/types/index.js';
import { MerkleTree } from './MerkleTree.mjs';
import { keccak256 } from "@ethersproject/keccak256/lib/index.js";
import fs from 'fs'
const ZERO_VALUE = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725";
const MAX_VALUE = Fr.MAX_VALUE;
export const treeConfig = {
utxoDepth: 4,
txDepth: 4,
stateDepth: 9
}
export const circuitConfig = {
main: "./src/main.nr"
}
const treeSum = [treeConfig.utxoDepth, treeConfig.txDepth, treeConfig.stateDepth].reduce((a, b) => a + b)
function evalWithScopePrefix(js) {
let public_inputs = {};
js.split('\n').forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine.length > 0) {
eval(`public_inputs.` + trimmedLine);
}
});
return public_inputs;
}
export function getInputsObject() {
let regex = /(.*)\: (pub )?(\[.*\; (.*)?\],)?.*/;
let circuit = fs.readFileSync(circuitConfig.main).toString();
let inputs = {}
let input_ordering = []
let structLine = circuit.split('\n').find(line => line.includes('fn main'));
let circuitSplit = circuit.split('\n');
let structLines = circuitSplit.slice(circuitSplit.indexOf(structLine) + 1, circuitSplit.indexOf(circuitSplit.find(line => line.includes(') {'))));
let line_maps = structLines.map(line => line.match(regex)).filter(x => x !== null);
line_maps.forEach(line_map => {
inputs[line_map[1].trim()] = [(line_map[2] ? true : false), (line_map[4] ? line_map[4] : 1)];
input_ordering.push(line_map[1].trim());
});
return [inputs, input_ordering];
}
export function getInputs() {
let inputs = getInputsObject()[0];
return inputs;
}
export function getInputsOrdered(pub) {
let inputsObj = getInputsObject();
let inputs = inputsObj[1];
let input_ordering = inputs;
if (pub) input_ordering = input_ordering.map(input => inputsObj[0][input][0] ? input : null).filter(x => x !== null);
return input_ordering;
}
export function getPublicInputsOrdered() {
return getInputsOrdered(true);
}
export function readToml(path) {
let public_inputs = getPublicInputsOrdered();
let unordered_inputs = evalWithScopePrefix(fs.readFileSync(path).toString());
let ordered_inputs = public_inputs.map(input => unordered_inputs[input]);
return ordered_inputs;
}
export function randomBytesFr(numBytes) {
const bytes = randomBytes(numBytes)
const bytesFr = Fr.fromBufferReduce(bytes)
return bytesFr
}
export const format = (data) => {
if (typeof data === "string") return `"${data}"`;
if (data.length === 0) return "[]";
return `[\n "${data.join('",\n "')}"\n]`;
}
export const dumpToml = (data) => {
let public_inputs = getInputsOrdered();
let toml = [];
public_inputs.forEach((input) => {
toml.push(` ${input} = ${format(data[input])}`);
});
toml = toml.join('\n');
fs.writeFileSync('./Prover.toml', toml);
}
export const dumpSolidity = () => {
let contract = fs.readFileSync("./contracts/TechnicalPreview.sol").toString();
let structLine = contract.split('\n').find(line => line.includes('BatchPublicInputs'));
let public_inputs = getInputsOrdered(true);
let public_inputs_sizes = public_inputs.map(input => getInputs()[input][1]);
let new_struct = `struct BatchPublicInputs {`;
public_inputs.forEach((input, index) => {
new_struct += `\n bytes32${public_inputs_sizes[index] !== 1 ? `[${public_inputs_sizes[index]}]` : ''} ${input};`;
});
contract = contract.replace(structLine, new_struct);
let flattenLine = contract.split('\n').find(line => line.includes('function flattenBatchPublicInputs(BatchPublicInputs memory input) public pure returns (bytes32[] memory) {'));
let flattenLineIndex = contract.split('\n').indexOf(flattenLine);
let flatArrayLine = ` bytes32[] memory flatArray = new bytes32[](${public_inputs_sizes.reduce((a, b) => parseInt(a) + parseInt(b))});`;
contract = contract.split('\n').slice(0, flattenLineIndex + 1).join('\n') + '\n' + flatArrayLine + '\n' + contract.split('\n').slice(flattenLineIndex + 1).join('\n');
let idxLine = ` uint256 idx = 0;\n`;
contract = contract.split('\n').slice(0, flattenLineIndex + 2).join('\n') + '\n' + idxLine + '\n' + contract.split('\n').slice(flattenLineIndex + 2).join('\n');
let forLoopLines = [];
public_inputs.forEach((input, index) => {
if (public_inputs_sizes[index] === 1) {
forLoopLines.push(` flatArray[idx++] = input.${input};`);
} else {
forLoopLines.push(` for (uint i = 0; i < ${public_inputs_sizes[index]}; i++) flatArray[idx++] = input.${input}[i];`);
}
});
contract = contract.split('\n').slice(0, flattenLineIndex + 3).join('\n') + '\n' + forLoopLines.join('\n') + '\n' + contract.split('\n').slice(flattenLineIndex + 3).join('\n');
fs.writeFileSync('./contracts/TechnicalPreview.sol', contract);
}
export function path_to_uint8array(path) {
let buffer = readFileSync(path);
return new Uint8Array(buffer);
}
const toFixedHex = (number, pad0x, length = 32) => {
let hexString = number.toString(16).padStart(length * 2, '0');
return (pad0x ? `0x` + hexString : hexString);
}
export function getSolidityHash(asset) {
return keccak256(asset);
}
export function generateHashPathInput(hash_path) {
let hash_path_input = [];
for (var i = 0; i < hash_path.length; i++) {
hash_path_input.push(`0x` + hash_path[i]);
}
return hash_path_input;
}
export function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) {
let utxos = []
for (let i = 0; i < batchSize; i++) {
let amountBN = amounts[i]
let utxo = {
secret: _secrets[i],
owner: BarretenbergApi.pedersenPlookupCompress([_secrets[i]]),
amountBN: amountBN,
amount: Fr.fromString(toFixedHex(Number(amountBN.toString()), true)),
assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), 'hex')),
}
utxos.push(utxo)
}
return utxos
}
export async function generateTreeProof(utxoIn, BarretenbergApi, contract) {
let trees = {
utxo_tree: new MerkleTree(treeConfig.utxoDepth, BarretenbergApi),
tx_tree: new MerkleTree(treeConfig.txDepth, BarretenbergApi),
historic_tree: new MerkleTree(treeConfig.stateDepth, BarretenbergApi),
}
let commitment = BarretenbergApi.pedersenPlookupCompress(
[utxoIn.owner, utxoIn.amount, utxoIn.assetType]
).toString()
let old_root = await contract.getRootFromUtxo(commitment)
let utxo_list = await contract.getUtxoFromRoot(old_root)
let historic_roots = await contract.getValidRoots()
let oracle = BarretenbergApi.pedersenPlookupCompress([new Fr(0n)])
for (let i = 0; i < utxo_list.length; i++) {
trees.utxo_tree.insert(utxo_list[i]);
}
let utxo_root = trees.utxo_tree.root()
trees.tx_tree.insert(utxo_root);
let tx_root_Fr = Fr.fromString(trees.tx_tree.root())
let batch = BarretenbergApi.pedersenPlookupCompress([tx_root_Fr, oracle])
let new_root = BarretenbergApi.pedersenPlookupCompress([batch, Fr.fromString(old_root)]).toString()
for (let i = 0; i < historic_roots.length; i++) {
trees.historic_tree.insert(historic_roots[i]);
}
let proofs = {
utxo: {
leaf: commitment,
index: trees.utxo_tree.getIndex(commitment),
root: utxo_root,
hash_path: trees.utxo_tree.proof(
trees.utxo_tree.getIndex(commitment)
).pathElements
},
tx: {
leaf: utxo_root,
index: trees.tx_tree.getIndex(utxo_root),
root: trees.tx_tree.root(),
hash_path: trees.tx_tree.proof(
trees.tx_tree.getIndex(utxo_root)
).pathElements
},
historic: {
leaf: new_root,
index: trees.historic_tree.getIndex(new_root),
root: trees.historic_tree.root(),
hash_path: trees.historic_tree.proof(
trees.historic_tree.getIndex(new_root)
).pathElements
}
}
return proofs
}
export function generateDataToml(oldRoot, newRoot, trees, api) {
let zeroHash = api.pedersenPlookupCompress([Fr.fromString(toFixedHex(0, true))])
const data = {
tx_in: new Array(16).fill(ZERO_VALUE),
secrets: new Array(16).fill('0'),
utxo_in: new Array(48).fill('0'),
utxo_out: new Array(48).fill('0'),
oracle: zeroHash.toString(),
old_root_proof: new Array(16).fill('0'),
old_root: ZERO_VALUE,
new_root: ZERO_VALUE,
current_root: trees.historic_tree.root(),
indexes: new Array(48).fill('0'),
hash_path: new Array(16 * treeSum).fill('0'),
commitment_out: new Array(16).fill(ZERO_VALUE),
amount_public_in: "0",
amount_public_out: "0",
nullifier_hashes: new Array(16).fill('0'),
recipient: Fr.fromString(toFixedHex(0, true)).toString()
}
if (oldRoot !== "0") data.old_root = oldRoot;
if (newRoot !== "0") data.new_root = newRoot;
return data
}
export async function generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient, BarretenbergApi, contract) {
let utxoInLen = utxoIn.length
let utxoOutLen = utxoOut.length
for (let i = 0; i < utxoInLen; i++) {
let ownerHex = utxoIn[i].owner.toString();
let amountHex = utxoIn[i].amount.toString();
let assetTypeHex = utxoIn[i].assetType.toString();
let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]);
let utxoLeaf = note_commitment.toString()
data.secrets[i] = utxoIn[i].secret
data.nullifier_hashes[i] = BarretenbergApi.pedersenPlookupCompressFields(utxoIn[i].secret, utxoIn[i].secret)
data.old_root_proof[i] = await contract.getRootFromUtxo(utxoLeaf)
data.utxo_in[i * 3 + 0] = ownerHex
data.utxo_in[i * 3 + 1] = amountHex
data.utxo_in[i * 3 + 2] = assetTypeHex
data.indexes[i * 4 + 0] = treeProof[i].utxo.index
data.indexes[i * 4 + 1] = treeProof[i].tx.index
data.indexes[i * 4 + 2] = treeProof[i].historic.index
let utxoPath = treeProof[i].utxo.hash_path
let txPath = treeProof[i].tx.hash_path
let historicPath = treeProof[i].historic.hash_path
for (let j = 0; j < utxoPath.length; j++) {
data.hash_path[i * treeSum + 0 + j] = utxoPath[j]
data.hash_path[i * treeSum + treeConfig.utxoDepth + j] = txPath[j]
}
for (let k = 0; k < historicPath.length; k++) {
data.hash_path[i * treeSum + treeConfig.utxoDepth + treeConfig.txDepth + k] = historicPath[k]
}
}
for (let i = 0; i < utxoOutLen; i++) {
let ownerHex = utxoOut[i].owner.toString();
let amountHex = utxoOut[i].amount.toString();
let assetTypeHex = utxoOut[i].assetType.toString();
let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]);
let utxoLeaf = note_commitment.toString()
trees.utxo_tree.insert(utxoLeaf)
data.utxo_out[i * 3 + 0] = ownerHex
data.utxo_out[i * 3 + 1] = amountHex
data.utxo_out[i * 3 + 2] = assetTypeHex
data.commitment_out[i] = utxoLeaf
}
data.tx_in[0] = trees.utxo_tree.root()
data.amount_public_in = toFixedHex(Number(amountPublic.amountIn.toString()), true)
data.amount_public_out = toFixedHex(Number(amountPublic.amountOut.toString()), true)
data.recipient = recipient
data.current_root = trees.historic_tree.root()
dumpToml(data)
}
export function generateTestPublish(trees, data, api) {
let utxoTree = trees.utxo_tree
let txTree = trees.tx_tree
let historicTree = trees.historic_tree
let utxoRoot = utxoTree.root()
txTree.insert(utxoRoot)
let txRoot = txTree.root()
let txRootFr = Fr.fromBufferReduce(Buffer.from(txRoot.slice(2), 'hex'))
let oracleFr = Fr.fromBufferReduce(toFixedHex(0, true))
let oracleHash = api.pedersenPlookupCompress([oracleFr])
let batch = api.pedersenPlookupCompressFields(txRootFr, oracleHash)
let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex'))
let newHistoricRoot = api.pedersenPlookupCompress([batch, oldHistoricRoot])
let newHistoricRootHex = newHistoricRoot.toString()
historicTree.insert(newHistoricRootHex)
data.old_root = oldHistoricRoot.toString()
data.new_root = newHistoricRootHex
trees.utxoTreeOld = trees.utxo_tree
trees.txTreeOld = trees.tx_tree
trees.newHistoricRoot = newHistoricRootHex
trees.utxo_tree = new MerkleTree(4, api)
trees.tx_tree = new MerkleTree(4, api)
dumpToml(data)
}
\ No newline at end of file
import { exec, execSync } from "child_process";
import { newBarretenbergApiSync } from "@aztec/bb.js/dest/node/index.js";
import { Fr } from "@aztec/bb.js/dest/node/types/index.js";
import inquirer from "inquirer";
import {
generateUTXO,
generateDataToml,
generateTestTransaction,
generateTreeProof,
generateTestPublish,
randomBytesFr,
readToml,
getSolidityHash,
treeConfig,
getInputs,
getInputsOrdered,
getPublicInputsOrdered,
} from "./utils/helpers.js";
import { MerkleTree } from "./utils/MerkleTree.mjs";
import fs from "fs";
import { ethers, JsonRpcProvider, ContractFactory } from "ethers";
import PrivateTransferData from "../deps/TechnicalPreview.json" assert { type: "json" };
import { Sequelize, DataTypes, Model } from "sequelize";
import "dotenv/config";
class UTXOdb extends Model {
secret;
amount;
assetType;
spent;
}
let sequelize = new Sequelize({
dialect: "sqlite",
storage: "txs.db",
logging: false,
});
UTXOdb.init(
{
secret: {
type: DataTypes.STRING,
allowNull: false,
},
amount: {
type: DataTypes.STRING,
allowNull: false,
},
assetType: {
type: DataTypes.INTEGER,
},
spent: {
type: DataTypes.BOOLEAN,
},
id: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
},
},
{
sequelize: sequelize,
tableName: "UTXOs",
}
);
await sequelize.sync();
const fetchUTXOs = async () =>
await UTXOdb.findAll({ where: { spent: false } });
const fetchSpentUTXOs = async () =>
await UTXOdb.findAll({ where: { spent: true } });
const spendUTXO = async (UTXO) =>
await UTXOdb.update({ spent: true }, { where: { id: UTXO } });
const saveUTXO = async (UTXO) => {
await UTXOdb.upsert({
secret: UTXO.secret.toString(),
amount: UTXO.amount.toString(),
assetType: 0,
id: UTXO.id.toString(),
spent: false,
});
};
const provider = new JsonRpcProvider(process.env.PROVIDER);
const privateKey = process.env.PRIVATE_KEY;
const wallet = new ethers.Wallet(privateKey, provider);
const technicalPreviewContract = new ethers.Contract(
"0x8162d56A21D0ee799Eef055C3acC2b4F776f693a",
PrivateTransferData.abi,
wallet
);
export async function balance() {
const api = await newBarretenbergApiSync();
let utxos = await fetchUTXOs();
let nullifierArray = [];
for (let i = 0; i < utxos.length; i++) {
nullifierArray.push(api.pedersenPlookupCompress([
Fr.fromString(utxos[i].dataValues.secret),
Fr.fromString(utxos[i].dataValues.secret)
]).toString());
}
let spentArray = await technicalPreviewContract.getSpentNullifiers(nullifierArray);
for (let i = 0; i < spentArray.length; i++) {
if (spentArray[i] === true) {
await spendUTXO(utxos[i].dataValues.id);
utxos.splice(i, 1);
}
}
if (utxos.length === 0) return ethers.formatEther(BigInt(0));
else {
let utxosBigInt = utxos.map((res) => BigInt(res.dataValues.amount));
return ethers.formatEther(utxosBigInt.reduce((a, b) => a + b));
}
}
export const deposit = async (args) => {
console.log(`** Compiling Transaction Circuit **`);
execSync(`nargo check`);
execSync(`nargo compile`);
let recipient, recipient_new;
const api = await newBarretenbergApiSync();
let trees = {
utxo_tree: new MerkleTree(4, api),
tx_tree: new MerkleTree(4, api),
historic_tree: new MerkleTree(9, api),
utxoTreeOld: new MerkleTree(4, api),
txTreeOld: new MerkleTree(4, api),
newHistoricRoot: "",
};
let utxoIn = [];
let utxoOut = [];
let treeProof = [];
let validRoots = await technicalPreviewContract.getValidRoots();
let oldRoot, newRoot;
if (validRoots.length === 1) {
oldRoot = validRoots[0];
newRoot = validRoots[0];
} else {
oldRoot = validRoots[validRoots.length - 2];
newRoot = validRoots[validRoots.length - 1];
}
let data = generateDataToml(oldRoot, newRoot, trees, api);
let amountPublic = {
amountIn: BigInt(0),
amountOut: BigInt(0),
};
recipient = `0x` + `dEaD`.padStart(64, "0");
console.log("** Populating UTXO tree.. **");
console.log("** Generating a batch of zkXFT UTXOs... **");
let batchSize0 = 1;
let secret0 = [];
for (let s = 0; s < batchSize0; s++) {
secret0.push(randomBytesFr(32));
}
let amountsOutUTXO = new Array(batchSize0).fill(BigInt(0));
amountsOutUTXO[0] = BigInt(args.amount);
utxoOut = generateUTXO(batchSize0, amountsOutUTXO, secret0, api);
amountPublic.amountIn = BigInt(args.amount);
generateTestTransaction(
utxoIn,
utxoOut,
trees,
treeProof,
amountPublic,
data,
recipient,
api,
technicalPreviewContract
);
console.log("** Creating Deposit and Publisher Proof... **");
generateTestPublish(trees, data, api);
execSync(`nargo prove`);
let proof = fs.readFileSync("./proofs/main.proof").toString();
proof = `0x` + proof;
let public_inputs = readToml("./Verifier.toml");
let tx = await technicalPreviewContract.publish(proof, public_inputs, {
value: 0,
});
utxoIn = utxoOut;
let txReceipt = await provider.getTransactionReceipt(tx.hash);
while (txReceipt === null) {
txReceipt = await provider.getTransactionReceipt(tx.hash);
}
let utxoId = api.pedersenPlookupCompress([
utxoIn[0].owner,
utxoIn[0].amount,
utxoIn[0].assetType,
]);
utxoOut[0].id = utxoId;
await saveUTXO(utxoOut[0]);
return [trees, data];
};
export const transact = async (args) => {
const api = await newBarretenbergApiSync();
let amount = BigInt(args.amount);
let trees = {
utxo_tree: new MerkleTree(4, api),
tx_tree: new MerkleTree(4, api),
historic_tree: new MerkleTree(9, api),
utxoTreeOld: new MerkleTree(4, api),
txTreeOld: new MerkleTree(4, api),
newHistoricRoot: "",
};
console.log("** Populating Historic tree.. **");
let validRoots = await technicalPreviewContract.getValidRoots();
for (let i = 0; i < validRoots.length; i++) {
trees.historic_tree.insert(validRoots[i]);
}
let spending = [];
let spendingSum = BigInt(0);
let utxo_leaves = (await fetchUTXOs()).map((utxo) => {
let utxo_out = {
secret: Fr.fromString(utxo.dataValues.secret),
owner: api.pedersenPlookupCompress([
Fr.fromString(utxo.dataValues.secret),
]),
amount: Fr.fromString(utxo.dataValues.amount),
assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
spent: utxo.dataValues.spent,
id: utxo.dataValues.id,
};
return utxo_out;
});
let utxoOut = [];
let changeUTXO = {
secret: Fr.fromString("0x00"),
owner: api.pedersenPlookupCompress([Fr.fromString("0x00")]),
amount: Fr.fromString("0x00"),
assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
};
for (let i = 0; i < utxo_leaves.length; i++) {
if (spendingSum < amount) {
spending.push(utxo_leaves[i]);
spendingSum += BigInt(utxo_leaves[i].amount.toString());
}
if (spendingSum > amount) {
let change = spendingSum - amount;
let secret = randomBytesFr(32);
changeUTXO = {
secret: secret,
owner: api.pedersenPlookupCompress([secret]),
amount: new Fr(change),
assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
};
changeUTXO.id = api.pedersenPlookupCompress([
changeUTXO.owner,
changeUTXO.amount,
changeUTXO.assetType,
]);
utxoOut.push(changeUTXO);
break;
}
}
let treeProofs = [];
for (let i = 0; i < spending.length; i++) {
let proofForUtxo = await generateTreeProof(
spending[i],
api,
technicalPreviewContract
);
treeProofs.push(proofForUtxo);
}
let batchSize1 = 1;
let oldRoot, newRoot;
if (validRoots.length === 1) {
oldRoot = validRoots[0];
newRoot = validRoots[0];
} else {
oldRoot = validRoots[validRoots.length - 2];
newRoot = validRoots[validRoots.length - 1];
}
let data = generateDataToml(oldRoot, newRoot, trees, api);
let amountPublic = {
amountIn: BigInt(0),
amountOut: BigInt(args.amount),
};
let recipientPrompt = await inquirer.prompt({
type: "input",
name: "recipient",
message: "Enter the destination address: ",
});
let recipient_new =
`0x` + `${recipientPrompt.recipient.replace("0x", "")}`.padStart(64, "0");
await generateTestTransaction(
spending,
utxoOut,
trees,
treeProofs,
amountPublic,
data,
recipient_new,
api,
technicalPreviewContract
);
generateTestPublish(trees, data, api);
console.log(`** Compiling Transaction Circuit **`);
execSync(`nargo check`);
execSync(`nargo compile`);
console.log("** Generating transaction Parameters... **");
execSync(`nargo prove`);
let proof = fs.readFileSync("./proofs/main.proof").toString();
proof = `0x` + proof;
let public_inputs = readToml("./Verifier.toml");
let tx = await technicalPreviewContract.publish(proof, public_inputs, {
value: 0,
});
let txReceipt = await provider.getTransactionReceipt(tx.hash);
while (txReceipt === null) {
console.log("Waiting for tx confirmation...")
txReceipt = await provider.getTransactionReceipt(tx.hash);
}
for (let i = 0; i < spending.length; i++) {
spendUTXO(spending[i].id);
}
await saveUTXO(changeUTXO);
};
use dep::std;
global state_depth = 9;
global utxo_depth = 4;
global batch_depth = 4;
fn main(
tx_in: pub [Field; 16],
current_root: pub Field,
amount_public_in: pub Field,
amount_public_out: pub Field,
commitment_out: pub [Field; 16],
recipient: pub Field,
oracle: pub Field,
old_root_proof: [Field; 16],
old_root: pub Field,
new_root: pub Field,
nullifier_hashes: pub [Field; 16],
secrets: [Field; 16],
utxo_in: [Field; 48],
utxo_out: [Field; 48],
indexes: [Field; 48],
hash_path: [Field; 272],
) {
let trees: Field = 4;
let mut sum_in: Field = amount_public_in;
let mut sum_out: Field = amount_public_out;
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);
}
}
let utxo_root_calc: Field = pedersen_tree_four(commitment_out);
assert(tx_in[0] == utxo_root_calc);
let tx_root_calc: Field = pedersen_tree_four(tx_in);
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);
assert(sum_in == sum_out);
assert(recipient == recipient);
}
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
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