Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locking by spending and creating UTXOs #121

Merged
merged 29 commits into from
Jan 29, 2025
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
97f3894
spend inputs for locking
jimthematrix Jan 9, 2025
0e3de1a
Squashed commit of the following:
jimthematrix Jan 9, 2025
5a8e9f2
zeto lock with nullifiers
jimthematrix Jan 9, 2025
ac8c0f9
remove lock verifiers
jimthematrix Jan 14, 2025
4725fba
transfer() and transferLocked() with new circuits
jimthematrix Jan 14, 2025
61a0902
fix anon_nullifier and add locking support to nf_anon
jimthematrix Jan 15, 2025
e710d01
add locking support for NF token implementations
jimthematrix Jan 16, 2025
8a2504e
Add escrow contract based on Zeto_Anon for testing purposes
jimthematrix Jan 17, 2025
444a062
add escrow contracts to test locking flows
jimthematrix Jan 20, 2025
1a5a3a9
fix tests
jimthematrix Jan 22, 2025
eb8723f
fix circuit tests
jimthematrix Jan 22, 2025
7660df1
fix go-sdk integration test
jimthematrix Jan 22, 2025
7b3861b
update checks of inputs and output
jimthematrix Jan 22, 2025
a6a911e
update circuit integration tests
jimthematrix Jan 22, 2025
121185b
fix nf nullifier integration test
jimthematrix Jan 22, 2025
518cd5b
fix nf anon nullifier integration test
jimthematrix Jan 22, 2025
ff3fe10
fix test for the escrow flow
jimthematrix Jan 22, 2025
63a902a
fix anon_nullifier build and tests for batching
jimthematrix Jan 23, 2025
5f1a2df
fix deployment parameters for the factory
jimthematrix Jan 23, 2025
f4dff77
change the verifier initialization params to a struct
jimthematrix Jan 23, 2025
64966b5
move verifiers to the contracts/verifiers folder
jimthematrix Jan 23, 2025
c6c2d7e
fix factory unit tests
jimthematrix Jan 23, 2025
e7e96b7
fix tests for using the cloneable factory
jimthematrix Jan 24, 2025
073918c
consolidate solidity interfaces
jimthematrix Jan 24, 2025
7323357
use proof to check for existence in the locked commitments tree
jimthematrix Jan 27, 2025
a9c95c7
renaming of internal variables
jimthematrix Jan 27, 2025
1c54398
Update doc-site/docs/advanced/erc20-tokens-integration.md
jimthematrix Jan 28, 2025
8ced077
test for duplicate output utxos
Chengxuan Jan 29, 2025
925ee0c
Update inline comments in the transferLocked circuit
jimthematrix Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add locking support for NF token implementations
Signed-off-by: Jim Zhang <[email protected]>
jimthematrix committed Jan 16, 2025
commit e710d0192a8a147c41ae25c317ef90d79a72e1e1
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@

pragma solidity >=0.7.0 <0.9.0;

contract Groth16Verifier_NfAnonNullifier {
contract Groth16Verifier_NfAnonNullifierTransfer {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
189 changes: 189 additions & 0 deletions solidity/contracts/lib/verifier_nf_anon_nullifier_transferLocked.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.

This file is generated with [snarkJS](https://github.com/iden3/snarkjs).

snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.

You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/

pragma solidity >=0.7.0 <0.9.0;

contract Groth16Verifier_NfAnonNullifierTransferLocked {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;

// Verification Key data
uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679;
uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856;
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant deltax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant deltay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant deltay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;


uint256 constant IC0x = 13058539721432268764503714828619663141375444649879587444307036004161584042698;
uint256 constant IC0y = 13231166779218028213678924006298697901762633361997917108298474235370208643631;

uint256 constant IC1x = 849607412105127908955236592676452928309067622614671328242877911559171816381;
uint256 constant IC1y = 9222683832419210809057694616641998563004311751553037755021147738067550736734;

uint256 constant IC2x = 9871623610933911028634462909589180438415336243702078369478199024739298745179;
uint256 constant IC2y = 12558694680939557613065088945115267813385867643726654066869895466080337882994;

uint256 constant IC3x = 14397948545111947035652763878398920333973148010650892671108039740746020365026;
uint256 constant IC3y = 20464400755082899469201600515683238637495978370394706359025655826389217735748;

uint256 constant IC4x = 3443709114960617808052213020206140977156870277533520461473304460806783298332;
uint256 constant IC4y = 18227440296082766393881163014973838627101961125778703077445419063306877543024;


// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;

uint16 constant pLastMem = 896;

function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[4] calldata _pubSignals) public view returns (bool) {
assembly {
function checkField(v) {
if iszero(lt(v, r)) {
mstore(0, 0)
return(0, 0x20)
}
}

// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)

success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)

if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}

mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))

success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)

if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}

function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)

mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)

// Compute the linear combination vk_x

g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))

g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))

g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64)))

g1_mulAccC(_pVk, IC4x, IC4y, calldataload(add(pubSignals, 96)))


// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))

// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))

// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)

// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)

// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))


// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)

// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))

// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)


let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)

isOk := and(success, mload(_pPairing))
}

let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))

// Validate that all evaluations ∈ F

checkField(calldataload(add(_pubSignals, 0)))

checkField(calldataload(add(_pubSignals, 32)))

checkField(calldataload(add(_pubSignals, 64)))

checkField(calldataload(add(_pubSignals, 96)))


// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)

mstore(0, isValid)
return(0, 0x20)
}
}
}
116 changes: 102 additions & 14 deletions solidity/contracts/zeto_nf_anon_nullifier.sol
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@
pragma solidity ^0.8.27;

import {IZeto} from "./lib/interfaces/izeto.sol";
import {Groth16Verifier_NfAnonNullifier} from "./lib/verifier_nf_anon_nullifier.sol";
import {Groth16Verifier_NfAnonNullifierTransfer} from "./lib/verifier_nf_anon_nullifier_transfer.sol";
import {Groth16Verifier_NfAnonNullifierTransferLocked} from "./lib/verifier_nf_anon_nullifier_transferLocked.sol";
import {ZetoNullifier} from "./lib/zeto_nullifier.sol";
import {Commonlib} from "./lib/common.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
@@ -30,14 +31,17 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
/// - the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers
/// - the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash
contract Zeto_NfAnonNullifier is IZeto, ZetoNullifier, UUPSUpgradeable {
Groth16Verifier_NfAnonNullifier _verifier;
Groth16Verifier_NfAnonNullifierTransfer _verifier;
Groth16Verifier_NfAnonNullifierTransferLocked _lockedVerifier;

function initialize(
address initialOwner,
Groth16Verifier_NfAnonNullifier verifier
Groth16Verifier_NfAnonNullifierTransfer verifier,
Groth16Verifier_NfAnonNullifierTransferLocked lockedVerifier
) public initializer {
__ZetoNullifier_init(initialOwner);
_verifier = verifier;
_lockedVerifier = lockedVerifier;
}

function _authorizeUpgrade(address) internal override onlyOwner {}
@@ -64,29 +68,113 @@ contract Zeto_NfAnonNullifier is IZeto, ZetoNullifier, UUPSUpgradeable {
nullifiers[0] = nullifier;
uint256[] memory outputs = new uint256[](1);
outputs[0] = output;

validateTransactionProposal(nullifiers, outputs, root, false);
checkProof(nullifiers, outputs, root, proof);
uint256[] memory empty;
processInputsAndOutputs(nullifiers, outputs, empty, address(0));

emit UTXOTransfer(nullifiers, outputs, msg.sender, data);
return true;
}

/**
* @dev the main function of the contract.
*
* @param nullifier A nullifier that are secretly bound to the UTXO to be spent by the transaction.
* @param output new UTXO to generate, for future transactions to spend.
* @param root The root hash of the Sparse Merkle Tree that contains the nullifier.
* @param proof A zero knowledge proof that the submitter is authorized to spend the inputs, and
* that the outputs are valid in terms of obeying mass conservation rules.
*
* Emits a {UTXOTransfer} event.
*/
function transferLocked(
uint256 nullifier,
uint256 output,
uint256 root,
Commonlib.Proof calldata proof,
bytes calldata data
) public returns (bool) {
uint256[] memory nullifiers = new uint256[](1);
nullifiers[0] = nullifier;
uint256[] memory outputs = new uint256[](1);
outputs[0] = output;
validateTransactionProposal(nullifiers, outputs, root, true);
checkProofLocked(nullifiers, outputs, root, proof);
uint256[] memory empty;
processInputsAndOutputs(nullifiers, outputs, empty, address(0));

emit UTXOTransfer(nullifiers, outputs, msg.sender, data);
return true;
}

function mint(uint256[] memory utxos, bytes calldata data) public {
_mint(utxos, data);
}

function lock(
uint256 nullifier,
uint256 lockedOutput,
uint256 root,
Commonlib.Proof calldata proof,
address delegate,
bytes calldata data
) public {
uint256[] memory nullifiers = new uint256[](1);
nullifiers[0] = nullifier;
uint256[] memory lockedOutputs = new uint256[](1);
lockedOutputs[0] = lockedOutput;
validateTransactionProposal(nullifiers, lockedOutputs, root, false);
checkProof(nullifiers, lockedOutputs, root, proof);

spendNullifiers(nullifiers);

// lock the intended outputs
uint256[] memory outputs;
_lock(nullifiers, outputs, lockedOutputs, delegate, data);
}

function checkProof(
uint256[] memory nullifiers,
uint256[] memory outputs,
uint256 root,
Commonlib.Proof calldata proof
) internal {
// construct the public inputs
uint256[3] memory publicInputs;
publicInputs[0] = nullifier;
publicInputs[0] = nullifiers[0];
publicInputs[1] = root;
publicInputs[2] = output;
publicInputs[2] = outputs[0];

// Check the proof
require(
_verifier.verifyProof(proof.pA, proof.pB, proof.pC, publicInputs),
"Invalid proof"
);

uint256[] memory empty;
processInputsAndOutputs(nullifiers, outputs, empty, address(0));

emit UTXOTransfer(nullifiers, outputs, msg.sender, data);
return true;
}

function mint(uint256[] memory utxos, bytes calldata data) public {
_mint(utxos, data);
function checkProofLocked(
uint256[] memory nullifiers,
uint256[] memory outputs,
uint256 root,
Commonlib.Proof calldata proof
) internal {
// construct the public inputs
uint256[4] memory publicInputs;
publicInputs[0] = nullifiers[0];
publicInputs[1] = uint256(uint160(msg.sender));
publicInputs[2] = root;
publicInputs[3] = outputs[0];

// Check the proof
require(
_lockedVerifier.verifyProof(
proof.pA,
proof.pB,
proof.pC,
publicInputs
),
"Invalid proof"
);
}
}
File renamed without changes.
12 changes: 9 additions & 3 deletions solidity/ignition/modules/zeto_nf_anon_nullifier.ts
Original file line number Diff line number Diff line change
@@ -17,14 +17,20 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import { SmtLibModule } from "./lib/deps";

const VerifierModule = buildModule("Groth16Verifier_NfAnonNullifier", (m) => {
const verifier = m.contract("Groth16Verifier_NfAnonNullifier", []);
const VerifierModule = buildModule("Groth16Verifier_NfAnonNullifierTransfer", (m) => {
const verifier = m.contract("Groth16Verifier_NfAnonNullifierTransfer", []);
return { verifier };
});

const LockedVerifierModule = buildModule("Groth16Verifier_NfAnonNullifierTransferLocked", (m) => {
const verifier = m.contract("Groth16Verifier_NfAnonNullifierTransferLocked", []);
return { verifier };
});

export default buildModule("Zeto_NfAnonNullifier", (m) => {
const { smtLib, poseidon3 } = m.useModule(SmtLibModule);
const { verifier } = m.useModule(VerifierModule);
const { verifier: lockedVerifier } = m.useModule(LockedVerifierModule);

return { verifier, smtLib, poseidon3 };
return { verifier, lockedVerifier, smtLib, poseidon3 };
});
3 changes: 2 additions & 1 deletion solidity/scripts/tokens/Zeto_NfAnonNullifier.ts
Original file line number Diff line number Diff line change
@@ -20,12 +20,13 @@ import zetoModule from "../../ignition/modules/zeto_nf_anon_nullifier";
export async function deployDependencies() {
const [deployer] = await ethers.getSigners();

const { verifier, smtLib, poseidon3 } = await ignition.deploy(zetoModule);
const { verifier, lockedVerifier, smtLib, poseidon3 } = await ignition.deploy(zetoModule);
return {
deployer,
args: [
await deployer.getAddress(),
verifier.target,
lockedVerifier.target,
],
libraries: {
SmtLib: smtLib.target,
Loading