├── .env copy.devnet ├── .env.template ├── .github └── workflows │ ├── ci.yaml │ └── rust_ci.yaml ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── contract ├── .github │ └── workflows │ │ └── test.yml ├── .gitignore ├── README.md ├── foundry.toml ├── remappings.txt └── src │ ├── MinaAccountValidationExample.sol │ └── MinaStateSettlementExample.sol ├── contract_deployer ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── core ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── abi │ ├── MinaAccountValidationExample.json │ └── MinaStateSettlementExample.json ├── graphql │ ├── account_query.graphql │ ├── best_chain_query.graphql │ ├── mina_schema.json │ └── state_query.graphql └── src │ ├── aligned.rs │ ├── eth.rs │ ├── lib.rs │ ├── main.rs │ ├── mina.rs │ ├── proof │ ├── account_proof.rs │ ├── mod.rs │ └── state_proof.rs │ ├── sdk.rs │ ├── sol │ ├── account.rs │ ├── mod.rs │ └── serialization.rs │ └── utils │ ├── constants.rs │ ├── env.rs │ ├── mod.rs │ ├── wallet.rs │ └── wallet_alloy.rs ├── example ├── app │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── abi │ │ └── SudokuValidity.json │ └── src │ │ └── main.rs ├── eth_contract │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── lib │ │ └── mina_bridge │ ├── remappings.txt │ └── src │ │ └── SudokuValidity.sol └── mina_zkapp │ ├── .eslintrc.cjs │ ├── .gitattributes │ ├── .github │ └── workflows │ │ └── ci.yml │ ├── .gitignore │ ├── .npmignore │ ├── .prettierignore │ ├── .prettierrc │ ├── LICENSE │ ├── README.md │ ├── babel.config.cjs │ ├── config.json │ ├── jest-resolver.cjs │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── index.ts │ ├── run.ts │ ├── sudoku-lib.js │ ├── sudoku.test.ts │ └── sudoku.ts │ └── tsconfig.json ├── img ├── batch_verification_evaluation_proofs.png ├── commitments_to_quotient_poly.png ├── commitments_to_secret_poly.png ├── consensus01.png ├── consensus02.png ├── consensus03.png ├── consensus04.png ├── consensus05.png ├── consensus06.png ├── consensus07.png ├── consensus08.png ├── example_diagram.png ├── palas_vesta.png ├── pickles_step_01.png ├── pickles_step_02.png ├── pickles_step_03.png ├── pickles_step_04.png ├── pickles_step_05.png ├── pickles_step_06.png ├── pickles_step_07.png ├── pickles_step_08.png ├── pickles_structure_drawio.png ├── prover_provides_evaluations_linearization_01.png ├── prover_provides_evaluations_linearization_02.png ├── step_diagram.png └── verifier_produces_evaluation_point.png └── srs ├── pallas.srs └── vesta.srs /.env copy.devnet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/.env copy.devnet -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | ETH_CHAIN= 2 | MINA_RPC_URL= 3 | STATE_SETTLEMENT_ETH_ADDR=
4 | ACCOUNT_VALIDATION_ETH_ADDR=
5 | SAVE_PROOF=true/false # also false if other than "true" or variable were to be not defined 6 | 7 | ## These are necessary for running the Sudoku example 8 | #FEEPAYER_KEY= 9 | #SUDOKU_VALIDITY_HOLESKY_ADDRESS= 10 | 11 | ## These can be skipped if using devnet with Anvil. 12 | # BATCHER_ADDR= 13 | # BATCHER_ETH_ADDR= 14 | # ETH_RPC_URL= 15 | # PROOF_GENERATOR_ADDR= 16 | # BRIDGE_HOLESKY_ETH_ADDR= 17 | # BRIDGE_ACCOUNT_HOLESKY_ETH_ADDR= 18 | # ALIGNED_SERVICE_MANAGER_ADDR= 19 | 20 | ## You can choose to use a keystore or private key for your signing wallet. 21 | ## Leave empty if choosing Anvil Devnet. 22 | # KEYSTORE_PATH= 23 | # PRIVATE_KEY= 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: ["*"] 8 | 9 | jobs: 10 | core: 11 | name: Test Core 12 | uses: ./.github/workflows/rust_ci.yaml 13 | with: 14 | skip_run: true 15 | directory: core 16 | -------------------------------------------------------------------------------- /.github/workflows/rust_ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI Rust package 2 | on: 3 | workflow_call: 4 | inputs: 5 | directory: 6 | required: true 7 | type: string 8 | skip_run: 9 | required: false 10 | type: boolean 11 | default: false 12 | 13 | jobs: 14 | check: 15 | name: Check, fmt, clippy, test and run 16 | runs-on: ubuntu-selfhosted 17 | defaults: 18 | run: 19 | working-directory: ./${{inputs.directory}} 20 | steps: 21 | - name: Checkout sources 22 | uses: actions/checkout@v4 23 | 24 | - name: Run cargo check 25 | run: /root/.cargo/bin/cargo check --workspace 26 | 27 | - name: Run rustfmt 28 | run: /root/.cargo/bin/cargo fmt --all --check 29 | 30 | - name: Run clippy 31 | run: /root/.cargo/bin/cargo clippy --workspace --all-targets -- -D warnings 32 | 33 | - name: Run binary 34 | if: ${{ !inputs.skip_run }} 35 | run: /root/.cargo/bin/cargo run 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | mina_3_0_0_devnet 3 | mina_state.pub 4 | mina_state.proof 5 | mina_account.pub 6 | mina_account.proof 7 | aligned_verification_data 8 | .env 9 | nonce_*.bin 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "verifier_circuit/o1js"] 2 | path = verifier_circuit/o1js 3 | url = https://github.com/lambdaclass/o1js.git 4 | branch = verifier_bn254 5 | [submodule "eth_verifier/lib/forge-std"] 6 | path = eth_verifier/lib/forge-std 7 | url = https://github.com/foundry-rs/forge-std 8 | [submodule "contract/lib/forge-std"] 9 | path = contract/lib/forge-std 10 | url = https://github.com/foundry-rs/forge-std 11 | [submodule "contract/lib/aligned_layer"] 12 | path = contract/lib/aligned_layer 13 | url = https://github.com/lambdaclass/aligned_layer 14 | [submodule "app/eth_contract/lib/forge-std"] 15 | path = app/eth_contract/lib/forge-std 16 | url = https://github.com/foundry-rs/forge-std 17 | [submodule "example/eth_contract/lib/forge-std"] 18 | path = example/eth_contract/lib/forge-std 19 | url = https://github.com/foundry-rs/forge-std 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: submit_mainnet_state submit_devnet_state submit_account gen_contract_abi deploy_example_bridge_contracts 2 | 3 | submit_mainnet_state: 4 | @cargo run --manifest-path core/Cargo.toml --release -- submit-state 5 | 6 | submit_devnet_state: 7 | @cargo run --manifest-path core/Cargo.toml --release -- submit-state --devnet 8 | 9 | submit_account: 10 | @cargo run --manifest-path core/Cargo.toml --release -- submit-account ${PUBLIC_KEY} ${STATE_HASH} 11 | 12 | gen_contract_abis: 13 | forge build --root contract/ 14 | forge build --root example/eth_contract 15 | cp contract/out/MinaStateSettlementExample.sol/MinaStateSettlementExample.json core/abi/MinaStateSettlementExample.json 16 | cp contract/out/MinaAccountValidationExample.sol/MinaAccountValidationExample.json core/abi/MinaAccountValidationExample.json 17 | cp example/eth_contract/out/SudokuValidity.sol/SudokuValidity.json example/app/abi/SudokuValidity.json 18 | 19 | deploy_example_bridge_contracts: 20 | @cargo run --manifest-path contract_deployer/Cargo.toml --release 21 | 22 | deploy_example_app_contracts: 23 | @cargo run --manifest-path example/app/Cargo.toml --release -- deploy-contract 24 | 25 | execute_example: 26 | cd example/mina_zkapp; \ 27 | npm run build; \ 28 | node build/src/run.js 29 | cargo run --manifest-path example/app/Cargo.toml --release -- validate-solution 30 | -------------------------------------------------------------------------------- /contract/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly 27 | 28 | - name: Show Forge version 29 | run: | 30 | forge --version 31 | 32 | - name: Run Forge fmt 33 | run: | 34 | forge fmt --check 35 | id: fmt 36 | 37 | - name: Run Forge build 38 | run: | 39 | forge build --sizes 40 | id: build 41 | 42 | - name: Run Forge tests 43 | run: | 44 | forge test -vvv 45 | id: test 46 | -------------------------------------------------------------------------------- /contract/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /contract/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /contract/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /contract/remappings.txt: -------------------------------------------------------------------------------- 1 | aligned_layer/=lib/aligned_layer/ 2 | ds-test/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/ds-test/src/ 3 | 4 | eigenlayer-contracts/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/ 5 | eigenlayer-middleware/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/src 6 | eigenlayer-core/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/ 7 | eigenlayer-core-contracts/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/core 8 | eigenlayer-scripts/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/script 9 | 10 | erc4626-tests/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/lib/erc4626-tests/ 11 | 12 | openzeppelin-contracts-upgradeable-v4.9.0/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/ 13 | openzeppelin-contracts-upgradeable/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/ 14 | openzeppelin-contracts-v4.9.0/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/ 15 | openzeppelin-contracts/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 16 | 17 | openzeppelin-contracts-v4.9.0/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/ 18 | openzeppelin-contracts/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 19 | 20 | @openzeppelin-upgrades/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/ 21 | @openzeppelin/=lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 22 | 23 | forge-std/=lib/forge-std/src/ 24 | -------------------------------------------------------------------------------- /contract/src/MinaAccountValidationExample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.12; 3 | 4 | import "aligned_layer/contracts/src/core/AlignedLayerServiceManager.sol"; 5 | 6 | error MinaAccountProvingSystemIdIsNotValid(bytes32); // c1872967 7 | 8 | /// WARNING: This contract is meant ot be used as an example of how to use the Bridge. 9 | /// NEVER use this contract in a production environment. 10 | contract MinaAccountValidationExample { 11 | /// @notice The commitment to Mina Account proving system ID. 12 | bytes32 constant PROVING_SYSTEM_ID_COMM = 0xd0591206d9e81e07f4defc5327957173572bcd1bca7838caa7be39b0c12b1873; 13 | 14 | struct AlignedArgs { 15 | bytes32 proofCommitment; 16 | bytes32 provingSystemAuxDataCommitment; 17 | bytes20 proofGeneratorAddr; 18 | bytes32 batchMerkleRoot; 19 | bytes merkleProof; 20 | uint256 verificationDataBatchIndex; 21 | bytes pubInput; 22 | address batcherPaymentService; 23 | } 24 | 25 | /// @notice Reference to the AlignedLayerServiceManager contract. 26 | AlignedLayerServiceManager aligned; 27 | 28 | constructor(address payable _alignedServiceAddr) { 29 | aligned = AlignedLayerServiceManager(_alignedServiceAddr); 30 | } 31 | 32 | function validateAccount(AlignedArgs calldata args) external view returns (bool) { 33 | if (args.provingSystemAuxDataCommitment != PROVING_SYSTEM_ID_COMM) { 34 | revert MinaAccountProvingSystemIdIsNotValid(args.provingSystemAuxDataCommitment); 35 | } 36 | 37 | bytes32 pubInputCommitment = keccak256(args.pubInput); 38 | 39 | return aligned.verifyBatchInclusion( 40 | args.proofCommitment, 41 | pubInputCommitment, 42 | args.provingSystemAuxDataCommitment, 43 | args.proofGeneratorAddr, 44 | args.batchMerkleRoot, 45 | args.merkleProof, 46 | args.verificationDataBatchIndex, 47 | args.batcherPaymentService 48 | ); 49 | } 50 | 51 | function validateAccountAndReturn(AlignedArgs calldata args) external view returns (Account memory) { 52 | if (args.provingSystemAuxDataCommitment != PROVING_SYSTEM_ID_COMM) { 53 | revert MinaAccountProvingSystemIdIsNotValid(args.provingSystemAuxDataCommitment); 54 | } 55 | 56 | bytes32 pubInputCommitment = keccak256(args.pubInput); 57 | 58 | bool isAccountVerified = aligned.verifyBatchInclusion( 59 | args.proofCommitment, 60 | pubInputCommitment, 61 | args.provingSystemAuxDataCommitment, 62 | args.proofGeneratorAddr, 63 | args.batchMerkleRoot, 64 | args.merkleProof, 65 | args.verificationDataBatchIndex, 66 | args.batcherPaymentService 67 | ); 68 | 69 | if (isAccountVerified) { 70 | return abi.decode(args.pubInput[32 + 8:], (Account)); 71 | } else { 72 | revert(); 73 | } 74 | } 75 | 76 | struct Account { 77 | CompressedECPoint publicKey; 78 | bytes32 tokenIdKeyHash; 79 | string tokenSymbol; 80 | uint64 balance; 81 | uint32 nonce; 82 | bytes32 receiptChainHash; 83 | CompressedECPoint delegate; 84 | bytes32 votingFor; 85 | Timing timing; 86 | Permissions permissions; 87 | ZkappAccount zkapp; 88 | } 89 | 90 | struct CompressedECPoint { 91 | bytes32 x; 92 | bool isOdd; 93 | } 94 | 95 | struct Timing { 96 | uint64 initialMinimumBalance; 97 | uint32 cliffTime; 98 | uint64 cliffAmount; 99 | uint32 vestingPeriod; 100 | uint64 vestingIncrement; 101 | } 102 | 103 | enum AuthRequired { 104 | None, 105 | Either, 106 | Proof, 107 | Signature, 108 | Impossible 109 | } 110 | 111 | struct Permissions { 112 | AuthRequired editState; 113 | AuthRequired access; 114 | AuthRequired send; 115 | AuthRequired rreceive; 116 | AuthRequired setDelegate; 117 | AuthRequired setPermissions; 118 | AuthRequired setVerificationKeyAuth; 119 | uint32 setVerificationKeyUint; 120 | AuthRequired setZkappUri; 121 | AuthRequired editActionState; 122 | AuthRequired setTokenSymbol; 123 | AuthRequired incrementNonce; 124 | AuthRequired setVotingFor; 125 | AuthRequired setTiming; 126 | } 127 | 128 | struct ZkappAccount { 129 | bytes32[8] appState; 130 | VerificationKey verificationKey; 131 | uint32 zkappVersion; 132 | bytes32[5] actionState; 133 | uint32 lastActionSlot; 134 | bool provedState; 135 | bytes zkappUri; 136 | } 137 | 138 | struct VerificationKey { 139 | ProofsVerified maxProofsVerified; 140 | ProofsVerified actualWrapDomainSize; 141 | WrapIndex wrapIndex; 142 | } 143 | 144 | enum ProofsVerified { 145 | N0, 146 | N1, 147 | N2 148 | } 149 | 150 | struct WrapIndex { 151 | Commitment[7] sigmaComm; 152 | Commitment[15] coefficientsComm; 153 | Commitment genericComm; 154 | Commitment psmComm; 155 | Commitment completeAddComm; 156 | Commitment mulComm; 157 | Commitment emulComm; 158 | Commitment endomulScalarComm; 159 | } 160 | 161 | struct Commitment { 162 | bytes32 x; 163 | bytes32 y; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /contract/src/MinaStateSettlementExample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.12; 3 | 4 | import "aligned_layer/contracts/src/core/AlignedLayerServiceManager.sol"; 5 | 6 | error MinaProvingSystemIdIsNotValid(bytes32); // c35f1ecd 7 | error MinaNetworkIsWrong(); // 042eb0cf 8 | error NewStateIsNotValid(); // 114602f0 9 | error TipStateIsWrong(bytes32 pubInputTipStateHash, bytes32 tipStatehash); // bbd80128 10 | error AccountIsNotValid(bytes32 accountIdHash); 11 | 12 | /// @title Mina to Ethereum Bridge's smart contract for verifying and storing a valid state chain. 13 | /// WARNING: This contract is meant ot be used as an example of how to use the Bridge. 14 | /// NEVER use this contract in a production environment. 15 | contract MinaStateSettlementExample { 16 | /// @notice The commitment to Mina proving system ID. 17 | bytes32 constant PROVING_SYSTEM_ID_COMM = 18 | 0xdbb8d0f4c497851a5043c6363657698cb1387682cac2f786c731f8936109d795; 19 | 20 | /// @notice The length of the verified state chain (also called the bridge's transition 21 | /// frontier) to store. 22 | uint256 public constant BRIDGE_TRANSITION_FRONTIER_LEN = 16; 23 | 24 | /// @notice The state hash of the last verified chain of Mina states (also called 25 | /// the bridge's transition frontier). 26 | bytes32[BRIDGE_TRANSITION_FRONTIER_LEN] chainStateHashes; 27 | /// @notice The ledger hash of the last verified chain of Mina states (also called 28 | /// the bridge's transition frontier). 29 | bytes32[BRIDGE_TRANSITION_FRONTIER_LEN] chainLedgerHashes; 30 | 31 | bool devnetFlag; 32 | 33 | /// @notice Reference to the AlignedLayerServiceManager contract. 34 | AlignedLayerServiceManager aligned; 35 | 36 | constructor(address payable _alignedServiceAddr, bytes32 _tipStateHash, bool _devnetFlag 37 | ) { 38 | aligned = AlignedLayerServiceManager(_alignedServiceAddr); 39 | chainStateHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1] = _tipStateHash; 40 | devnetFlag = _devnetFlag; 41 | } 42 | 43 | /// @notice Returns the last verified state hash. 44 | function getTipStateHash() external view returns (bytes32) { 45 | return chainStateHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1]; 46 | } 47 | 48 | /// @notice Returns the last verified ledger hash. 49 | function getTipLedgerHash() external view returns (bytes32) { 50 | return chainLedgerHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1]; 51 | } 52 | 53 | /// @notice Returns the latest verified chain state hashes. 54 | function getChainStateHashes() external view returns (bytes32[BRIDGE_TRANSITION_FRONTIER_LEN] memory) 55 | { 56 | return chainStateHashes; 57 | } 58 | 59 | /// @notice Returns the latest verified chain ledger hashes. 60 | function getChainLedgerHashes() external view returns (bytes32[BRIDGE_TRANSITION_FRONTIER_LEN] memory) 61 | { 62 | return chainLedgerHashes; 63 | } 64 | 65 | /// @notice Returns true if this snarked ledger hash was bridged. 66 | function isLedgerVerified(bytes32 ledgerHash) external view returns (bool) { 67 | for (uint256 i = 0; i < BRIDGE_TRANSITION_FRONTIER_LEN; i++) { 68 | if ( 69 | chainLedgerHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1 - i] == 70 | ledgerHash 71 | ) { 72 | return true; 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | function updateChain( 79 | bytes32 proofCommitment, 80 | bytes32 provingSystemAuxDataCommitment, 81 | bytes20 proofGeneratorAddr, 82 | bytes32 batchMerkleRoot, 83 | bytes memory merkleProof, 84 | uint256 verificationDataBatchIndex, 85 | bytes memory pubInput, 86 | address batcherPaymentService 87 | ) external { 88 | if (provingSystemAuxDataCommitment != PROVING_SYSTEM_ID_COMM) { 89 | revert MinaProvingSystemIdIsNotValid(provingSystemAuxDataCommitment); 90 | } 91 | 92 | bool pubInputDevnetFlag = pubInput[0] == 0x01; 93 | 94 | if (pubInputDevnetFlag != devnetFlag) { 95 | revert MinaNetworkIsWrong(); 96 | } 97 | 98 | bytes32 pubInputBridgeTipStateHash; 99 | assembly { 100 | pubInputBridgeTipStateHash := mload(add(pubInput, 0x21)) // Shift 33 bytes (32 bytes length + 1 byte Devnet flag) 101 | } 102 | 103 | if (pubInputBridgeTipStateHash != chainStateHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1]) { 104 | revert TipStateIsWrong(pubInputBridgeTipStateHash, chainStateHashes[BRIDGE_TRANSITION_FRONTIER_LEN - 1]); 105 | } 106 | 107 | bytes32 pubInputCommitment = keccak256(pubInput); 108 | 109 | bool isNewStateVerified = aligned.verifyBatchInclusion( 110 | proofCommitment, 111 | pubInputCommitment, 112 | provingSystemAuxDataCommitment, 113 | proofGeneratorAddr, 114 | batchMerkleRoot, 115 | merkleProof, 116 | verificationDataBatchIndex, 117 | batcherPaymentService 118 | ); 119 | 120 | if (isNewStateVerified) { 121 | // store the verified state and ledger hashes 122 | assembly { 123 | let slot_states := chainStateHashes.slot 124 | let slot_ledgers := chainLedgerHashes.slot 125 | 126 | // first 32 bytes is length of byte array. 127 | // the next byte is the Devnet flag 128 | // the next 32 bytes set is the bridge tip state hash 129 | // the next BRIDGE_TRANSITION_FRONTIER_LEN sets of 32 bytes are state hashes. 130 | let addr_states := add(pubInput, 65) 131 | // the next BRIDGE_TRANSITION_FRONTIER_LEN sets of 32 bytes are ledger hashes. 132 | let addr_ledgers := add( 133 | addr_states, 134 | mul(32, BRIDGE_TRANSITION_FRONTIER_LEN) 135 | ) 136 | 137 | for { let i := 0 } lt(i, BRIDGE_TRANSITION_FRONTIER_LEN) { i := add(i, 1) } { 138 | sstore(slot_states, mload(addr_states)) 139 | addr_states := add(addr_states, 32) 140 | slot_states := add(slot_states, 1) 141 | 142 | sstore(slot_ledgers, mload(addr_ledgers)) 143 | addr_ledgers := add(addr_ledgers, 32) 144 | slot_ledgers := add(slot_ledgers, 1) 145 | } 146 | } 147 | } else { 148 | revert NewStateIsNotValid(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /contract_deployer/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /contract_deployer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contract_deployer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | mina_bridge_core = { path = "../core/" } 8 | aligned-sdk = { git = "https://github.com/lambdaclass/aligned_layer.git", rev = "220546afa12c035a508529224f5148cd6af4ca78" } 9 | tokio = "1.39.1" 10 | env_logger = "0.11.5" 11 | bincode = "1.3.3" 12 | log = "0.4.22" 13 | 14 | [patch.crates-io] 15 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 16 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 17 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 18 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 19 | 20 | [patch.'https://github.com/openmina/algebra'] 21 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 22 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 23 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 24 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 25 | -------------------------------------------------------------------------------- /contract_deployer/src/main.rs: -------------------------------------------------------------------------------- 1 | use aligned_sdk::core::types::Network; 2 | use log::{debug, error, info}; 3 | use mina_bridge_core::{ 4 | eth::{ 5 | deploy_mina_account_validation_example_contract, deploy_mina_bridge_example_contract, 6 | MinaAccountValidationExampleConstructorArgs, MinaStateSettlementExampleConstructorArgs, 7 | SolStateHash, 8 | }, 9 | mina::query_root, 10 | utils::{ 11 | constants::{ALIGNED_SM_DEVNET_ETH_ADDR, BRIDGE_TRANSITION_FRONTIER_LEN}, 12 | env::EnvironmentVariables, 13 | wallet_alloy::get_wallet, 14 | }, 15 | }; 16 | use std::process; 17 | 18 | #[tokio::main] 19 | async fn main() { 20 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 21 | 22 | debug!("Reading env. variables"); 23 | let EnvironmentVariables { 24 | rpc_url, 25 | eth_rpc_url, 26 | network, 27 | private_key, 28 | keystore_path, 29 | .. 30 | } = EnvironmentVariables::new().unwrap_or_else(|err| { 31 | error!("{}", err); 32 | process::exit(1); 33 | }); 34 | 35 | let root_hash = query_root(&rpc_url, BRIDGE_TRANSITION_FRONTIER_LEN) 36 | .await 37 | .unwrap_or_else(|err| { 38 | error!("Failed to query root state hash: {err}"); 39 | process::exit(1); 40 | }); 41 | info!( 42 | "Queried root state hash {root_hash} for chain of length {BRIDGE_TRANSITION_FRONTIER_LEN}" 43 | ); 44 | let root_hash = bincode::serialize(&SolStateHash(root_hash)).unwrap_or_else(|err| { 45 | error!("Failed to serialize root state hash: {err}"); 46 | process::exit(1); 47 | }); 48 | 49 | let aligned_sm_addr = match network { 50 | Network::Devnet => Ok(ALIGNED_SM_DEVNET_ETH_ADDR.to_owned()), 51 | Network::Holesky => std::env::var("ALIGNED_SERVICE_MANAGER_ADDR") 52 | .map_err(|err| format!("Error getting Aligned SM contract address: {err}")), 53 | _ => Err("Unimplemented Ethereum contract on selected chain".to_owned()), 54 | } 55 | .unwrap_or_else(|err| { 56 | error!("{err}"); 57 | process::exit(1); 58 | }); 59 | 60 | let bridge_constructor_args = 61 | MinaStateSettlementExampleConstructorArgs::new(&aligned_sm_addr, root_hash).unwrap_or_else( 62 | |err| { 63 | error!("Failed to make constructor args for bridge contract call: {err}"); 64 | process::exit(1); 65 | }, 66 | ); 67 | let account_constructor_args = 68 | MinaAccountValidationExampleConstructorArgs::new(&aligned_sm_addr).unwrap_or_else(|err| { 69 | error!("Failed to make constructor args for account contract call: {err}"); 70 | process::exit(1); 71 | }); 72 | 73 | let wallet = get_wallet(&network, keystore_path.as_deref(), private_key.as_deref()) 74 | .unwrap_or_else(|err| { 75 | error!("Failed to get wallet: {err}"); 76 | process::exit(1); 77 | }); 78 | 79 | // Contract for Devnet state proofs 80 | deploy_mina_bridge_example_contract(ð_rpc_url, &bridge_constructor_args, &wallet, true) 81 | .await 82 | .unwrap_or_else(|err| { 83 | error!("Failed to deploy contract: {err}"); 84 | process::exit(1); 85 | }); 86 | 87 | // Contract for Mainnet state proofs 88 | deploy_mina_bridge_example_contract(ð_rpc_url, &bridge_constructor_args, &wallet, false) 89 | .await 90 | .unwrap_or_else(|err| { 91 | error!("Failed to deploy contract: {err}"); 92 | process::exit(1); 93 | }); 94 | 95 | deploy_mina_account_validation_example_contract( 96 | ð_rpc_url, 97 | account_constructor_args, 98 | &wallet, 99 | ) 100 | .await 101 | .unwrap_or_else(|err| { 102 | error!("Failed to deploy contract: {err}"); 103 | process::exit(1); 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mina_bridge_core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features = ["derive"] } 8 | serde_with = "3.6.0" 9 | serde_json = "1.0" 10 | num-bigint = "0.4.3" 11 | rmp-serde = "1.1.2" 12 | hex = "0.4.3" 13 | reqwest = { version = "^0.11", features = ["blocking"] } 14 | kimchi = { git = "https://github.com/lambdaclass/openmina-proof-systems", rev = "44e0d3b98b8747de54e595f53d97c035ff43167c" } 15 | mina-signer = { git = "https://github.com/lambdaclass/openmina-proof-systems", rev = "44e0d3b98b8747de54e595f53d97c035ff43167c" } 16 | poly-commitment = { git = "https://github.com/lambdaclass/openmina-proof-systems", rev = "44e0d3b98b8747de54e595f53d97c035ff43167c" } 17 | mina-curves = { git = "https://github.com/lambdaclass/openmina-proof-systems", rev = "44e0d3b98b8747de54e595f53d97c035ff43167c" } 18 | o1-utils = { git = "https://github.com/lambdaclass/proof-systems", rev = "5bdeab3c2a43a671645952f63b9354b7a20b2326" } 19 | ark-ff = { version = "0.3.0", features = ["parallel", "asm"] } 20 | ark-ec = { version = "0.3.0", features = ["parallel"] } 21 | ark-poly = { version = "0.3.0", features = ["parallel"] } 22 | ark-serialize = "0.3.0" 23 | mina-tree = { git = "https://github.com/lambdaclass/openmina/", rev = "860a55dde0e2943c9437ebdfdecbee5f1ac4976f" } 24 | mina-p2p-messages = { git = "https://github.com/lambdaclass/openmina/", rev = "860a55dde0e2943c9437ebdfdecbee5f1ac4976f" } 25 | aligned-sdk = { git = "https://github.com/lambdaclass/aligned_layer.git", rev = "220546afa12c035a508529224f5148cd6af4ca78" } 26 | ethers = { version = "2.0", features = ["ws", "rustls"] } 27 | rpassword = "7.3.1" 28 | tokio = "1.39.1" 29 | dotenv = "0.15.0" 30 | env_logger = "0.11.5" 31 | log = "0.4.22" 32 | base64 = "0.22.1" 33 | graphql_client = { version = "0.14.0", features = [ 34 | "reqwest", 35 | "reqwest-blocking", 36 | ] } 37 | alloy = { version = "0.3.1", features = ["full", "signer-keystore"] } 38 | clap = { version = "4.5.4", features = ["derive"] } 39 | sha3 = "0.10.8" 40 | bincode = "1.3.3" 41 | futures = "0.3.30" 42 | num-traits = "0.2.19" 43 | alloy-sol-types = "0.8.2" 44 | alloy-contract = "0.3.1" 45 | zeroize = "1.8.1" 46 | 47 | [patch.crates-io] 48 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 49 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 50 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 51 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 52 | 53 | [patch.'https://github.com/openmina/algebra'] 54 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 55 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 56 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 57 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 58 | -------------------------------------------------------------------------------- /core/graphql/account_query.graphql: -------------------------------------------------------------------------------- 1 | query AccountQuery($stateHash: String!, $publicKey: String!) { 2 | encodedSnarkedLedgerAccountMembership(accountInfos: {publicKey: $publicKey}, stateHash: $stateHash) { 3 | account 4 | merklePath { 5 | left 6 | right 7 | } 8 | } 9 | block(stateHash: $stateHash) { 10 | protocolState { 11 | blockchainState { 12 | snarkedLedgerHash 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/graphql/best_chain_query.graphql: -------------------------------------------------------------------------------- 1 | query BestChainQuery($maxLength: Int!) { 2 | bestChain(maxLength: $maxLength) { 3 | stateHashField 4 | stateHash 5 | protocolStateProof { 6 | base64 7 | } 8 | protocolState { 9 | blockchainState { 10 | snarkedLedgerHash 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/graphql/state_query.graphql: -------------------------------------------------------------------------------- 1 | query StateQuery($stateHash: String!) { 2 | protocolState(encoding: BASE64, stateHash: $stateHash) 3 | } 4 | -------------------------------------------------------------------------------- /core/src/aligned.rs: -------------------------------------------------------------------------------- 1 | use std::{process, str::FromStr}; 2 | 3 | use aligned_sdk::{ 4 | core::types::{ 5 | AlignedVerificationData, FeeEstimationType, Network, ProvingSystemId, VerificationData, 6 | }, 7 | sdk::estimate_fee, 8 | }; 9 | 10 | use ethers::{ 11 | core::k256::ecdsa::SigningKey, 12 | signers::Wallet, 13 | types::{Address, U256}, 14 | }; 15 | use futures::TryFutureExt; 16 | use log::{error, info}; 17 | 18 | use crate::proof::MinaProof; 19 | 20 | /// Submits a Mina Proof to Aligned's batcher and waits until the batch is verified. 21 | #[allow(clippy::too_many_arguments)] 22 | pub async fn submit( 23 | proof: MinaProof, 24 | network: &Network, 25 | proof_generator_addr: &str, 26 | _batcher_addr: &str, 27 | eth_rpc_url: &str, 28 | wallet: Wallet, 29 | save_proof: bool, 30 | ) -> Result { 31 | let (proof, pub_input, proving_system, proof_name, file_name) = match proof { 32 | MinaProof::State((proof, pub_input)) => { 33 | let proof = bincode::serialize(&proof) 34 | .map_err(|err| format!("Failed to serialize state proof: {err}"))?; 35 | let pub_input = bincode::serialize(&pub_input) 36 | .map_err(|err| format!("Failed to serialize public inputs: {err}"))?; 37 | ( 38 | proof, 39 | pub_input, 40 | ProvingSystemId::Mina, 41 | "Mina Proof of State", 42 | "mina_state", 43 | ) 44 | } 45 | MinaProof::Account((proof, pub_input)) => { 46 | let proof = bincode::serialize(&proof) 47 | .map_err(|err| format!("Failed to serialize state proof: {err}"))?; 48 | let pub_input = bincode::serialize(&pub_input) 49 | .map_err(|err| format!("Failed to serialize public inputs: {err}"))?; 50 | ( 51 | proof, 52 | pub_input, 53 | ProvingSystemId::MinaAccount, 54 | "Mina Proof of Account", 55 | "mina_account", 56 | ) 57 | } 58 | }; 59 | 60 | if save_proof { 61 | std::fs::write(format!("./{file_name}.pub"), &pub_input).unwrap_or_else(|err| { 62 | error!("{}", err); 63 | process::exit(1); 64 | }); 65 | std::fs::write(format!("./{file_name}.proof"), &proof).unwrap_or_else(|err| { 66 | error!("{}", err); 67 | process::exit(1); 68 | }); 69 | } 70 | 71 | let proof_generator_addr = 72 | Address::from_str(proof_generator_addr).map_err(|err| err.to_string())?; 73 | 74 | let verification_data = VerificationData { 75 | proving_system, 76 | proof, 77 | pub_input: Some(pub_input), 78 | // Use this instead of `None` to force Aligned to include the commitment to the proving system ID (valid for Aligned 0.7.0) 79 | verification_key: Some(vec![]), 80 | vm_program_code: None, 81 | proof_generator_addr, 82 | }; 83 | 84 | let max_fee = estimate_fee(eth_rpc_url, FeeEstimationType::Instant) 85 | .map_err(|err| err.to_string()) 86 | .await?; 87 | 88 | info!("Max fee: {max_fee} gas"); 89 | 90 | info!("Submitting {proof_name} into Aligned and waiting for the batch to be verified..."); 91 | aligned_sdk::sdk::submit_and_wait_verification( 92 | eth_rpc_url, 93 | network.to_owned(), 94 | &verification_data, 95 | max_fee, 96 | wallet, 97 | U256::from(0), 98 | ) 99 | .await 100 | .map_err(|e| e.to_string()) 101 | } 102 | -------------------------------------------------------------------------------- /core/src/eth.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::sync::Arc; 3 | 4 | use aligned_sdk::core::types::{AlignedVerificationData, Network, VerificationDataCommitment}; 5 | use alloy::network::EthereumWallet; 6 | use alloy::providers::ProviderBuilder; 7 | use alloy::sol; 8 | use ethers::{abi::AbiEncode, prelude::*}; 9 | use k256::ecdsa::SigningKey; 10 | use log::{debug, info}; 11 | use mina_p2p_messages::v2::StateHash; 12 | use serde::{Deserialize, Serialize}; 13 | use serde_with::serde_as; 14 | 15 | use crate::{ 16 | proof::{account_proof::MinaAccountPubInputs, state_proof::MinaStatePubInputs}, 17 | sol::serialization::SolSerialize, 18 | utils::constants::{ANVIL_CHAIN_ID, BRIDGE_TRANSITION_FRONTIER_LEN, HOLESKY_CHAIN_ID}, 19 | }; 20 | 21 | abigen!( 22 | MinaStateSettlementExampleEthereumContract, 23 | "abi/MinaStateSettlementExample.json" 24 | ); 25 | abigen!( 26 | MinaAccountValidationExampleEthereumContract, 27 | "abi/MinaAccountValidationExample.json" 28 | ); 29 | 30 | type MinaStateSettlementExampleEthereum = MinaStateSettlementExampleEthereumContract< 31 | SignerMiddleware, Wallet>, 32 | >; 33 | 34 | type MinaStateSettlementExampleEthereumCallOnly = 35 | MinaStateSettlementExampleEthereumContract>; 36 | type MinaAccountValidationExampleEthereumCallOnly = 37 | MinaAccountValidationExampleEthereumContract>; 38 | 39 | sol!( 40 | #[allow(clippy::too_many_arguments)] 41 | #[sol(rpc)] 42 | MinaStateSettlementExample, 43 | "abi/MinaStateSettlementExample.json" 44 | ); 45 | 46 | sol!( 47 | #[allow(clippy::too_many_arguments)] 48 | #[sol(rpc)] 49 | MinaAccountValidationExample, 50 | "abi/MinaAccountValidationExample.json" 51 | ); 52 | 53 | // Define constant values that will be used for gas limits and calculations 54 | const MAX_GAS_LIMIT_VALUE: u64 = 1_000_000; // Maximum allowed gas for a transaction 55 | const MAX_GAS_PRICE_GWEI: u64 = 300; // Maximum allowed gas price in Gwei 56 | const GAS_ESTIMATE_MARGIN: u64 = 110; // Safety margin (110 means 110%, or +10%) 57 | 58 | /// Wrapper of Mina Ledger hash for Ethereum 59 | #[serde_as] 60 | #[derive(Serialize, Deserialize)] 61 | pub struct SolStateHash(#[serde_as(as = "SolSerialize")] pub StateHash); 62 | 63 | /// Arguments of the Mina State Settlement Example Ethereum Contract constructor: 64 | /// 65 | /// - `aligned_service_addr`: Address of the Aligned Service Manager Ethereum Contract 66 | /// - `root_state_hash`: Root state hash of the Mina transition frontier 67 | pub struct MinaStateSettlementExampleConstructorArgs { 68 | aligned_service_addr: alloy::primitives::Address, 69 | root_state_hash: alloy::primitives::FixedBytes<32>, 70 | } 71 | 72 | /// Arguments of the Mina Account Validation Example Ethereum Contract constructor: 73 | /// 74 | /// - `aligned_service_addr`: Address of the Aligned Service Manager Ethereum Contract 75 | pub struct MinaAccountValidationExampleConstructorArgs { 76 | aligned_service_addr: alloy::primitives::Address, 77 | } 78 | 79 | impl MinaStateSettlementExampleConstructorArgs { 80 | /// Creates the arguments of the Mina State Settlement Example Ethereum Contract constructor. 81 | /// Receives `aligned_service_addr` as a string slice and `root_state_hash` as a vector of bytes 82 | /// and converts them to Ethereum friendly types. 83 | pub fn new(aligned_service_addr: &str, root_state_hash: Vec) -> Result { 84 | let aligned_service_addr = 85 | alloy::primitives::Address::parse_checksummed(aligned_service_addr, None) 86 | .map_err(|err| err.to_string())?; 87 | let root_state_hash = alloy::primitives::FixedBytes( 88 | root_state_hash 89 | .try_into() 90 | .map_err(|_| "Could not convert root state hash into fixed array".to_string())?, 91 | ); 92 | Ok(Self { 93 | aligned_service_addr, 94 | root_state_hash, 95 | }) 96 | } 97 | } 98 | 99 | impl MinaAccountValidationExampleConstructorArgs { 100 | /// Creates the arguments of the Mina Account Validation Example Ethereum Contract constructor. 101 | /// Receives `aligned_service_addr` as a string slice and converts them to Ethereum friendly types. 102 | pub fn new(aligned_service_addr: &str) -> Result { 103 | let aligned_service_addr = 104 | alloy::primitives::Address::parse_checksummed(aligned_service_addr, None) 105 | .map_err(|err| err.to_string())?; 106 | Ok(Self { 107 | aligned_service_addr, 108 | }) 109 | } 110 | } 111 | 112 | // Main function that validates gas parameters 113 | // Takes provider (connection to Ethereum) and estimated_gas as parameters 114 | async fn validate_gas_params( 115 | provider: &Provider, 116 | estimated_gas: U256, 117 | ) -> Result { 118 | // Query the current network gas price 119 | let current_gas_price = provider 120 | .get_gas_price() 121 | .await 122 | .map_err(|err| err.to_string())?; 123 | 124 | // Convert gas price from Wei to Gwei by dividing by 1_000_000_000 125 | let gas_price_gwei = current_gas_price 126 | .checked_div(U256::from(1_000_000_000)) 127 | .ok_or("Gas price calculation overflow")?; 128 | 129 | // Check if the current gas price is above our maximum allowed price 130 | if gas_price_gwei > U256::from(MAX_GAS_PRICE_GWEI) { 131 | return Err(format!( 132 | "Gas price too high: {} gwei (max: {} gwei)", 133 | gas_price_gwei, MAX_GAS_PRICE_GWEI 134 | )); 135 | } 136 | 137 | // Calculate gas limit with safety margin: 138 | // 1. Multiply estimated gas by 110 (for 10% extra) 139 | // 2. Divide by 100 to get the final value 140 | let gas_with_margin = estimated_gas 141 | .checked_mul(U256::from(GAS_ESTIMATE_MARGIN)) 142 | .and_then(|v| v.checked_div(U256::from(100))) 143 | .ok_or("Gas margin calculation overflow")?; 144 | 145 | // Check if our gas limit with margin is above maximum allowed gas 146 | if gas_with_margin > U256::from(MAX_GAS_LIMIT_VALUE) { 147 | return Err(format!( 148 | "Estimated gas too high: {} (max: {})", 149 | gas_with_margin, MAX_GAS_LIMIT_VALUE 150 | )); 151 | } 152 | 153 | // If all checks pass, return the gas limit with safety margin 154 | Ok(gas_with_margin) 155 | } 156 | 157 | /// Wrapper of the `updateChain` function of the Mina State Settlement Example Ethereum Contract with address 158 | /// `contract_addr`. 159 | /// Adapts arguments to be Ethereum friendly and sends the corresponding transaction to run `updateChain` on 160 | /// Ethereum. 161 | /// 162 | /// See [updateChain](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/contract/src/MinaStateSettlementExample.sol#L78) 163 | /// for more info. 164 | pub async fn update_chain( 165 | verification_data: AlignedVerificationData, 166 | pub_input: &MinaStatePubInputs, 167 | network: &Network, 168 | eth_rpc_url: &str, 169 | wallet: Wallet, 170 | contract_addr: &str, 171 | batcher_payment_service: &str, 172 | ) -> Result<(), String> { 173 | let provider = Provider::::try_from(eth_rpc_url).map_err(|err| err.to_string())?; 174 | let bridge_eth_addr = Address::from_str(contract_addr).map_err(|err| err.to_string())?; 175 | 176 | let serialized_pub_input = bincode::serialize(pub_input) 177 | .map_err(|err| format!("Failed to serialize public inputs: {err}"))?; 178 | 179 | let batcher_payment_service = Address::from_str(batcher_payment_service) 180 | .map_err(|err| format!("Failed to parse batcher payment service address: {err}"))?; 181 | 182 | debug!("Creating contract instance"); 183 | let mina_bridge_contract = mina_bridge_contract(eth_rpc_url, bridge_eth_addr, network, wallet)?; 184 | 185 | let AlignedVerificationData { 186 | verification_data_commitment, 187 | batch_merkle_root, 188 | batch_inclusion_proof, 189 | index_in_batch, 190 | } = verification_data; 191 | let merkle_proof = batch_inclusion_proof 192 | .merkle_path 193 | .clone() 194 | .into_iter() 195 | .flatten() 196 | .collect(); 197 | 198 | let VerificationDataCommitment { 199 | proof_commitment, 200 | proving_system_aux_data_commitment, 201 | proof_generator_addr, 202 | .. 203 | } = verification_data_commitment; 204 | 205 | debug!("Updating contract"); 206 | 207 | let update_call = mina_bridge_contract.update_chain( 208 | proof_commitment, 209 | proving_system_aux_data_commitment, 210 | proof_generator_addr, 211 | batch_merkle_root, 212 | merkle_proof, 213 | index_in_batch.into(), 214 | serialized_pub_input.into(), 215 | batcher_payment_service, 216 | ); 217 | // update call reverts if batch is not valid or proof isn't included in it. 218 | 219 | let estimated_gas = update_call 220 | .estimate_gas() 221 | .await 222 | .map_err(|err| err.to_string())?; 223 | 224 | info!("Estimated gas cost: {}", estimated_gas); 225 | 226 | // Validate gas parameters and get safe gas limit 227 | let gas_limit = validate_gas_params(&provider, estimated_gas).await?; 228 | let update_call_with_gas_limit = update_call.gas(gas_limit); 229 | 230 | let pending_tx = update_call_with_gas_limit 231 | .send() 232 | .await 233 | .map_err(|err| err.to_string())?; 234 | info!( 235 | "Transaction {} was submitted and is now pending", 236 | pending_tx.tx_hash().encode_hex() 237 | ); 238 | 239 | let receipt = pending_tx 240 | .await 241 | .map_err(|err| err.to_string())? 242 | .ok_or("Missing transaction receipt")?; 243 | 244 | info!( 245 | "Transaction mined! final gas cost: {}", 246 | receipt.gas_used.ok_or("Missing gas used")? 247 | ); 248 | 249 | info!("Checking that the state hashes were stored correctly.."); 250 | 251 | // TODO(xqft): do the same for ledger hashes 252 | debug!("Getting network state hashes"); 253 | let new_network_state_hashes = get_bridge_chain_state_hashes(contract_addr, eth_rpc_url) 254 | .await 255 | .map_err(|err| err.to_string())?; 256 | 257 | if new_network_state_hashes != pub_input.candidate_chain_state_hashes { 258 | return Err("Stored network state hashes don't match the candidate's".to_string()); 259 | } 260 | 261 | let tip_state_hash = new_network_state_hashes 262 | .last() 263 | .ok_or("Failed to get tip state hash".to_string())? 264 | .clone(); 265 | info!("Successfuly updated smart contract to verified network of tip {tip_state_hash}"); 266 | 267 | Ok(()) 268 | } 269 | 270 | /// Wrapper of the `getTipStateHash` function of the Mina State Settlement Example Ethereum Contract with address 271 | /// `contract_addr`. 272 | /// Calls `getTipStateHash` on Ethereum. 273 | /// 274 | /// See [getTipStateHash](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/contract/src/MinaStateSettlementExample.sol#L44) 275 | /// for more info. 276 | pub async fn get_bridge_tip_hash( 277 | contract_addr: &str, 278 | eth_rpc_url: &str, 279 | ) -> Result { 280 | let bridge_eth_addr = Address::from_str(contract_addr).map_err(|err| err.to_string())?; 281 | 282 | debug!("Creating contract instance"); 283 | let mina_bridge_contract = mina_bridge_contract_call_only(eth_rpc_url, bridge_eth_addr)?; 284 | 285 | let state_hash_bytes = mina_bridge_contract 286 | .get_tip_state_hash() 287 | .await 288 | .map_err(|err| err.to_string())?; 289 | 290 | let state_hash: SolStateHash = bincode::deserialize(&state_hash_bytes) 291 | .map_err(|err| format!("Failed to deserialize bridge tip state hash: {err}"))?; 292 | info!("Retrieved bridge tip state hash: {}", state_hash.0,); 293 | 294 | Ok(state_hash) 295 | } 296 | 297 | /// Wrapper of the `getChainStateHashes` function of the Mina State Settlement Example Ethereum Contract with address 298 | /// `contract_addr`. 299 | /// Calls `getChainStateHashes` on Ethereum. 300 | /// 301 | /// See [getChainStateHashes](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/contract/src/MinaStateSettlementExample.sol#L54) 302 | /// for more info. 303 | pub async fn get_bridge_chain_state_hashes( 304 | contract_addr: &str, 305 | eth_rpc_url: &str, 306 | ) -> Result<[StateHash; BRIDGE_TRANSITION_FRONTIER_LEN], String> { 307 | let bridge_eth_addr = Address::from_str(contract_addr).map_err(|err| err.to_string())?; 308 | 309 | debug!("Creating contract instance"); 310 | let mina_bridge_contract = mina_bridge_contract_call_only(eth_rpc_url, bridge_eth_addr)?; 311 | 312 | mina_bridge_contract 313 | .get_chain_state_hashes() 314 | .await 315 | .map_err(|err| format!("Could not call contract for state hashes: {err}")) 316 | .and_then(|hashes| { 317 | hashes 318 | .into_iter() 319 | .map(|hash| { 320 | bincode::deserialize::(&hash) 321 | .map_err(|err| format!("Failed to deserialize network state hashes: {err}")) 322 | .map(|hash| hash.0) 323 | }) 324 | .collect::, _>>() 325 | }) 326 | .and_then(|hashes| { 327 | hashes 328 | .try_into() 329 | .map_err(|_| "Failed to convert network state hashes vec into array".to_string()) 330 | }) 331 | } 332 | 333 | /// Wrapper of the `validateAccount` function of the Mina Account Validation Example Ethereum Contract with address 334 | /// `contract_addr`. 335 | /// Adapts arguments to be Ethereum friendly and sends the corresponding transaction to run `validateAccount` on 336 | /// Ethereum. 337 | /// 338 | /// See [validateAccount](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/contract/src/MinaAccountValidationExample.sol#L32) 339 | /// for more info. 340 | pub async fn validate_account( 341 | verification_data: AlignedVerificationData, 342 | pub_input: &MinaAccountPubInputs, 343 | eth_rpc_url: &str, 344 | contract_addr: &str, 345 | batcher_payment_service: &str, 346 | ) -> Result<(), String> { 347 | let provider = Provider::::try_from(eth_rpc_url).map_err(|err| err.to_string())?; 348 | let bridge_eth_addr = Address::from_str(contract_addr).map_err(|err| err.to_string())?; 349 | 350 | debug!("Creating contract instance"); 351 | 352 | let contract = mina_account_validation_contract_call_only(eth_rpc_url, bridge_eth_addr)?; 353 | 354 | let serialized_pub_input = bincode::serialize(pub_input) 355 | .map_err(|err| format!("Failed to serialize public inputs: {err}"))?; 356 | 357 | let batcher_payment_service = Address::from_str(batcher_payment_service) 358 | .map_err(|err| format!("Failed to parse batcher payment service address: {err}"))?; 359 | 360 | let AlignedVerificationData { 361 | verification_data_commitment, 362 | batch_merkle_root, 363 | batch_inclusion_proof, 364 | index_in_batch, 365 | } = verification_data; 366 | 367 | let merkle_proof = batch_inclusion_proof 368 | .merkle_path 369 | .clone() 370 | .into_iter() 371 | .flatten() 372 | .collect(); 373 | 374 | let VerificationDataCommitment { 375 | proof_commitment, 376 | proving_system_aux_data_commitment, 377 | proof_generator_addr, 378 | .. 379 | } = verification_data_commitment; 380 | 381 | debug!("Validating account"); 382 | 383 | let aligned_args = AlignedArgs { 384 | proof_commitment, 385 | proving_system_aux_data_commitment, 386 | proof_generator_addr, 387 | batch_merkle_root, 388 | merkle_proof, 389 | verification_data_batch_index: index_in_batch.into(), 390 | pub_input: serialized_pub_input.into(), 391 | batcher_payment_service, 392 | }; 393 | 394 | let call = contract.validate_account(aligned_args); 395 | let estimated_gas = call.estimate_gas().await.map_err(|err| err.to_string())?; 396 | 397 | info!("Estimated account verification gas cost: {estimated_gas}"); 398 | 399 | let gas_limit = validate_gas_params(&provider, estimated_gas).await?; 400 | 401 | call.gas(gas_limit).await.map_err(|err| err.to_string())?; 402 | 403 | Ok(()) 404 | } 405 | 406 | /// Deploys the Mina State Settlement Example Contract on Ethereum 407 | pub async fn deploy_mina_bridge_example_contract( 408 | eth_rpc_url: &str, 409 | constructor_args: &MinaStateSettlementExampleConstructorArgs, 410 | wallet: &EthereumWallet, 411 | is_state_proof_from_devnet: bool, 412 | ) -> Result { 413 | let provider = ProviderBuilder::new() 414 | .with_recommended_fillers() 415 | .wallet(wallet) 416 | .on_http(reqwest::Url::parse(eth_rpc_url).map_err(|err| err.to_string())?); 417 | 418 | let MinaStateSettlementExampleConstructorArgs { 419 | aligned_service_addr, 420 | root_state_hash, 421 | } = constructor_args; 422 | let contract = MinaStateSettlementExample::deploy( 423 | &provider, 424 | *aligned_service_addr, 425 | *root_state_hash, 426 | is_state_proof_from_devnet, 427 | ) 428 | .await 429 | .map_err(|err| err.to_string())?; 430 | let address = contract.address(); 431 | 432 | let network = if is_state_proof_from_devnet { 433 | "Devnet" 434 | } else { 435 | "Mainnet" 436 | }; 437 | 438 | info!( 439 | "Mina {} Bridge example contract successfuly deployed with address {}", 440 | network, address 441 | ); 442 | info!( 443 | "Set STATE_SETTLEMENT_ETH_ADDR={} if using Mina {}", 444 | address, network 445 | ); 446 | 447 | Ok(*address) 448 | } 449 | 450 | /// Deploys the Mina Account Validation Example Contract on Ethereum 451 | pub async fn deploy_mina_account_validation_example_contract( 452 | eth_rpc_url: &str, 453 | constructor_args: MinaAccountValidationExampleConstructorArgs, 454 | wallet: &EthereumWallet, 455 | ) -> Result { 456 | let provider = ProviderBuilder::new() 457 | .with_recommended_fillers() 458 | .wallet(wallet) 459 | .on_http(reqwest::Url::parse(eth_rpc_url).map_err(|err| err.to_string())?); 460 | 461 | let MinaAccountValidationExampleConstructorArgs { 462 | aligned_service_addr, 463 | } = constructor_args; 464 | let contract = MinaAccountValidationExample::deploy(&provider, aligned_service_addr) 465 | .await 466 | .map_err(|err| err.to_string())?; 467 | let address = contract.address(); 468 | 469 | info!( 470 | "Mina Account Validation example contract successfuly deployed with address {}", 471 | address 472 | ); 473 | info!("Set ACCOUNT_VALIDATION_ETH_ADDR={}", address); 474 | 475 | Ok(*address) 476 | } 477 | 478 | fn mina_bridge_contract( 479 | eth_rpc_url: &str, 480 | contract_address: Address, 481 | network: &Network, 482 | wallet: Wallet, 483 | ) -> Result { 484 | let eth_rpc_provider = 485 | Provider::::try_from(eth_rpc_url).map_err(|err| err.to_string())?; 486 | let network_id = match network { 487 | Network::Devnet => ANVIL_CHAIN_ID, 488 | Network::Holesky => HOLESKY_CHAIN_ID, 489 | _ => unimplemented!(), 490 | }; 491 | let signer = SignerMiddleware::new(eth_rpc_provider, wallet.with_chain_id(network_id)); 492 | let client = Arc::new(signer); 493 | debug!("contract address: {contract_address}"); 494 | Ok(MinaStateSettlementExampleEthereum::new( 495 | contract_address, 496 | client, 497 | )) 498 | } 499 | 500 | fn mina_bridge_contract_call_only( 501 | eth_rpc_url: &str, 502 | contract_address: Address, 503 | ) -> Result { 504 | let eth_rpc_provider = 505 | Provider::::try_from(eth_rpc_url).map_err(|err| err.to_string())?; 506 | let client = Arc::new(eth_rpc_provider); 507 | Ok(MinaStateSettlementExampleEthereumCallOnly::new( 508 | contract_address, 509 | client, 510 | )) 511 | } 512 | 513 | fn mina_account_validation_contract_call_only( 514 | eth_rpc_url: &str, 515 | contract_address: Address, 516 | ) -> Result { 517 | let eth_rpc_provider = 518 | Provider::::try_from(eth_rpc_url).map_err(|err| err.to_string())?; 519 | let client = Arc::new(eth_rpc_provider); 520 | Ok(MinaAccountValidationExampleEthereumCallOnly::new( 521 | contract_address, 522 | client, 523 | )) 524 | } 525 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Sends Mina proofs to AlignedLayer. 2 | pub mod aligned; 3 | /// Interacts with the bridge's example smart contracts on Ethereum. 4 | pub mod eth; 5 | /// Interacts with a Mina node for requesting proofs and data. 6 | pub mod mina; 7 | /// Mina Proof of State/Account definitions and (de)serialization. 8 | pub mod proof; 9 | /// High level abstractions for the bridge. 10 | pub mod sdk; 11 | /// Solidity-friendly data structures and serialization. 12 | pub mod sol; 13 | /// Internal utils. 14 | pub mod utils; 15 | -------------------------------------------------------------------------------- /core/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use log::{error, info}; 3 | use mina_bridge_core::{ 4 | aligned, eth, mina, 5 | proof::MinaProof, 6 | utils::{env::EnvironmentVariables, wallet::get_wallet}, 7 | }; 8 | use std::{process, time::SystemTime}; 9 | 10 | #[derive(Parser)] 11 | #[command(version, about)] 12 | struct Cli { 13 | #[command(subcommand)] 14 | command: Command, 15 | } 16 | 17 | #[derive(Subcommand)] 18 | enum Command { 19 | SubmitState { 20 | #[arg(short, long)] 21 | devnet: bool, 22 | /// Write the proof into .proof and .pub files 23 | #[arg(short, long)] 24 | save_proof: bool, 25 | }, 26 | SubmitAccount { 27 | /// Write the proof into .proof and .pub files 28 | #[arg(short, long)] 29 | save_proof: bool, 30 | /// Public key string of the account to verify 31 | public_key: String, 32 | /// Hash of the state to verify the account for 33 | state_hash: String, 34 | }, 35 | } 36 | 37 | #[tokio::main] 38 | async fn main() { 39 | let cli = Cli::parse(); 40 | let now = SystemTime::now(); 41 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 42 | 43 | let EnvironmentVariables { 44 | rpc_url, 45 | network, 46 | state_settlement_addr, 47 | account_validation_addr, 48 | batcher_addr, 49 | batcher_eth_addr, 50 | eth_rpc_url, 51 | proof_generator_addr, 52 | keystore_path, 53 | private_key, 54 | } = EnvironmentVariables::new().unwrap_or_else(|err| { 55 | error!("{}", err); 56 | process::exit(1); 57 | }); 58 | 59 | let state_settlement_addr = state_settlement_addr.unwrap_or_else(|| { 60 | error!("Error getting State settlement contract address"); 61 | process::exit(1); 62 | }); 63 | let account_validation_addr = account_validation_addr.unwrap_or_else(|| { 64 | error!("Error getting Account validation contract address"); 65 | process::exit(1); 66 | }); 67 | 68 | let wallet = get_wallet(&network, keystore_path.as_deref(), private_key.as_deref()) 69 | .unwrap_or_else(|err| { 70 | error!("{}", err); 71 | process::exit(1); 72 | }); 73 | 74 | match cli.command { 75 | Command::SubmitState { devnet, save_proof } => { 76 | let (proof, pub_input) = mina::get_mina_proof_of_state( 77 | &rpc_url, 78 | ð_rpc_url, 79 | &state_settlement_addr, 80 | devnet, 81 | ) 82 | .await 83 | .unwrap_or_else(|err| { 84 | error!("{}", err); 85 | process::exit(1); 86 | }); 87 | 88 | let verification_data = aligned::submit( 89 | MinaProof::State((proof, pub_input.clone())), 90 | &network, 91 | &proof_generator_addr, 92 | &batcher_addr, 93 | ð_rpc_url, 94 | wallet.clone(), 95 | save_proof, 96 | ) 97 | .await 98 | .unwrap_or_else(|err| { 99 | error!("{}", err); 100 | process::exit(1); 101 | }); 102 | 103 | eth::update_chain( 104 | verification_data, 105 | &pub_input, 106 | &network, 107 | ð_rpc_url, 108 | wallet, 109 | &state_settlement_addr, 110 | &batcher_eth_addr, 111 | ) 112 | .await 113 | .unwrap_or_else(|err| { 114 | error!("{}", err); 115 | process::exit(1); 116 | }); 117 | } 118 | Command::SubmitAccount { 119 | save_proof, 120 | public_key, 121 | state_hash, 122 | } => { 123 | let (proof, pub_input) = 124 | mina::get_mina_proof_of_account(&public_key, &state_hash, &rpc_url) 125 | .await 126 | .unwrap_or_else(|err| { 127 | error!("{}", err); 128 | process::exit(1); 129 | }); 130 | 131 | let verification_data = aligned::submit( 132 | MinaProof::Account((proof, pub_input.clone())), 133 | &network, 134 | &proof_generator_addr, 135 | &batcher_addr, 136 | ð_rpc_url, 137 | wallet.clone(), 138 | save_proof, 139 | ) 140 | .await 141 | .unwrap_or_else(|err| { 142 | error!("{}", err); 143 | process::exit(1); 144 | }); 145 | 146 | if let Err(err) = eth::validate_account( 147 | verification_data, 148 | &pub_input, 149 | ð_rpc_url, 150 | &account_validation_addr, 151 | &batcher_eth_addr, 152 | ) 153 | .await 154 | { 155 | error!("Mina account {public_key} was not validated: {err}",); 156 | } else { 157 | info!("Mina account {public_key} was validated!"); 158 | }; 159 | } 160 | } 161 | 162 | if let Ok(elapsed) = now.elapsed() { 163 | info!("Time spent: {} s", elapsed.as_secs()); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /core/src/mina.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use alloy_sol_types::SolValue; 4 | use base64::prelude::*; 5 | use futures::future::join_all; 6 | use graphql_client::{ 7 | reqwest::{post_graphql, post_graphql_blocking}, 8 | GraphQLQuery, 9 | }; 10 | use kimchi::mina_curves::pasta::Fp; 11 | use log::{debug, info}; 12 | use mina_p2p_messages::{ 13 | binprot::BinProtRead, 14 | v2::{ 15 | LedgerHash, MinaBaseAccountBinableArgStableV2 as MinaAccount, MinaBaseProofStableV2, 16 | MinaStateProtocolStateValueStableV2, StateHash, 17 | }, 18 | }; 19 | 20 | use crate::{ 21 | eth::get_bridge_tip_hash, 22 | proof::{ 23 | account_proof::{MerkleNode, MinaAccountProof, MinaAccountPubInputs}, 24 | state_proof::{MinaStateProof, MinaStatePubInputs}, 25 | }, 26 | sol::account::MinaAccountValidationExample, 27 | utils::constants::BRIDGE_TRANSITION_FRONTIER_LEN, 28 | }; 29 | 30 | type StateHashAsDecimal = String; 31 | type PrecomputedBlockProof = String; 32 | type FieldElem = String; 33 | 34 | #[derive(GraphQLQuery)] 35 | #[graphql( 36 | schema_path = "graphql/mina_schema.json", 37 | query_path = "graphql/state_query.graphql" 38 | )] 39 | /// A query for a protocol state given some state hash (non-field). 40 | struct StateQuery; 41 | 42 | #[derive(GraphQLQuery)] 43 | #[graphql( 44 | schema_path = "graphql/mina_schema.json", 45 | query_path = "graphql/best_chain_query.graphql" 46 | )] 47 | /// A query for the state hashes and proofs of the transition frontier. 48 | struct BestChainQuery; 49 | 50 | #[derive(GraphQLQuery)] 51 | #[graphql( 52 | schema_path = "graphql/mina_schema.json", 53 | query_path = "graphql/account_query.graphql" 54 | )] 55 | /// A query for retrieving an a Mina account state at some block, along with its ledger hash and 56 | /// merkle path. 57 | struct AccountQuery; 58 | 59 | /// Queries the Mina state from the Mina Node and returns the proof that the queried Mina state is the last finalized state 60 | /// of the blockchain. 61 | /// This proof along its public inputs are structured so that they can be sent to Aligned Layer to be verified. 62 | /// This function also queries info from the Mina State Settlement Example Ethereum Contract to fetch one of the public 63 | /// inputs. 64 | /// 65 | /// The queried data consists of: 66 | /// 67 | /// - Bridge tip state hash from the Mina State Settlement Example Ethereum Contract 68 | /// - Mina candidate chain states from the Mina node 69 | /// - Mina Bridge tip state from the Mina node 70 | pub async fn get_mina_proof_of_state( 71 | rpc_url: &str, 72 | eth_rpc_url: &str, 73 | contract_addr: &str, 74 | is_state_proof_from_devnet: bool, 75 | ) -> Result<(MinaStateProof, MinaStatePubInputs), String> { 76 | let bridge_tip_state_hash = get_bridge_tip_hash(contract_addr, eth_rpc_url).await?.0; 77 | let ( 78 | candidate_chain_states, 79 | candidate_chain_state_hashes, 80 | candidate_chain_ledger_hashes, 81 | candidate_tip_proof, 82 | ) = query_candidate_chain(rpc_url).await?; 83 | 84 | let candidate_tip_state_hash = candidate_chain_state_hashes 85 | .last() 86 | .ok_or("Missing candidate tip state hash".to_string())?; 87 | 88 | let bridge_tip_state = query_state(rpc_url, &bridge_tip_state_hash).await?; 89 | 90 | info!("Queried Mina candidate chain with tip {candidate_tip_state_hash} and its proof"); 91 | 92 | Ok(( 93 | MinaStateProof { 94 | candidate_tip_proof, 95 | candidate_chain_states, 96 | bridge_tip_state, 97 | }, 98 | MinaStatePubInputs { 99 | is_state_proof_from_devnet, 100 | bridge_tip_state_hash, 101 | candidate_chain_state_hashes, 102 | candidate_chain_ledger_hashes, 103 | }, 104 | )) 105 | } 106 | 107 | /// Queries the state of the account that corresponds to `public_key` from the Mina node and returns the proof that the 108 | /// queried account is included in the ledger hash. 109 | /// This proof along its public inputs are structured so that they can be sent to Aligned Layer to be verified. 110 | /// 111 | /// The proof consists of: 112 | /// 113 | /// - A Merkle root which maps to the ledger hash. 114 | /// - A Merkle leaf which maps to the queried account. 115 | /// - A Merkle path from the root to the leaf both mentioned above. 116 | pub async fn get_mina_proof_of_account( 117 | public_key: &str, 118 | state_hash: &str, 119 | rpc_url: &str, 120 | ) -> Result<(MinaAccountProof, MinaAccountPubInputs), String> { 121 | let (account, ledger_hash, merkle_path) = 122 | query_account(rpc_url, state_hash, public_key).await?; 123 | 124 | let encoded_account = MinaAccountValidationExample::Account::try_from(&account)?.abi_encode(); 125 | 126 | debug!( 127 | "Retrieved proof of account for ledger {}", 128 | LedgerHash::from_fp(ledger_hash) 129 | ); 130 | 131 | Ok(( 132 | MinaAccountProof { 133 | merkle_path, 134 | account, 135 | }, 136 | MinaAccountPubInputs { 137 | ledger_hash, 138 | encoded_account, 139 | }, 140 | )) 141 | } 142 | 143 | async fn query_state( 144 | rpc_url: &str, 145 | state_hash: &StateHash, 146 | ) -> Result { 147 | let variables = state_query::Variables { 148 | state_hash: state_hash.to_string(), 149 | }; 150 | debug!("Querying state {}", variables.state_hash); 151 | let client = reqwest::Client::new(); 152 | let proof = post_graphql::(&client, rpc_url, variables) 153 | .await 154 | .map_err(|err| err.to_string())? 155 | .data 156 | .ok_or("Missing state query response data".to_string()) 157 | .map(|response| response.protocol_state) 158 | .and_then(|base64| { 159 | BASE64_STANDARD 160 | .decode(base64) 161 | .map_err(|err| format!("Couldn't decode state from base64: {err}")) 162 | }) 163 | .and_then(|binprot| { 164 | MinaStateProtocolStateValueStableV2::binprot_read(&mut binprot.as_slice()) 165 | .map_err(|err| format!("Couldn't read state binprot: {err}")) 166 | })?; 167 | Ok(proof) 168 | } 169 | 170 | async fn query_candidate_chain( 171 | rpc_url: &str, 172 | ) -> Result< 173 | ( 174 | [MinaStateProtocolStateValueStableV2; BRIDGE_TRANSITION_FRONTIER_LEN], 175 | [StateHash; BRIDGE_TRANSITION_FRONTIER_LEN], 176 | [LedgerHash; BRIDGE_TRANSITION_FRONTIER_LEN], 177 | MinaBaseProofStableV2, 178 | ), 179 | String, 180 | > { 181 | debug!("Querying for candidate state"); 182 | let client = reqwest::blocking::Client::new(); 183 | let variables = best_chain_query::Variables { 184 | max_length: BRIDGE_TRANSITION_FRONTIER_LEN 185 | .try_into() 186 | .map_err(|_| "Transition frontier length conversion failure".to_string())?, 187 | }; 188 | let response = post_graphql_blocking::(&client, rpc_url, variables) 189 | .map_err(|err| err.to_string())? 190 | .data 191 | .ok_or("Missing candidate query response data".to_string())?; 192 | let best_chain = response 193 | .best_chain 194 | .ok_or("Missing best chain field".to_string())?; 195 | if best_chain.len() != BRIDGE_TRANSITION_FRONTIER_LEN { 196 | return Err(format!( 197 | "Not enough blocks ({}) were returned from query", 198 | best_chain.len() 199 | )); 200 | } 201 | let chain_state_hashes: [StateHash; BRIDGE_TRANSITION_FRONTIER_LEN] = best_chain 202 | .iter() 203 | .map(|state| state.state_hash.clone()) 204 | .collect::>() 205 | .try_into() 206 | .map_err(|_| "Failed to convert chain state hashes vector into array".to_string())?; 207 | let chain_ledger_hashes: [LedgerHash; BRIDGE_TRANSITION_FRONTIER_LEN] = best_chain 208 | .iter() 209 | .map(|state| { 210 | state 211 | .protocol_state 212 | .blockchain_state 213 | .snarked_ledger_hash 214 | .clone() 215 | }) 216 | .collect::>() 217 | .try_into() 218 | .map_err(|_| "Failed to convert chain ledger hashes vector into array".to_string())?; 219 | 220 | let chain_states = join_all( 221 | chain_state_hashes 222 | .iter() 223 | .map(|state_hash| query_state(rpc_url, state_hash)), 224 | ) 225 | .await 226 | .into_iter() 227 | .collect::, _>>() 228 | .and_then(|states| { 229 | states 230 | .try_into() 231 | .map_err(|_| "Couldn't convert vector of states to array".to_string()) 232 | })?; 233 | 234 | let tip = best_chain.last().ok_or("Missing best chain".to_string())?; 235 | let tip_state_proof = tip 236 | .protocol_state_proof 237 | .base64 238 | .clone() 239 | .ok_or("No tip state proof".to_string()) 240 | .and_then(|base64| { 241 | BASE64_URL_SAFE 242 | .decode(base64) 243 | .map_err(|err| format!("Couldn't decode state proof from base64: {err}")) 244 | }) 245 | .and_then(|binprot| { 246 | MinaBaseProofStableV2::binprot_read(&mut binprot.as_slice()) 247 | .map_err(|err| format!("Couldn't read state proof binprot: {err}")) 248 | })?; 249 | 250 | debug!("Queried state hashes: {chain_state_hashes:?}"); 251 | debug!("Queried ledger hashes: {chain_ledger_hashes:?}"); 252 | 253 | Ok(( 254 | chain_states, 255 | chain_state_hashes, 256 | chain_ledger_hashes, 257 | tip_state_proof, 258 | )) 259 | } 260 | 261 | /// Queries the Mina node with URL `rpc_url` for the root state hash of the transition frontier. 262 | /// Returns the ledger hash structured so that it can be sent to the Mina State Settlement Ethereum Contract Example 263 | /// constructor. 264 | pub async fn query_root(rpc_url: &str, length: usize) -> Result { 265 | let client = reqwest::Client::new(); 266 | let variables = best_chain_query::Variables { 267 | max_length: length as i64, 268 | }; 269 | let response = post_graphql::(&client, rpc_url, variables) 270 | .await 271 | .map_err(|err| err.to_string())? 272 | .data 273 | .ok_or("Missing root hash query response data".to_string())?; 274 | let best_chain = response 275 | .best_chain 276 | .ok_or("Missing best chain field".to_string())?; 277 | let root = best_chain.first().ok_or("No root state")?; 278 | Ok(root.state_hash.clone()) 279 | } 280 | 281 | async fn query_account( 282 | rpc_url: &str, 283 | state_hash: &str, 284 | public_key: &str, 285 | ) -> Result<(MinaAccount, Fp, Vec), String> { 286 | debug!( 287 | "Querying account {public_key}, its merkle proof and ledger hash for state {state_hash}" 288 | ); 289 | let client = reqwest::Client::new(); 290 | 291 | let variables = account_query::Variables { 292 | state_hash: state_hash.to_owned(), 293 | public_key: public_key.to_owned(), 294 | }; 295 | 296 | let response = post_graphql::(&client, rpc_url, variables) 297 | .await 298 | .map_err(|err| err.to_string())? 299 | .data 300 | .ok_or("Missing merkle query response data".to_string())?; 301 | 302 | let membership = response 303 | .encoded_snarked_ledger_account_membership 304 | .first() 305 | .ok_or("Failed to retrieve membership query field".to_string())?; 306 | 307 | let account = BASE64_STANDARD 308 | .decode(&membership.account) 309 | .map_err(|err| format!("Failed to decode account from base64: {err}")) 310 | .and_then(|binprot| { 311 | MinaAccount::binprot_read(&mut binprot.as_slice()) 312 | .map_err(|err| format!("Failed to deserialize account binprot: {err}")) 313 | })?; 314 | 315 | debug!( 316 | "Queried account {} with token id {}", 317 | account.public_key, 318 | account.token_id //Into::::into(account.token_id.clone()) 319 | ); 320 | 321 | let ledger_hash = response 322 | .block 323 | .protocol_state 324 | .blockchain_state 325 | .snarked_ledger_hash 326 | .to_fp() 327 | .unwrap(); 328 | 329 | let merkle_path = membership 330 | .merkle_path 331 | .iter() 332 | .map(|node| -> Result { 333 | match (node.left.as_ref(), node.right.as_ref()) { 334 | (Some(fp_str), None) => Ok(MerkleNode::Left(Fp::from_str(fp_str)?)), 335 | (None, Some(fp_str)) => Ok(MerkleNode::Right(Fp::from_str(fp_str)?)), 336 | _ => unreachable!(), 337 | } 338 | }) 339 | .collect::, ()>>() 340 | .map_err(|_| "Error deserializing merkle path nodes".to_string())?; 341 | 342 | Ok((account, ledger_hash, merkle_path)) 343 | } 344 | -------------------------------------------------------------------------------- /core/src/proof/account_proof.rs: -------------------------------------------------------------------------------- 1 | use mina_curves::pasta::Fp; 2 | use mina_p2p_messages::v2::MinaBaseAccountBinableArgStableV2 as MinaAccount; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_with::serde_as; 5 | 6 | use crate::sol::serialization::SolSerialize; 7 | 8 | /// Node of the Merkle path used to build the proof that a Mina account is included in the ledger hash 9 | #[serde_as] 10 | #[derive(Serialize, Deserialize)] 11 | pub enum MerkleNode { 12 | Left(#[serde_as(as = "o1_utils::serialization::SerdeAs")] Fp), 13 | Right(#[serde_as(as = "o1_utils::serialization::SerdeAs")] Fp), 14 | } 15 | 16 | /// Public inputs of the proof that a Mina account is included in the ledger hash 17 | #[serde_as] 18 | #[derive(Serialize, Deserialize, Clone)] 19 | pub struct MinaAccountPubInputs { 20 | /// Hash of the snarked ledger that this account state is included on 21 | #[serde_as(as = "SolSerialize")] 22 | pub ledger_hash: Fp, 23 | /// ABI encoded Mina account (Solidity structure) 24 | pub encoded_account: Vec, 25 | } 26 | 27 | /// Proof that a Mina account is included in the ledger hash 28 | #[serde_as] 29 | #[derive(Serialize, Deserialize)] 30 | pub struct MinaAccountProof { 31 | /// Merkle path between the leaf hash (account hash) and the merkle root (ledger hash) 32 | pub merkle_path: Vec, 33 | /// The Mina account 34 | pub account: MinaAccount, 35 | } 36 | -------------------------------------------------------------------------------- /core/src/proof/mod.rs: -------------------------------------------------------------------------------- 1 | use account_proof::{MinaAccountProof, MinaAccountPubInputs}; 2 | use state_proof::{MinaStateProof, MinaStatePubInputs}; 3 | 4 | /// Mina Proof of Account definition. 5 | pub mod account_proof; 6 | /// Mina Proof of State definition. 7 | pub mod state_proof; 8 | 9 | // TODO(xqft): we should fix this lint instead 10 | #[allow(clippy::large_enum_variant)] 11 | pub enum MinaProof { 12 | State((MinaStateProof, MinaStatePubInputs)), 13 | Account((MinaAccountProof, MinaAccountPubInputs)), 14 | } 15 | -------------------------------------------------------------------------------- /core/src/proof/state_proof.rs: -------------------------------------------------------------------------------- 1 | use mina_p2p_messages::v2::{ 2 | LedgerHash, MinaBaseProofStableV2, MinaStateProtocolStateValueStableV2, StateHash, 3 | }; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_with::serde_as; 6 | 7 | use crate::{sol::serialization::SolSerialize, utils::constants::BRIDGE_TRANSITION_FRONTIER_LEN}; 8 | 9 | /// The public inputs of the proof that a certain Mina state is valid 10 | #[serde_as] 11 | #[derive(Serialize, Deserialize, Clone)] 12 | pub struct MinaStatePubInputs { 13 | pub is_state_proof_from_devnet: bool, 14 | /// The hash of the bridge's transition frontier tip state. Used for making sure that we're 15 | /// checking if a candidate tip is better than the latest bridged tip. 16 | #[serde_as(as = "SolSerialize")] 17 | pub bridge_tip_state_hash: StateHash, 18 | /// The state hashes of the candidate chain. 19 | #[serde_as(as = "[SolSerialize; BRIDGE_TRANSITION_FRONTIER_LEN]")] 20 | pub candidate_chain_state_hashes: [StateHash; BRIDGE_TRANSITION_FRONTIER_LEN], 21 | /// The ledger hashes of the candidate chain. The ledger hashes are the root of a Merkle tree 22 | /// where the leafs are Mina account hashes. Used for account verification. 23 | #[serde_as(as = "[SolSerialize; BRIDGE_TRANSITION_FRONTIER_LEN]")] 24 | pub candidate_chain_ledger_hashes: [LedgerHash; BRIDGE_TRANSITION_FRONTIER_LEN], 25 | } 26 | 27 | /// The proof that a certain Mina state is valid 28 | #[derive(Serialize, Deserialize)] 29 | pub struct MinaStateProof { 30 | /// The state proof of the tip state (latest state of the chain, or "transition frontier"). If 31 | /// this state is valid, then all previous states are valid thanks to Pickles recursion. 32 | pub candidate_tip_proof: MinaBaseProofStableV2, 33 | /// The state data of the candidate chain. Used for consensus checks and checking that the 34 | /// public input state hashes correspond to states that effectively form a chain. 35 | pub candidate_chain_states: 36 | [MinaStateProtocolStateValueStableV2; BRIDGE_TRANSITION_FRONTIER_LEN], 37 | /// The latest state of the previously bridged chain, the latter also called the bridge's 38 | /// transition frontier. Used for consensus checks needed to be done as part of state 39 | /// verification to ensure that the candidate tip is better than the bridged tip. 40 | pub bridge_tip_state: MinaStateProtocolStateValueStableV2, 41 | } 42 | -------------------------------------------------------------------------------- /core/src/sdk.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use aligned_sdk::core::types::{AlignedVerificationData, Network, VerificationDataCommitment}; 4 | use ethers::{core::k256::ecdsa::SigningKey, signers::Wallet}; 5 | use log::debug; 6 | use mina_p2p_messages::v2::StateHash; 7 | 8 | use crate::{ 9 | aligned::submit, 10 | eth::{self, get_bridge_chain_state_hashes, update_chain}, 11 | mina::{get_mina_proof_of_account, get_mina_proof_of_state}, 12 | proof::MinaProof, 13 | }; 14 | 15 | /// Minimum data needed to verify a Mina account on Ethereum. 16 | /// To use this struct you need to: 17 | /// 18 | /// 1. Deploy an Ethereum contract that refers to a deployed Mina zkapp. 19 | /// The contract functions that mimic the ones from Mina should recieve this struct's fields as arguments. 20 | /// The contract's functions should verify that the referred Mina account is included in the 21 | /// ledger hash and also run the same logic that the function of the Mina zkapp the Ethereum contract is 22 | /// mimicing. 23 | /// 1. Call `validate_account` function which creates an instance of this struct. 24 | /// 1. Send a transaction that calls the verification function and pass the struct fields as arguments. 25 | /// 26 | /// For reference see [SudokuValidity](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/example/eth_contract/src/SudokuValidity.sol) 27 | /// example contract and [how can it be called](https://github.com/lambdaclass/mina_bridge/blob/7f2fa1f0eac39499ff2ed3ed2d989ea7314805e3/example/app/src/main.rs#L175-L200). 28 | pub struct AccountVerificationData { 29 | pub proof_commitment: [u8; 32], 30 | pub proving_system_aux_data_commitment: [u8; 32], 31 | pub proof_generator_addr: [u8; 20], 32 | pub batch_merkle_root: [u8; 32], 33 | pub merkle_proof: Vec, 34 | pub verification_data_batch_index: usize, 35 | pub pub_input: Vec, 36 | } 37 | 38 | /// Given a Mina state `hash`, checks that it has been verified by calling the Mina State Settlement Example Contract with 39 | /// address `state_settlement_addr`. 40 | /// The function `updateChain` of the example contract verifies the Mina state. 41 | /// So `is_state_verified` returns `true` if the `updateChain` function of the example contract was called by passing the 42 | /// Mina state `hash` and the Mina state was considered valid. Returns `false` otherwise. 43 | pub async fn is_state_verified( 44 | hash: &str, 45 | state_settlement_addr: &str, 46 | eth_rpc_url: &str, 47 | ) -> Result { 48 | let chain_state_hashes = 49 | get_bridge_chain_state_hashes(state_settlement_addr, eth_rpc_url).await?; 50 | let hash = StateHash::from_str(hash) 51 | .map_err(|err| format!("Failed to convert hash string to state hash: {err}"))?; 52 | Ok(chain_state_hashes.contains(&hash)) 53 | } 54 | 55 | /// Returns the hash of the Mina tip state stored on Ethereum. 56 | /// This function calls the Mina State Settlement Example Contract. 57 | pub async fn get_bridged_chain_tip_state_hash( 58 | state_settlement_addr: &str, 59 | eth_rpc_url: &str, 60 | ) -> Result { 61 | get_bridge_chain_state_hashes(state_settlement_addr, eth_rpc_url) 62 | .await 63 | .map(|hashes| hashes.last().unwrap().to_string()) 64 | } 65 | 66 | /// Updates the Mina state bridged on Ethereum using the Mina State Settlement Example Contract. 67 | /// 68 | /// Arguments: 69 | /// 70 | /// - `rpc_url`: Mina node RPC URL to get the Mina state 71 | /// - `network`: Enum variant to specify the Ethereum network to update the Mina state 72 | /// - `state_settlement_addr`: Address of the Mina State Settlement Example Contract 73 | /// - `batcher_addr`: Address of the Aligned Batcher Service 74 | /// - `eth_rpc_url`: Ethereum node RPC URL to send the transaction to update the Mina state 75 | /// - `proof_generator_addr`: Address of the Aligned Proof Generator 76 | /// - `wallet`: Ethereum wallet used to sign transactions for Aligned verification and Mina state update 77 | /// - `batcher_payment_service`: Address of the Aligned Batcher Payment Service 78 | /// - `is_state_proof_from_devnet`: `true` if the Mina state to fetch is from Mina Devnet. `false` if it is from Mainnet. 79 | /// - `save_proof`: `true` if the proof with its public inputs are persisted in a file. `false` otherwise. 80 | #[allow(clippy::too_many_arguments)] 81 | pub async fn update_bridge_chain( 82 | rpc_url: &str, 83 | network: &Network, 84 | state_settlement_addr: &str, 85 | batcher_addr: &str, 86 | eth_rpc_url: &str, 87 | proof_generator_addr: &str, 88 | wallet: Wallet, 89 | batcher_payment_service: &str, 90 | is_state_proof_from_devnet: bool, 91 | save_proof: bool, 92 | ) -> Result<(), String> { 93 | let (proof, pub_input) = get_mina_proof_of_state( 94 | rpc_url, 95 | eth_rpc_url, 96 | state_settlement_addr, 97 | is_state_proof_from_devnet, 98 | ) 99 | .await?; 100 | 101 | if pub_input.candidate_chain_state_hashes 102 | == get_bridge_chain_state_hashes(state_settlement_addr, eth_rpc_url).await? 103 | { 104 | debug!("The bridge chain is updated to the candidate chain"); 105 | return Err("Latest chain is already verified".to_string()); 106 | } 107 | 108 | let verification_data = submit( 109 | MinaProof::State((proof, pub_input.clone())), 110 | network, 111 | proof_generator_addr, 112 | batcher_addr, 113 | eth_rpc_url, 114 | wallet.clone(), 115 | save_proof, 116 | ) 117 | .await?; 118 | 119 | update_chain( 120 | verification_data, 121 | &pub_input, 122 | network, 123 | eth_rpc_url, 124 | wallet, 125 | state_settlement_addr, 126 | batcher_payment_service, 127 | ) 128 | .await?; 129 | 130 | Ok(()) 131 | } 132 | 133 | /// Validates that a Mina account is included of the ledger hash that corresponds to a valid Mina state bridged on Ethereum. 134 | /// Calls the Mina Account Validation Example Contract. 135 | /// 136 | /// Arguments: 137 | /// 138 | /// - `public_key`: Public key of the Mina account to validate. 139 | /// - `state_hash`: Hash of the Mina state that includes the Mina account state to validate. 140 | /// - `rpc_url`: Mina node RPC URL to get the Mina state 141 | /// - `network`: Enum variant to specify the Ethereum network to update the Mina state 142 | /// - `account_validation_addr`: Address of the Mina Account Validation Example Contract 143 | /// - `batcher_addr`: Address of the Aligned Batcher Contract 144 | /// - `eth_rpc_url`: Ethereum node RPC URL to send the transaction to update the Mina state 145 | /// - `proof_generator_addr`: Address of the Aligned Proof Generator 146 | /// - `batcher_payment_service`: Address of the Aligned Batcher Payment Service 147 | /// - `wallet`: Ethereum wallet used to sign transactions for Aligned verification and Mina state update 148 | /// - `save_proof`: `true` if the proof with its public inputs are persisted in a file. `false` otherwise. 149 | #[allow(clippy::too_many_arguments)] 150 | pub async fn validate_account( 151 | public_key: &str, 152 | state_hash: &str, 153 | rpc_url: &str, 154 | network: &Network, 155 | account_validation_addr: &str, 156 | batcher_addr: &str, 157 | eth_rpc_url: &str, 158 | proof_generator_addr: &str, 159 | batcher_payment_service: &str, 160 | wallet: Wallet, 161 | save_proof: bool, 162 | ) -> Result { 163 | let (proof, pub_input) = get_mina_proof_of_account(public_key, state_hash, rpc_url).await?; 164 | 165 | let verification_data = submit( 166 | MinaProof::Account((proof, pub_input.clone())), 167 | network, 168 | proof_generator_addr, 169 | batcher_addr, 170 | eth_rpc_url, 171 | wallet.clone(), 172 | save_proof, 173 | ) 174 | .await?; 175 | 176 | eth::validate_account( 177 | verification_data.clone(), 178 | &pub_input, 179 | eth_rpc_url, 180 | account_validation_addr, 181 | batcher_payment_service, 182 | ) 183 | .await?; 184 | 185 | let AlignedVerificationData { 186 | verification_data_commitment, 187 | batch_merkle_root, 188 | batch_inclusion_proof, 189 | index_in_batch, 190 | } = verification_data; 191 | let merkle_proof = batch_inclusion_proof 192 | .merkle_path 193 | .clone() 194 | .into_iter() 195 | .flatten() 196 | .collect(); 197 | 198 | let VerificationDataCommitment { 199 | proof_commitment, 200 | proving_system_aux_data_commitment, 201 | proof_generator_addr, 202 | .. 203 | } = verification_data_commitment; 204 | 205 | Ok(AccountVerificationData { 206 | proof_commitment, 207 | proving_system_aux_data_commitment, 208 | proof_generator_addr, 209 | batch_merkle_root, 210 | merkle_proof, 211 | verification_data_batch_index: index_in_batch, 212 | pub_input: bincode::serialize(&pub_input) 213 | .map_err(|err| format!("Failed to encode public inputs: {err}"))?, 214 | }) 215 | } 216 | -------------------------------------------------------------------------------- /core/src/sol/account.rs: -------------------------------------------------------------------------------- 1 | use std::iter::zip; 2 | 3 | use alloy::{ 4 | primitives::{Bytes, FixedBytes}, 5 | sol_types::sol, 6 | }; 7 | use mina_p2p_messages::{ 8 | bigint::BigInt, 9 | v2::{ 10 | MinaBaseAccountBinableArgStableV2 as MinaAccount, 11 | MinaBaseAccountTimingStableV2 as MinaTiming, MinaBasePermissionsAuthRequiredStableV2, 12 | MinaNumbersGlobalSlotSinceGenesisMStableV1, MinaNumbersGlobalSlotSpanStableV1, 13 | PicklesBaseProofsVerifiedStableV1, 14 | }, 15 | }; 16 | use num_traits::ToPrimitive; 17 | use MinaAccountValidationExample::*; 18 | 19 | sol!( 20 | MinaAccountValidationExample, 21 | "abi/MinaAccountValidationExample.json" 22 | ); 23 | 24 | #[allow(non_snake_case)] 25 | impl TryFrom<&MinaAccount> for Account { 26 | type Error = String; 27 | 28 | fn try_from(value: &MinaAccount) -> Result { 29 | let MinaAccount { 30 | public_key, 31 | token_id, 32 | token_symbol, 33 | balance, 34 | nonce, 35 | receipt_chain_hash, 36 | delegate, 37 | voting_for, 38 | timing, 39 | permissions, 40 | zkapp, 41 | } = value; 42 | 43 | let publicKey = CompressedECPoint { 44 | x: FixedBytes::try_from(public_key.x.as_ref()) 45 | .map_err(|err| format!("Could not convert public key x to FixedBytes: {err}"))?, 46 | isOdd: public_key.is_odd, 47 | }; 48 | let tokenIdKeyHash = FixedBytes::try_from(token_id.0.as_ref()) 49 | .map_err(|err| format!("Could not convert token id to FixedBytes: {err}"))?; 50 | 51 | let tokenSymbol: String = token_symbol 52 | .try_into() 53 | .map_err(|err| format!("Failed to convert token symbol to string: {err}"))?; 54 | let balance = balance.to_u64().ok_or("Failed to convert balance to u64")?; 55 | let nonce = nonce.to_u32().ok_or("Failed to convert nonce to u64")?; 56 | let receiptChainHash = FixedBytes::try_from(receipt_chain_hash.0.as_ref()) 57 | .map_err(|err| format!("Could not convert token id to FixedBytes: {err}"))?; 58 | let delegate = if let Some(delegate) = delegate { 59 | CompressedECPoint { 60 | x: FixedBytes::try_from(delegate.x.as_ref()) 61 | .map_err(|err| format!("Could not delegate x to FixedBytes: {err}"))?, 62 | isOdd: delegate.is_odd, 63 | } 64 | } else { 65 | CompressedECPoint { 66 | x: FixedBytes::ZERO, 67 | isOdd: true, 68 | } 69 | }; 70 | let votingFor = FixedBytes::try_from(voting_for.0.as_ref()) 71 | .map_err(|err| format!("Could not convert voting_for to FixedBytes: {err}"))?; 72 | 73 | let timing = match timing { 74 | MinaTiming::Timed { 75 | initial_minimum_balance, 76 | cliff_time, 77 | cliff_amount, 78 | vesting_period, 79 | vesting_increment, 80 | } => Timing { 81 | initialMinimumBalance: initial_minimum_balance 82 | .to_u64() 83 | .ok_or("Failed to convert initial_minimum_balance to u64".to_string())?, 84 | cliffTime: match cliff_time { 85 | MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(cliffTime) => { 86 | cliffTime 87 | .to_u32() 88 | .ok_or("Failed to convert clif_time to u32")? 89 | } 90 | }, 91 | cliffAmount: cliff_amount 92 | .to_u64() 93 | .ok_or("Failed to convert cliff_amount to u64")?, 94 | vestingPeriod: match vesting_period { 95 | MinaNumbersGlobalSlotSpanStableV1::GlobalSlotSpan(vesting_period) => { 96 | vesting_period 97 | .to_u32() 98 | .ok_or("Failed to convert vesting_period to u32")? 99 | } 100 | }, 101 | vestingIncrement: vesting_increment 102 | .to_u64() 103 | .ok_or("Failed to convert vesting_increment to u64")?, 104 | }, 105 | _ => Timing { 106 | initialMinimumBalance: 0, 107 | cliffTime: 0, 108 | cliffAmount: 0, 109 | vestingPeriod: 0, 110 | vestingIncrement: 0, 111 | }, 112 | }; 113 | let mina_to_sol_permissions = 114 | |permissions: MinaBasePermissionsAuthRequiredStableV2| match permissions { 115 | MinaBasePermissionsAuthRequiredStableV2::None => 0, 116 | MinaBasePermissionsAuthRequiredStableV2::Either => 1, 117 | MinaBasePermissionsAuthRequiredStableV2::Proof => 2, 118 | MinaBasePermissionsAuthRequiredStableV2::Signature => 3, 119 | MinaBasePermissionsAuthRequiredStableV2::Impossible => 4, 120 | }; 121 | 122 | let permissions = Permissions { 123 | editState: mina_to_sol_permissions(permissions.edit_state.clone()), 124 | access: mina_to_sol_permissions(permissions.access.clone()), 125 | send: mina_to_sol_permissions(permissions.send.clone()), 126 | rreceive: mina_to_sol_permissions(permissions.receive.clone()), 127 | setDelegate: mina_to_sol_permissions(permissions.set_delegate.clone()), 128 | setPermissions: mina_to_sol_permissions(permissions.set_permissions.clone()), 129 | setVerificationKeyAuth: mina_to_sol_permissions( 130 | permissions.set_verification_key.0.clone(), 131 | ), 132 | setVerificationKeyUint: permissions 133 | .set_verification_key 134 | .1 135 | .to_u32() 136 | .ok_or("Failed to convert verification key uint to u32")?, 137 | setZkappUri: mina_to_sol_permissions(permissions.set_zkapp_uri.clone()), 138 | editActionState: mina_to_sol_permissions(permissions.edit_action_state.clone()), 139 | setTokenSymbol: mina_to_sol_permissions(permissions.set_token_symbol.clone()), 140 | incrementNonce: mina_to_sol_permissions(permissions.increment_nonce.clone()), 141 | setVotingFor: mina_to_sol_permissions(permissions.set_voting_for.clone()), 142 | setTiming: mina_to_sol_permissions(permissions.set_timing.clone()), 143 | }; 144 | 145 | let zkapp = if let Some(zkapp) = zkapp { 146 | let mut appState: [FixedBytes<32>; 8] = [FixedBytes::ZERO; 8]; 147 | for (state, new_state) in zip(zkapp.app_state.0 .0.clone(), appState.iter_mut()) { 148 | *new_state = FixedBytes::try_from(state.as_ref()) 149 | .map_err(|err| format!("Could not convert app state to FixedBytes: {err}"))?; 150 | } 151 | let verificationKey = if let Some(verification_key) = zkapp.verification_key.clone() { 152 | let mina_to_sol_proofs_verified = 153 | |proofs_verified: PicklesBaseProofsVerifiedStableV1| match proofs_verified { 154 | PicklesBaseProofsVerifiedStableV1::N0 => 0, 155 | PicklesBaseProofsVerifiedStableV1::N1 => 1, 156 | PicklesBaseProofsVerifiedStableV1::N2 => 2, 157 | }; 158 | let mina_to_sol_commitment = 159 | |comm: (BigInt, BigInt)| -> Result { 160 | Ok(Commitment { 161 | x: FixedBytes::try_from(comm.0.as_ref()).map_err(|err| { 162 | format!("Could not convert commitment x to FixedBytes: {err}") 163 | })?, 164 | y: FixedBytes::try_from(comm.1.as_ref()).map_err(|err| { 165 | format!("Could not convert commitment y to FixedBytes: {err}") 166 | })?, 167 | }) 168 | }; 169 | 170 | let wrap_index = verification_key.wrap_index; 171 | 172 | let mut sigmaComm: [Commitment; 7] = std::array::from_fn(|_| Commitment { 173 | x: FixedBytes::ZERO, 174 | y: FixedBytes::ZERO, 175 | }); 176 | for (comm, new_comm) in zip(wrap_index.sigma_comm.iter(), sigmaComm.iter_mut()) { 177 | *new_comm = mina_to_sol_commitment(comm.clone())?; 178 | } 179 | 180 | let mut coefficientsComm: [Commitment; 15] = std::array::from_fn(|_| Commitment { 181 | x: FixedBytes::ZERO, 182 | y: FixedBytes::ZERO, 183 | }); 184 | for (comm, new_comm) in zip( 185 | wrap_index.coefficients_comm.iter(), 186 | coefficientsComm.iter_mut(), 187 | ) { 188 | *new_comm = mina_to_sol_commitment(comm.clone())?; 189 | } 190 | 191 | let wrapIndex = WrapIndex { 192 | sigmaComm, 193 | coefficientsComm, 194 | genericComm: mina_to_sol_commitment(wrap_index.generic_comm)?, 195 | psmComm: mina_to_sol_commitment(wrap_index.psm_comm)?, 196 | completeAddComm: mina_to_sol_commitment(wrap_index.complete_add_comm)?, 197 | mulComm: mina_to_sol_commitment(wrap_index.mul_comm)?, 198 | emulComm: mina_to_sol_commitment(wrap_index.emul_comm)?, 199 | endomulScalarComm: mina_to_sol_commitment(wrap_index.endomul_scalar_comm)?, 200 | }; 201 | VerificationKey { 202 | maxProofsVerified: mina_to_sol_proofs_verified( 203 | verification_key.max_proofs_verified, 204 | ), 205 | actualWrapDomainSize: mina_to_sol_proofs_verified( 206 | verification_key.actual_wrap_domain_size, 207 | ), 208 | wrapIndex, 209 | } 210 | } else { 211 | // Empty VerificationKey 212 | 213 | let commitment_zero = Commitment { 214 | x: FixedBytes::ZERO, 215 | y: FixedBytes::ZERO, 216 | }; 217 | let sigmaComm: [Commitment; 7] = std::array::from_fn(|_| commitment_zero.clone()); 218 | 219 | let coefficientsComm: [Commitment; 15] = 220 | std::array::from_fn(|_| commitment_zero.clone()); 221 | 222 | let wrapIndex = WrapIndex { 223 | sigmaComm, 224 | coefficientsComm, 225 | genericComm: commitment_zero.clone(), 226 | psmComm: commitment_zero.clone(), 227 | completeAddComm: commitment_zero.clone(), 228 | mulComm: commitment_zero.clone(), 229 | emulComm: commitment_zero.clone(), 230 | endomulScalarComm: commitment_zero.clone(), 231 | }; 232 | VerificationKey { 233 | maxProofsVerified: 0, 234 | actualWrapDomainSize: 0, 235 | wrapIndex, 236 | } 237 | }; 238 | let mut actionState: [FixedBytes<32>; 5] = [FixedBytes::ZERO; 5]; 239 | for (state, new_state) in zip(zkapp.action_state.iter(), actionState.iter_mut()) { 240 | *new_state = FixedBytes::try_from(state.as_ref()).map_err(|err| { 241 | format!("Could not convert action state to FixedBytes: {err}") 242 | })?; 243 | } 244 | ZkappAccount { 245 | appState, 246 | verificationKey, 247 | zkappVersion: zkapp 248 | .zkapp_version 249 | .to_u32() 250 | .ok_or("Failed to convert zkapp version to u32".to_string())?, 251 | actionState, 252 | lastActionSlot: match zkapp.last_action_slot { 253 | MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(last_action_slot) => { 254 | last_action_slot 255 | .to_u32() 256 | .ok_or("Failed to convert zkapp version to u32".to_string())? 257 | } 258 | }, 259 | provedState: zkapp.proved_state, 260 | zkappUri: zkapp.zkapp_uri.0.clone().into(), 261 | } 262 | } else { 263 | // Empty ZkappAccount 264 | 265 | let commitment_zero = Commitment { 266 | x: FixedBytes::ZERO, 267 | y: FixedBytes::ZERO, 268 | }; 269 | let sigmaComm: [Commitment; 7] = std::array::from_fn(|_| commitment_zero.clone()); 270 | 271 | let coefficientsComm: [Commitment; 15] = 272 | std::array::from_fn(|_| commitment_zero.clone()); 273 | 274 | let wrapIndex = WrapIndex { 275 | sigmaComm, 276 | coefficientsComm, 277 | genericComm: commitment_zero.clone(), 278 | psmComm: commitment_zero.clone(), 279 | completeAddComm: commitment_zero.clone(), 280 | mulComm: commitment_zero.clone(), 281 | emulComm: commitment_zero.clone(), 282 | endomulScalarComm: commitment_zero.clone(), 283 | }; 284 | 285 | ZkappAccount { 286 | appState: [FixedBytes::ZERO; 8], 287 | verificationKey: VerificationKey { 288 | maxProofsVerified: 0, 289 | actualWrapDomainSize: 0, 290 | wrapIndex, 291 | }, 292 | zkappVersion: 0, 293 | actionState: [FixedBytes::ZERO; 5], 294 | lastActionSlot: 0, 295 | provedState: false, 296 | zkappUri: Bytes::new(), 297 | } 298 | }; 299 | 300 | Ok(Account { 301 | publicKey, 302 | tokenIdKeyHash, 303 | tokenSymbol, 304 | balance, 305 | nonce, 306 | receiptChainHash, 307 | delegate, 308 | votingFor, 309 | timing, 310 | permissions, 311 | zkapp, 312 | }) 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /core/src/sol/mod.rs: -------------------------------------------------------------------------------- 1 | /// Solidity-friendly serialization 2 | pub mod serialization; 3 | 4 | /// Solidity-friendly account state definition 5 | pub mod account; 6 | -------------------------------------------------------------------------------- /core/src/sol/serialization.rs: -------------------------------------------------------------------------------- 1 | use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; 2 | use mina_curves::pasta::Fp; 3 | use mina_p2p_messages::{ 4 | bigint, 5 | v2::{DataHashLibStateHashStableV1, LedgerHash, MinaBaseLedgerHash0StableV1, StateHash}, 6 | }; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// Serialization to bytes for simple types that need to be deserialized in Ethereum. 10 | /// More complex structures, like an [`MinaAccountValidationExample::Account`], may use Solidity's ABI Encoding. 11 | pub struct SolSerialize; 12 | 13 | impl serde_with::SerializeAs for SolSerialize { 14 | fn serialize_as(val: &StateHash, serializer: S) -> Result 15 | where 16 | S: serde::Serializer, 17 | { 18 | let bytes: [u8; 32] = val 19 | .0 20 | .as_ref() 21 | .try_into() 22 | .map_err(serde::ser::Error::custom)?; 23 | bytes.serialize(serializer) 24 | } 25 | } 26 | 27 | impl<'de> serde_with::DeserializeAs<'de, StateHash> for SolSerialize { 28 | fn deserialize_as(deserializer: D) -> Result 29 | where 30 | D: serde::Deserializer<'de>, 31 | { 32 | let bytes = <[u8; 32]>::deserialize(deserializer)?; 33 | let bigint = bigint::BigInt::new(bytes.into()); 34 | Ok(StateHash::from(DataHashLibStateHashStableV1(bigint))) 35 | } 36 | } 37 | 38 | impl serde_with::SerializeAs for SolSerialize { 39 | fn serialize_as(val: &LedgerHash, serializer: S) -> Result 40 | where 41 | S: serde::Serializer, 42 | { 43 | let bytes: [u8; 32] = val 44 | .0 45 | .as_ref() 46 | .try_into() 47 | .map_err(serde::ser::Error::custom)?; 48 | bytes.serialize(serializer) 49 | } 50 | } 51 | 52 | impl<'de> serde_with::DeserializeAs<'de, LedgerHash> for SolSerialize { 53 | fn deserialize_as(deserializer: D) -> Result 54 | where 55 | D: serde::Deserializer<'de>, 56 | { 57 | let bytes = <[u8; 32]>::deserialize(deserializer)?; 58 | let bigint = bigint::BigInt::new(bytes.into()); 59 | Ok(LedgerHash::from(MinaBaseLedgerHash0StableV1(bigint))) 60 | } 61 | } 62 | 63 | impl serde_with::SerializeAs for SolSerialize { 64 | fn serialize_as(val: &Fp, serializer: S) -> Result 65 | where 66 | S: serde::Serializer, 67 | { 68 | let mut bytes = Vec::with_capacity(32); 69 | val.serialize(&mut bytes) 70 | .map_err(serde::ser::Error::custom)?; 71 | let bytes: [u8; 32] = bytes.try_into().map_err(|_| { 72 | serde::ser::Error::custom("failed to convert byte vector into 32 byte array") 73 | })?; 74 | bytes.serialize(serializer) 75 | } 76 | } 77 | 78 | impl<'de> serde_with::DeserializeAs<'de, Fp> for SolSerialize { 79 | fn deserialize_as(deserializer: D) -> Result 80 | where 81 | D: serde::Deserializer<'de>, 82 | { 83 | let bytes = <[u8; 32]>::deserialize(deserializer)?; 84 | Fp::deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/utils/constants.rs: -------------------------------------------------------------------------------- 1 | // Anvil related constants 2 | 3 | /// Private key used to sign transactions to Aligned Devnet 4 | pub const ANVIL_PRIVATE_KEY: &str = 5 | "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"; // Anvil wallet 9 6 | /// Chain ID of Aligned Devnet network 7 | pub const ANVIL_CHAIN_ID: u64 = 31337; 8 | /// URL of the Aligned Devnet Batcher 9 | pub const ANVIL_BATCHER_ADDR: &str = "ws://localhost:8080"; 10 | /// Address of the Aligned Batcher Payment Service on Devnet 11 | pub const ANVIL_BATCHER_ETH_ADDR: &str = "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650"; 12 | /// URL of Aligned Devnet RPC 13 | pub const ANVIL_ETH_RPC_URL: &str = "http://localhost:8545"; 14 | 15 | // Holesky related constants 16 | 17 | /// Chain ID of Aligned Testnet network 18 | pub const HOLESKY_CHAIN_ID: u64 = 17000; 19 | 20 | // Mina related constants 21 | // TODO(gabrielbosio): These are temporary, we will fetch the tip from the Mina contract instead of using these hardcoded values. 22 | pub const MINA_TIP_PROTOCOL_STATE: &str = "Va9U7YpJjxXGg9IcS2npo+3axwra34v/JNsZW+XS4SUC8DXQX42qQSBaswvRI1uKu+UuVUvMQxEO4trzXicENbvJbooTtatm3+9bq4Z/RGzArLJ5rhTc30sJHoNjGyMZIMJX9MI+K4l1eiTChYphL4+odqeBQ7kGXhI+fVAMVM6ZIFfL2sMs61cDhApcSSi8zR029wdYaVHpph9XZ0ZqwG6Hrl43zlIWHVtuilYPo0fQlp1ItzcbT6c7N6jHva3X/Q8lE7fiEW5jIVHePd3obQSIgeHm857pq8T4H9/pXQdyGznxIVaWPq4kH76XZEfaJWK6gAb32jjhbuQvrPQmGj8SHZ9V7Apwdx2Ux2EcmXDEk+IEayOtrLW8v5kzsjs1Eww1udUeXXx0FFb4ZyBzEkGoKAJzz8bCFmj9e8bFh9DMHQIdVMT8mfe3oP365vIUYuYqfX43NCHQR0u8b5rjy3UtAh1UxPyZ97eg/frm8hRi5ip9fjc0IdBHS7xvmuPLdS1sxnDlJh772cxIxYjNovS7KSfQWcCv0HDJjtaULmZBBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEwxNzpy3bMctvXJVb3iJc9xE2oE6SfRaXfK+97SZRDFYj3CzchWlcNJzqE8lngCUq4iXwcy7yIACrD6ZpJJBAqhsuA+bafTm3SZTS4sgevRUFahNf00prjrKs69LvnPB4CHVTE/Jn3t6D9+ubyFGLmKn1+NzQh0EdLvG+a48t1LWRf927TkBEYaGk9IZ3fcFZUXAnvOqgCyisv7IjDsS4VbMZw5SYe+9nMSMWIzaL0uykn0FnAr9BwyY7WlC5mQQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABHZ9V7Apwdx2Ux2EcmXDEk+IEayOtrLW8v5kzsjs1EwyI9ws3IVpXDSc6hPJZ4AlKuIl8HMu8iAAqw+maSSQQKvwAQLBGTwEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8wNgM5pABAAAgmowzZ75TWxff/nZTAemMaXQ4TBgrLlbuUCku9Aw53f394rEFAAMdCwEEAgMFAwQCAwMCIIelFLE7OpzaBMXCUq8pbJUGIusX3mx4noqZ4b/nEwAA/EG9qZbMT1EQAP5WXf7kGwD9VvoIACY9EcI8wwDk7SIR+P+we1ypqkYmkTQ/cru0cObh+QYr/EFBaiJ0gUMQIcTxtxPFJjpgmYFu9oQvo5mmPkfb8QrtpydnIjzdTyG80bmgeL7ljSGQdRDl6Cav6klIt2AC5Lmt1XzP5RmMAFe+grwJMx9Sy9Dh8YVM0lBzjqCEx5zq9r2kAhblYqU//r4PpYnWw5CTfPDHtsqXSoG0RF6ITuM1IIgJV7upWr8zXD38QblgSQzCTRBqRRmB0Da87xFFhlWVYAaqYE3wOWKs0l3pfqDnnUhmG4WMED/odD5FUo90d6VJf7m5ng+OysRzSJtog5ykdhgmVa9U7YpJjxXGg9IcS2npo+3axwra34v/JNsZW+XS4SX+RwUB0WiDnvvPm0OMlpbaiVi9y/86iTLi/0CEPuAjcFqsfjIB6eZmmJLgQh0VsTpNQxJwO6M+ANjEeItPGVJFHnyvUCABjRA0XVmv6t9a3AKtey/RHEtkbzQ9R8h7M3YUjDzpLDoBAf4iAf7kGwf+cAgA/AAEsuWPAQAA"; 23 | pub const MINA_TIP_STATE_HASH_FIELD: &str = 24 | "26201757517054449641912404249424749469164718222967816857204695395894215860942"; 25 | pub const MINA_HASH_SIZE: usize = 32; 26 | 27 | // Bridge related constants 28 | 29 | pub const BRIDGE_DEVNET_ETH_ADDR: &str = "0x700b6A60ce7EaaEA56F065753d8dcB9653dbAD35"; 30 | /// Length of the Mina transition frontier that will or is bridged to Ethereum 31 | pub const BRIDGE_TRANSITION_FRONTIER_LEN: usize = 16; 32 | pub const BRIDGE_ACCOUNT_DEVNET_ETH_ADDR: &str = "0xA15BB66138824a1c7167f5E85b957d04Dd34E468"; 33 | 34 | // Aligned related constants 35 | 36 | /// Address of the Aligned Proof Generator 37 | pub const PROOF_GENERATOR_ADDR: &str = "0x66f9664f97F2b50F62D13eA064982f936dE76657"; 38 | /// Address of the Aligned Service Manager on Devnet 39 | pub const ALIGNED_SM_DEVNET_ETH_ADDR: &str = "0x851356ae760d987E095750cCeb3bC6014560891C"; 40 | -------------------------------------------------------------------------------- /core/src/utils/env.rs: -------------------------------------------------------------------------------- 1 | use aligned_sdk::core::types::Network; 2 | extern crate dotenv; 3 | use dotenv::dotenv; 4 | use log::debug; 5 | 6 | use super::constants::{ 7 | ANVIL_BATCHER_ADDR, ANVIL_BATCHER_ETH_ADDR, ANVIL_ETH_RPC_URL, PROOF_GENERATOR_ADDR, 8 | }; 9 | 10 | /// Struct that is created by reading environment variables or, for some fields, from defined constants if the 11 | /// corresponding environment variable is not defined. 12 | /// 13 | /// - `rpc_url`: Mina node RPC URL to get the Mina state 14 | /// - `network`: Enum variant to specify the Ethereum network to update the Mina state 15 | /// - `state_settlement_addr`: Address of the Mina State Settlement Example Contract 16 | /// - `account_validation_addr`: Address of the Mina Account Validation Example Contract 17 | /// - `batcher_addr`: Address of the Aligned Batcher Service 18 | /// - `batcher_eth_addr`: Address of the Aligned Batcher Payment Service 19 | /// - `eth_rpc_url`: Ethereum node RPC URL to send the transaction to update the Mina state 20 | /// - `proof_generator_addr`: Address of the Aligned Proof Generator 21 | /// - `keystore_path`: Path to the keystore used to sign Ethereum transactions. 22 | /// `None` if `private_key` is defined. 23 | /// - `private_key`: Private key of the Ethereum wallet used to sign Ethereum transactions. 24 | /// `None` if `keystore_path` is defined. 25 | pub struct EnvironmentVariables { 26 | pub rpc_url: String, 27 | pub network: Network, 28 | pub state_settlement_addr: Option, 29 | pub account_validation_addr: Option, 30 | pub batcher_addr: String, 31 | pub batcher_eth_addr: String, 32 | pub eth_rpc_url: String, 33 | pub proof_generator_addr: String, 34 | pub keystore_path: Option, 35 | pub private_key: Option, 36 | } 37 | 38 | fn load_var_or(key: &str, default: &str, network: &Network) -> Result { 39 | // Default value is only valid for Anvil devnet setup. 40 | match std::env::var(key) { 41 | Ok(value) => Ok(value), 42 | Err(_) if matches!(network, Network::Devnet) => { 43 | debug!("Using default {} for devnet: {}", key, default); 44 | Ok(default.to_string()) 45 | } 46 | Err(err) => Err(format!( 47 | "Chain selected is not Devnet but couldn't read {}: {}", 48 | key, err 49 | )), 50 | } 51 | } 52 | 53 | impl EnvironmentVariables { 54 | /// Creates the `EnvironmentVariables` struct from environment variables or, for some fields, from defined 55 | /// constants if the corresponding environment variable is not defined. 56 | /// 57 | /// Returns `Err` if: 58 | /// 59 | /// - `MINA_RPC_URL` or `ETH_CHAIN` environemnt variables are not defined 60 | /// - `ETH_CHAIN` is not set to a valid Ethereum network (`"devnet"` or `"holesky"`) 61 | /// - Both `KEYSTORE_PATH` and `PRIVATE_KEY` are set 62 | pub fn new() -> Result { 63 | dotenv().map_err(|err| format!("Couldn't load .env file: {}", err))?; 64 | 65 | let rpc_url = std::env::var("MINA_RPC_URL") 66 | .map_err(|err| format!("Couldn't get MINA_RPC_URL env. variable: {err}"))?; 67 | let network = match std::env::var("ETH_CHAIN") 68 | .map_err(|err| format!("Couldn't get ETH_CHAIN env. variable: {err}"))? 69 | .as_str() 70 | { 71 | "devnet" => { 72 | debug!("Selected Anvil devnet chain."); 73 | Network::Devnet 74 | } 75 | "holesky" => { 76 | debug!("Selected Holesky chain."); 77 | Network::Holesky 78 | } 79 | _ => return Err( 80 | "Unrecognized chain, possible values for ETH_CHAIN are \"devnet\" and \"holesky\"." 81 | .to_owned(), 82 | ), 83 | }; 84 | 85 | let state_settlement_addr = std::env::var("STATE_SETTLEMENT_ETH_ADDR").ok(); 86 | let account_validation_addr = std::env::var("ACCOUNT_VALIDATION_ETH_ADDR").ok(); 87 | 88 | let batcher_addr = load_var_or("BATCHER_ADDR", ANVIL_BATCHER_ADDR, &network)?; 89 | let batcher_eth_addr = load_var_or("BATCHER_ETH_ADDR", ANVIL_BATCHER_ETH_ADDR, &network)?; 90 | let eth_rpc_url = load_var_or("ETH_RPC_URL", ANVIL_ETH_RPC_URL, &network)?; 91 | let proof_generator_addr = 92 | load_var_or("PROOF_GENERATOR_ADDR", PROOF_GENERATOR_ADDR, &network)?; 93 | 94 | let keystore_path = std::env::var("KEYSTORE_PATH").ok(); 95 | let private_key = std::env::var("PRIVATE_KEY").ok(); 96 | 97 | if keystore_path.is_some() && private_key.is_some() { 98 | return Err( 99 | "Both keystore and private key env. variables are defined. Choose only one." 100 | .to_string(), 101 | ); 102 | } 103 | 104 | Ok(EnvironmentVariables { 105 | rpc_url, 106 | network, 107 | state_settlement_addr, 108 | account_validation_addr, 109 | batcher_addr, 110 | batcher_eth_addr, 111 | eth_rpc_url, 112 | proof_generator_addr, 113 | keystore_path, 114 | private_key, 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /core/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod env; 3 | pub mod wallet; 4 | pub mod wallet_alloy; 5 | -------------------------------------------------------------------------------- /core/src/utils/wallet.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use aligned_sdk::core::types::Network; 4 | use ethers::{ 5 | prelude::k256::ecdsa::SigningKey, 6 | signers::{LocalWallet, Signer, Wallet}, 7 | }; 8 | 9 | use log::info; 10 | use zeroize::Zeroizing; 11 | 12 | use crate::utils::constants::{ANVIL_CHAIN_ID, ANVIL_PRIVATE_KEY, HOLESKY_CHAIN_ID}; 13 | 14 | /// Returns the `Wallet` struct defined in the `ethers` crate. 15 | /// This wallet is used to sign Ethereum transactions (e.g.: Aligned batches or example contract function calls). 16 | /// 17 | /// If `keystore_path` is defined it stops execution, prompts on the TTY and then reads the password from TTY. 18 | /// 19 | /// Returns `Err` if: 20 | /// - `keystore_path` is not a valid path to a keystore 21 | /// - `keystore_path` is defined and the password read from the TTY is not valid 22 | /// - `private_key` is not a valid Ethereum private key 23 | /// - Both `keystore_path` and `private_key` are defined 24 | pub fn get_wallet( 25 | network: &Network, 26 | keystore_path: Option<&str>, 27 | private_key: Option<&str>, 28 | ) -> Result, String> { 29 | if keystore_path.is_some() && private_key.is_some() { 30 | return Err( 31 | "Both keystore and private key env. variables are defined. Choose only one." 32 | .to_string(), 33 | ); 34 | } 35 | 36 | if matches!(network, Network::Holesky) { 37 | if let Some(keystore_path) = keystore_path { 38 | info!("Using keystore for Holesky wallet"); 39 | let password = Zeroizing::new( 40 | rpassword::prompt_password("Please enter your keystore password:") 41 | .map_err(|err| err.to_string())?, 42 | ); 43 | Wallet::decrypt_keystore(keystore_path, password).map_err(|err| err.to_string()) 44 | } else if let Some(private_key) = private_key { 45 | info!("Using private key for Holesky wallet"); 46 | let wallet = private_key 47 | .parse::() 48 | .map_err(|err| err.to_string())?; 49 | 50 | Ok(wallet.with_chain_id(HOLESKY_CHAIN_ID)) 51 | } else { 52 | return Err( 53 | "Holesky chain was selected but couldn't find KEYSTORE_PATH or PRIVATE_KEY." 54 | .to_string(), 55 | ); 56 | } 57 | } else { 58 | info!("Using Anvil wallet 9"); 59 | let wallet = LocalWallet::from_str(ANVIL_PRIVATE_KEY) 60 | .map_err(|err| format!("Failed to create Anvil wallet: {}", err))?; 61 | 62 | Ok(wallet.with_chain_id(ANVIL_CHAIN_ID)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/utils/wallet_alloy.rs: -------------------------------------------------------------------------------- 1 | use aligned_sdk::core::types::Network; 2 | use alloy::{ 3 | network::EthereumWallet, 4 | signers::local::{LocalSigner, PrivateKeySigner}, 5 | }; 6 | use log::info; 7 | use zeroize::Zeroizing; 8 | 9 | use crate::utils::constants::ANVIL_PRIVATE_KEY; 10 | 11 | /// Returns the `Wallet` struct defined in the `alloy` crate. 12 | /// This wallet is used to sign Ethereum example contract deployments. 13 | /// 14 | /// If `keystore_path` is defined it stops execution, prompts on the TTY and then reads the password from TTY. 15 | /// 16 | /// Returns `Err` if: 17 | /// - `keystore_path` is not a valid path to a keystore 18 | /// - `keystore_path` is defined and the password read from the TTY is not valid 19 | /// - `private_key` is not a valid Ethereum private key 20 | /// - Both `keystore_path` and `private_key` are defined 21 | pub fn get_wallet( 22 | network: &Network, 23 | keystore_path: Option<&str>, 24 | private_key: Option<&str>, 25 | ) -> Result { 26 | if keystore_path.is_some() && private_key.is_some() { 27 | return Err( 28 | "Both keystore and private key env. variables are defined. Choose only one." 29 | .to_string(), 30 | ); 31 | } 32 | 33 | if matches!(network, Network::Holesky) { 34 | if let Some(keystore_path) = keystore_path { 35 | info!("Using keystore for Holesky wallet"); 36 | let password = Zeroizing::new( 37 | rpassword::prompt_password("Please enter your keystore password:") 38 | .map_err(|err| err.to_string())?, 39 | ); 40 | let signer = LocalSigner::decrypt_keystore(keystore_path, password) 41 | .map_err(|err| err.to_string())?; 42 | Ok(EthereumWallet::new(signer)) 43 | } else if let Some(private_key) = private_key { 44 | info!("Using private key for Holesky wallet"); 45 | let signer: PrivateKeySigner = private_key 46 | .parse() 47 | .map_err(|_| "Failed to get Anvil signer".to_string())?; 48 | Ok(EthereumWallet::new(signer)) 49 | } else { 50 | return Err( 51 | "Holesky chain was selected but couldn't find KEYSTORE_PATH or PRIVATE_KEY." 52 | .to_string(), 53 | ); 54 | } 55 | } else { 56 | info!("Using Anvil wallet 9"); 57 | let signer: PrivateKeySigner = ANVIL_PRIVATE_KEY 58 | .parse() 59 | .map_err(|_| "Failed to get Anvil signer".to_string())?; 60 | Ok(EthereumWallet::new(signer)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/app/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /example/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.17", features = ["derive"] } 8 | env_logger = "0.11.5" 9 | log = "0.4.22" 10 | mina_bridge_core = { path = "../../core/" } 11 | tokio = "1.40.0" 12 | alloy = { version = "0.3.1", features = ["full", "signer-keystore"] } 13 | alloy-sol-types = "0.8.2" 14 | alloy-contract = "0.3.1" 15 | reqwest = "^0.11" 16 | aligned-sdk = { git = "https://github.com/lambdaclass/aligned_layer.git", rev = "220546afa12c035a508529224f5148cd6af4ca78" } 17 | 18 | [patch.crates-io] 19 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 20 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 21 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 22 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 23 | 24 | [patch.'https://github.com/openmina/algebra'] 25 | ark-ff = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 26 | ark-ec = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 27 | ark-poly = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 28 | ark-serialize = { git = "https://github.com/lambdaclass/openmina_algebra", rev = "017531e7aaa15a2c856532b0843876e371b01122" } 29 | -------------------------------------------------------------------------------- /example/app/src/main.rs: -------------------------------------------------------------------------------- 1 | use aligned_sdk::core::types::Network; 2 | use alloy::{ 3 | primitives::{Address, U256}, 4 | providers::ProviderBuilder, 5 | sol_types::sol, 6 | }; 7 | use clap::{Parser, Subcommand}; 8 | use log::{debug, error, info}; 9 | use mina_bridge_core::{ 10 | sdk::{ 11 | get_bridged_chain_tip_state_hash, update_bridge_chain, validate_account, 12 | AccountVerificationData, 13 | }, 14 | utils::{env::EnvironmentVariables, wallet, wallet_alloy}, 15 | }; 16 | use std::{process, str::FromStr, time::SystemTime}; 17 | 18 | const MINA_ZKAPP_ADDRESS: &str = "B62qmKCv2HaPwVRHBKrDFGUpjSh3PPY9VqSa6ZweGAmj9hBQL4pfewn"; 19 | const SUDOKU_VALIDITY_DEVNET_ADDRESS: &str = "0x8ce361602B935680E8DeC218b820ff5056BeB7af"; 20 | 21 | sol!( 22 | #[allow(clippy::too_many_arguments)] 23 | #[sol(rpc)] 24 | SudokuValidity, 25 | "abi/SudokuValidity.json" 26 | ); 27 | 28 | #[derive(Parser)] 29 | #[command(version, about)] 30 | struct Cli { 31 | #[command(subcommand)] 32 | command: Command, 33 | } 34 | 35 | #[derive(Subcommand)] 36 | enum Command { 37 | DeployContract, 38 | ValidateSolution, 39 | } 40 | 41 | #[tokio::main] 42 | async fn main() { 43 | let cli = Cli::parse(); 44 | let now = SystemTime::now(); 45 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 46 | 47 | let EnvironmentVariables { 48 | rpc_url, 49 | network, 50 | state_settlement_addr, 51 | account_validation_addr, 52 | batcher_addr, 53 | batcher_eth_addr, 54 | eth_rpc_url, 55 | proof_generator_addr, 56 | keystore_path, 57 | private_key, 58 | } = EnvironmentVariables::new().unwrap_or_else(|err| { 59 | error!("{}", err); 60 | process::exit(1); 61 | }); 62 | 63 | let state_settlement_addr = state_settlement_addr.unwrap_or_else(|| { 64 | error!("Error getting State settlement contract address"); 65 | process::exit(1); 66 | }); 67 | let account_validation_addr = account_validation_addr.unwrap_or_else(|| { 68 | error!("Error getting Account validation contract address"); 69 | process::exit(1); 70 | }); 71 | 72 | let sudoku_address = match network { 73 | Network::Devnet => SUDOKU_VALIDITY_DEVNET_ADDRESS.to_string(), 74 | Network::Holesky => std::env::var("SUDOKU_VALIDITY_HOLESKY_ADDRESS").unwrap_or_else(|_| { 75 | error!("Error getting Sudoku vality contract address"); 76 | process::exit(1); 77 | }), 78 | _ => todo!(), 79 | }; 80 | 81 | let wallet_alloy = 82 | wallet_alloy::get_wallet(&network, keystore_path.as_deref(), private_key.as_deref()) 83 | .unwrap_or_else(|err| { 84 | error!("{}", err); 85 | process::exit(1); 86 | }); 87 | 88 | let provider = ProviderBuilder::new() 89 | .with_recommended_fillers() 90 | .wallet(wallet_alloy) 91 | .on_http( 92 | reqwest::Url::parse(ð_rpc_url) 93 | .map_err(|err| err.to_string()) 94 | .unwrap(), 95 | ); 96 | 97 | match cli.command { 98 | Command::DeployContract => { 99 | // TODO(xqft): we might as well use the Chain type from Alloy, it isn't right to add 100 | // aligned-sdk as a dependency only for this type. 101 | 102 | let contract = SudokuValidity::deploy( 103 | &provider, 104 | Address::from_str(&state_settlement_addr).unwrap(), 105 | Address::from_str(&account_validation_addr).unwrap(), 106 | ) 107 | .await 108 | .map_err(|err| err.to_string()) 109 | .unwrap_or_else(|err| { 110 | error!("{}", err); 111 | process::exit(1); 112 | }); 113 | 114 | info!( 115 | "SudokuValidity contract successfuly deployed with address {}", 116 | contract.address() 117 | ); 118 | } 119 | Command::ValidateSolution => { 120 | // We could check if the specific block containing the tx is already verified, before 121 | // updating the bridge's chain. 122 | // let is_state_verified = is_state_verified(&state_hash, &state_settlement_addr, ð_rpc_url) 123 | // .await 124 | // .unwrap_or_else(|err| { 125 | // error!("{}", err); 126 | // process::exit(1); 127 | // }); 128 | 129 | // if !is_state_verified { 130 | // info!("State that includes the zkApp tx isn't verified. Bridging latest chain..."); 131 | 132 | let wallet = 133 | wallet::get_wallet(&network, keystore_path.as_deref(), private_key.as_deref()) 134 | .unwrap_or_else(|err| { 135 | error!("{}", err); 136 | process::exit(1); 137 | }); 138 | 139 | let state_verification_result = update_bridge_chain( 140 | &rpc_url, 141 | &network, 142 | &state_settlement_addr, 143 | &batcher_addr, 144 | ð_rpc_url, 145 | &proof_generator_addr, 146 | wallet.clone(), 147 | &batcher_eth_addr, 148 | true, 149 | false, 150 | ) 151 | .await; 152 | 153 | match state_verification_result { 154 | Err(err) if err == "Latest chain is already verified" => { 155 | info!("Bridge chain is up to date, won't verify new states.") 156 | } 157 | Err(err) => { 158 | error!("{}", err); 159 | process::exit(1); 160 | } 161 | _ => {} 162 | } 163 | // } 164 | 165 | let tip_state_hash = 166 | get_bridged_chain_tip_state_hash(&state_settlement_addr, ð_rpc_url) 167 | .await 168 | .unwrap_or_else(|err| { 169 | error!("{}", err); 170 | process::exit(1); 171 | }); 172 | 173 | info!("tip state hash: {}", &tip_state_hash); 174 | 175 | let AccountVerificationData { 176 | proof_commitment, 177 | proving_system_aux_data_commitment, 178 | proof_generator_addr, 179 | batch_merkle_root, 180 | merkle_proof, 181 | verification_data_batch_index, 182 | pub_input, 183 | } = validate_account( 184 | MINA_ZKAPP_ADDRESS, 185 | &tip_state_hash, 186 | &rpc_url, 187 | &network, 188 | &account_validation_addr, 189 | &batcher_addr, 190 | ð_rpc_url, 191 | &proof_generator_addr, 192 | &batcher_eth_addr, 193 | wallet, 194 | false, 195 | ) 196 | .await 197 | .unwrap_or_else(|err| { 198 | error!("{}", err); 199 | process::exit(1); 200 | }); 201 | 202 | debug!("Creating contract instance"); 203 | let contract = 204 | SudokuValidity::new(Address::from_str(&sudoku_address).unwrap(), provider); 205 | 206 | let call = contract.validateSolution( 207 | proof_commitment.into(), 208 | proving_system_aux_data_commitment.into(), 209 | proof_generator_addr.into(), 210 | batch_merkle_root.into(), 211 | merkle_proof.into(), 212 | U256::from(verification_data_batch_index), 213 | pub_input.into(), 214 | Address::from_str(&batcher_eth_addr).unwrap(), 215 | ); 216 | 217 | info!("Sending transaction to SudokuValidity contract..."); 218 | let tx = call.send().await; 219 | 220 | match tx { 221 | Ok(tx) => { 222 | let receipt = tx.get_receipt().await.unwrap_or_else(|err| { 223 | error!("{}", err); 224 | process::exit(1); 225 | }); 226 | let new_timestamp: U256 = contract 227 | .getLatestSolutionTimestamp() 228 | .call() 229 | .await 230 | .unwrap_or_else(|err| { 231 | error!("{}", err); 232 | process::exit(1); 233 | }) 234 | ._0; 235 | 236 | info!( 237 | "SudokuValidity contract was updated! transaction hash: {}, gas cost: {}, new timestamp: {}", 238 | receipt.transaction_hash, receipt.gas_used, new_timestamp 239 | ); 240 | } 241 | Err(err) => error!("SudokuValidity transaction failed!: {err}"), 242 | } 243 | } 244 | } 245 | 246 | if let Ok(elapsed) = now.elapsed() { 247 | info!("Time spent: {} s", elapsed.as_secs()); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /example/eth_contract/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly 27 | 28 | - name: Show Forge version 29 | run: | 30 | forge --version 31 | 32 | - name: Run Forge fmt 33 | run: | 34 | forge fmt --check 35 | id: fmt 36 | 37 | - name: Run Forge build 38 | run: | 39 | forge build --sizes 40 | id: build 41 | 42 | - name: Run Forge tests 43 | run: | 44 | forge test -vvv 45 | id: test 46 | -------------------------------------------------------------------------------- /example/eth_contract/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /example/eth_contract/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /example/eth_contract/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /example/eth_contract/lib/mina_bridge: -------------------------------------------------------------------------------- 1 | ../../.. -------------------------------------------------------------------------------- /example/eth_contract/remappings.txt: -------------------------------------------------------------------------------- 1 | aligned_layer/=lib/mina_bridge/contract/lib/aligned_layer/ 2 | ds-test/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/ds-test/src/ 3 | 4 | eigenlayer-contracts/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/ 5 | eigenlayer-middleware/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/src 6 | eigenlayer-core/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/ 7 | eigenlayer-core-contracts/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/core 8 | eigenlayer-scripts/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/script 9 | 10 | erc4626-tests/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/lib/erc4626-tests/ 11 | 12 | openzeppelin-contracts-upgradeable-v4.9.0/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-upgradeable-v4.9.0/ 13 | openzeppelin-contracts-upgradeable/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/ 14 | openzeppelin-contracts-v4.9.0/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/ 15 | openzeppelin-contracts/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 16 | 17 | openzeppelin-contracts-v4.9.0/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/lib/openzeppelin-contracts-v4.9.0/ 18 | openzeppelin-contracts/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 19 | 20 | @openzeppelin-upgrades/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/ 21 | @openzeppelin/=lib/mina_bridge/contract/lib/aligned_layer/contracts/lib/eigenlayer-middleware/lib/openzeppelin-contracts/ 22 | 23 | forge-std/=lib/forge-std/src/ 24 | -------------------------------------------------------------------------------- /example/eth_contract/src/SudokuValidity.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.12; 3 | 4 | import "mina_bridge/contract/src/MinaStateSettlementExample.sol"; 5 | import "mina_bridge/contract/src/MinaAccountValidationExample.sol"; 6 | 7 | contract SudokuValidity { 8 | error InvalidZkappAccount(); // f281a183 9 | error InvalidLedger(bytes32 ledgerHash); // 76f145ea 10 | error IncorrectZkappAccount(bytes32 verificationKeyHash); // 170e89eb 11 | error UnsolvedSudoku(); // a3790c0e 12 | 13 | /// @notice The Sudoku zkApp verification key hash. 14 | bytes32 constant ZKAPP_VERIFICATION_KEY_HASH = 15 | 0xdc9c283f73ce17466a01b90d36141b848805a3db129b6b80d581adca52c9b6f3; 16 | 17 | /// @notice Mina bridge contract that validates and stores Mina states. 18 | MinaStateSettlementExample stateSettlement; 19 | /// @notice Mina bridge contract that validates accounts 20 | MinaAccountValidationExample accountValidation; 21 | 22 | /// @notice Latest timestamp (Unix time) at which the contract determined that a 23 | // Sudoku was solved in the Mina ZkApp. 24 | uint256 latestSolutionValidationAt = 0; 25 | 26 | constructor(address _stateSettlementAddr, address _accountValidationAddr) { 27 | stateSettlement = MinaStateSettlementExample(_stateSettlementAddr); 28 | accountValidation = MinaAccountValidationExample(_accountValidationAddr); 29 | } 30 | 31 | function getLatestSolutionTimestamp() external view returns (uint256) { 32 | return latestSolutionValidationAt; 33 | } 34 | 35 | /// @notice Validates a Sudoku solution by bridging from Mina, and stores 36 | /// the last Unix time it was solved at. 37 | function validateSolution( 38 | bytes32 proofCommitment, 39 | bytes32 provingSystemAuxDataCommitment, 40 | bytes20 proofGeneratorAddr, 41 | bytes32 batchMerkleRoot, 42 | bytes memory merkleProof, 43 | uint256 verificationDataBatchIndex, 44 | bytes calldata pubInput, 45 | address batcherPaymentService 46 | ) external { 47 | bytes32 ledgerHash = bytes32(pubInput[:32]); 48 | if (!stateSettlement.isLedgerVerified(ledgerHash)) { 49 | revert InvalidLedger(ledgerHash); 50 | } 51 | 52 | MinaAccountValidationExample.AlignedArgs memory args = MinaAccountValidationExample.AlignedArgs( 53 | proofCommitment, 54 | provingSystemAuxDataCommitment, 55 | proofGeneratorAddr, 56 | batchMerkleRoot, 57 | merkleProof, 58 | verificationDataBatchIndex, 59 | pubInput, 60 | batcherPaymentService 61 | ); 62 | 63 | if (!accountValidation.validateAccount(args)) { 64 | revert InvalidZkappAccount(); 65 | } 66 | 67 | bytes calldata encodedAccount = pubInput[32 + 8:]; 68 | MinaAccountValidationExample.Account memory account = abi.decode(encodedAccount, (MinaAccountValidationExample.Account)); 69 | 70 | // check that this account represents the circuit we expect 71 | bytes32 verificationKeyHash = keccak256( 72 | abi.encode(account.zkapp.verificationKey) 73 | ); 74 | if (verificationKeyHash != ZKAPP_VERIFICATION_KEY_HASH) { 75 | revert IncorrectZkappAccount(verificationKeyHash); 76 | } 77 | 78 | // if isSolved == true 79 | if (account.zkapp.appState[1] != 0) { 80 | latestSolutionValidationAt = block.timestamp; 81 | } else { 82 | revert UnsolvedSudoku(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /example/mina_zkapp/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | jest: true, 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:@typescript-eslint/eslint-recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:o1js/recommended', 13 | ], 14 | parser: '@typescript-eslint/parser', 15 | parserOptions: { 16 | ecmaVersion: 'latest', 17 | }, 18 | plugins: ['@typescript-eslint', 'o1js'], 19 | rules: { 20 | 'no-constant-condition': 'off', 21 | 'prefer-const': 'off', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /example/mina_zkapp/.gitattributes: -------------------------------------------------------------------------------- 1 | # Use line endings appropriate for the system. This prevents Git from 2 | # complaining about project template line endings when committing on Windows. 3 | * text=auto eol=lf 4 | -------------------------------------------------------------------------------- /example/mina_zkapp/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: zkApp tests 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: {} 6 | jobs: 7 | test: 8 | timeout-minutes: 30 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | fail-fast: true 12 | matrix: 13 | node: [18, 20] 14 | os: [ubuntu-latest] 15 | steps: 16 | - name: Set up NodeJS 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node }} 20 | - name: Git checkout 21 | uses: actions/checkout@v4 22 | - name: NPM ci, build, & test 23 | run: | 24 | npm ci 25 | npm run build --if-present 26 | npm test 27 | env: 28 | CI: true 29 | -------------------------------------------------------------------------------- /example/mina_zkapp/.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | build 3 | node_modules 4 | coverage 5 | 6 | # Editor 7 | .vscode 8 | 9 | # System 10 | .DS_Store 11 | 12 | # Never commit keys to Git! 13 | keys 14 | -------------------------------------------------------------------------------- /example/mina_zkapp/.npmignore: -------------------------------------------------------------------------------- 1 | # Source files 2 | src 3 | 4 | node_modules 5 | coverage 6 | 7 | # Editor 8 | .vscode 9 | 10 | # System 11 | .DS_Store 12 | 13 | # Never reveal your keys! 14 | keys 15 | -------------------------------------------------------------------------------- /example/mina_zkapp/.prettierignore: -------------------------------------------------------------------------------- 1 | # NodeJS 2 | node_modules 3 | build 4 | coverage 5 | .husky 6 | 7 | # Editor 8 | .vscode 9 | 10 | # System 11 | .DS_Store 12 | 13 | # Misc 14 | LICENSE 15 | -------------------------------------------------------------------------------- /example/mina_zkapp/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /example/mina_zkapp/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Licensed under the Apache License, Version 2.0 (the "License"); 190 | you may not use this file except in compliance with the License. 191 | You may obtain a copy of the License at 192 | 193 | http://www.apache.org/licenses/LICENSE-2.0 194 | 195 | Unless required by applicable law or agreed to in writing, software 196 | distributed under the License is distributed on an "AS IS" BASIS, 197 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 198 | See the License for the specific language governing permissions and 199 | limitations under the License. 200 | -------------------------------------------------------------------------------- /example/mina_zkapp/README.md: -------------------------------------------------------------------------------- 1 | # Mina zkApp: Sudoku 2 | 3 | This template uses TypeScript. 4 | 5 | ## How to build 6 | 7 | ```sh 8 | npm run build 9 | ``` 10 | 11 | ## How to run tests 12 | 13 | ```sh 14 | npm run test 15 | npm run testw # watch mode 16 | ``` 17 | 18 | ## How to run coverage 19 | 20 | ```sh 21 | npm run coverage 22 | ``` 23 | 24 | ## License 25 | 26 | [Apache-2.0](LICENSE) 27 | -------------------------------------------------------------------------------- /example/mina_zkapp/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }]], 3 | }; 4 | -------------------------------------------------------------------------------- /example/mina_zkapp/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "zkappAddress": null, 3 | "version": 1, 4 | "deployAliases": {} 5 | } 6 | -------------------------------------------------------------------------------- /example/mina_zkapp/jest-resolver.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (request, options) => { 2 | return options.defaultResolver(request, { 3 | ...options, 4 | packageFilter: (pkg) => { 5 | // When importing o1js, we specify the Node ESM import as Jest by default imports the web version 6 | if (pkg.name === 'o1js') { 7 | return { 8 | ...pkg, 9 | main: pkg.exports.node.import, 10 | }; 11 | } 12 | if (pkg.name === 'node-fetch') { 13 | return { ...pkg, main: pkg.main }; 14 | } 15 | return { 16 | ...pkg, 17 | main: pkg.module || pkg.main, 18 | }; 19 | }, 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /example/mina_zkapp/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ 2 | export default { 3 | verbose: true, 4 | preset: 'ts-jest/presets/default-esm', 5 | testEnvironment: 'node', 6 | globals: { 7 | 'ts-jest': { 8 | useESM: true, 9 | }, 10 | }, 11 | testTimeout: 1_000_000, 12 | transform: { 13 | '^.+\\.(t)s$': 'ts-jest', 14 | '^.+\\.(j)s$': 'babel-jest', 15 | }, 16 | resolver: '/jest-resolver.cjs', 17 | transformIgnorePatterns: [ 18 | '/node_modules/(?!(tslib|o1js/node_modules/tslib))', 19 | ], 20 | modulePathIgnorePatterns: ['/build/'], 21 | moduleNameMapper: { 22 | '^(\\.{1,2}/.+)\\.js$': '$1', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /example/mina_zkapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sudoku", 3 | "version": "0.1.0", 4 | "description": "", 5 | "author": "", 6 | "license": "Apache-2.0", 7 | "keywords": [ 8 | "mina-zkapp", 9 | "mina-zk-app", 10 | "mina-dapp", 11 | "zkapp" 12 | ], 13 | "type": "module", 14 | "main": "build/src/index.js", 15 | "types": "build/src/index.d.ts", 16 | "scripts": { 17 | "build": "tsc", 18 | "buildw": "tsc --watch", 19 | "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage", 20 | "format": "prettier --write --ignore-unknown **/*", 21 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", 22 | "testw": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", 23 | "lint": "npx eslint src/* --fix", 24 | "start": "node build/src/run.js" 25 | }, 26 | "devDependencies": { 27 | "@babel/preset-env": "^7.16.4", 28 | "@babel/preset-typescript": "^7.16.0", 29 | "@types/jest": "^27.0.3", 30 | "@typescript-eslint/eslint-plugin": "^5.5.0", 31 | "@typescript-eslint/parser": "^5.5.0", 32 | "eslint": "^8.7.0", 33 | "eslint-plugin-o1js": "^0.4.0", 34 | "jest": "^28.1.3", 35 | "prettier": "^2.3.2", 36 | "ts-jest": "^28.0.8", 37 | "typescript": "^5.1" 38 | }, 39 | "peerDependencies": { 40 | "o1js": "^1.*" 41 | }, 42 | "engines": { 43 | "node": ">=18.14.0" 44 | }, 45 | "dependencies": { 46 | "dotenv": "^16.4.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/mina_zkapp/src/index.ts: -------------------------------------------------------------------------------- 1 | import { SudokuZkApp } from './sudoku.js'; 2 | 3 | export { SudokuZkApp }; 4 | -------------------------------------------------------------------------------- /example/mina_zkapp/src/run.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Sudoku, SudokuZkApp } from './sudoku.js'; 3 | import { generateSudoku, solveSudoku } from './sudoku-lib.js'; 4 | import { Mina, PrivateKey, NetworkId, fetchAccount, PublicKey } from 'o1js'; 5 | import dotenv from 'dotenv'; 6 | 7 | dotenv.config({ path: '../../.env' }); 8 | 9 | const TX_MAX_TRIES = 5; 10 | const FEE = 0.1; // in MINA 11 | 12 | let feepayerKey = PrivateKey.fromBase58(process.env.FEEPAYER_KEY as string); 13 | let feepayerAddress = feepayerKey.toPublicKey(); 14 | 15 | let zkAppAddress = PublicKey.fromBase58("B62qmKCv2HaPwVRHBKrDFGUpjSh3PPY9VqSa6ZweGAmj9hBQL4pfewn"); 16 | 17 | // define network (devnet) 18 | const Network = Mina.Network({ 19 | networkId: "testnet" as NetworkId, 20 | mina: "https://api.minascan.io/node/devnet/v1/graphql", 21 | }); 22 | const fee = Number(FEE) * 1e9; // in nanomina (1 billion = 1.0 mina) 23 | Mina.setActiveInstance(Network); 24 | 25 | // define zkapp and create sudoku to upload 26 | const zkApp = new SudokuZkApp(zkAppAddress); 27 | await fetchAccount({ publicKey: zkAppAddress }); 28 | console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); 29 | 30 | const sudoku = generateSudoku(0.5); 31 | 32 | console.log('Compiling Sudoku'); 33 | await SudokuZkApp.compile(); 34 | 35 | console.log("Sending update transaction"); 36 | await trySendTx( 37 | { sender: feepayerAddress, fee }, 38 | async () => { 39 | await zkApp.update(Sudoku.from(sudoku)); 40 | } 41 | ); 42 | 43 | let solution = solveSudoku(sudoku); 44 | if (solution === undefined) throw Error('cannot happen'); 45 | 46 | // submit the solution 47 | console.log("Sending submit transaction and waiting until it's included in a block"); 48 | await trySendTx({ sender: feepayerAddress, fee }, async () => { 49 | await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); 50 | }); 51 | 52 | console.log('Is the sudoku solved?', zkApp.isSolved.get().toBoolean()); 53 | 54 | async function trySendTx(sender: Mina.FeePayerSpec, f: () => Promise) { 55 | for (let i = 1; i <= TX_MAX_TRIES; i++) { 56 | try { 57 | console.log("Define new transaction"); 58 | const tx = await Mina.transaction(sender, f); 59 | 60 | console.log("Proving transaction"); 61 | await tx.prove(); 62 | 63 | console.log('Signing and sending transaction'); 64 | let pendingTx = await tx.sign([feepayerKey]).send(); 65 | 66 | if (pendingTx.status === 'pending') { 67 | console.log( 68 | `Success! transaction ${pendingTx.hash} sent\n` + 69 | "Waiting for transaction to be included in a block" 70 | ); 71 | await pendingTx.wait(); 72 | return; 73 | } 74 | } catch (err) { 75 | console.log(`Failed attempt ${i}/${TX_MAX_TRIES}, will try again`); 76 | console.log(err); 77 | continue; 78 | } 79 | } 80 | 81 | console.log("Failed all attempts, terminating."); 82 | process.exit(1); 83 | } 84 | -------------------------------------------------------------------------------- /example/mina_zkapp/src/sudoku-lib.js: -------------------------------------------------------------------------------- 1 | export { generateSudoku, solveSudoku, cloneSudoku }; 2 | 3 | /** 4 | * Generates a random 9x9 sudoku. Cells are either filled out (1,...,9) or empty (0). 5 | * 6 | * @param {number?} difficulty number between 0 (easiest = full sudoku) and 1 (hardest = empty sudoku) 7 | * @returns {number[][]} the sudoku 8 | */ 9 | function generateSudoku(difficulty = 0.5) { 10 | let solution = solveSudokuInternal(emptySudoku(), false); 11 | let partial = deleteRandomValues(solution, difficulty); 12 | return partial; 13 | } 14 | 15 | function deleteRandomValues(sudoku, p) { 16 | // p \in [0,1] ... probability to delete a value 17 | return sudoku.map((row) => row.map((x) => (Math.random() < p ? 0 : x))); 18 | } 19 | 20 | // sudoku = {0,...,9}^(9x9) matrix, 0 means empty cell 21 | function emptySudoku() { 22 | return Array.from({ length: 9 }, () => Array(9).fill(0)); 23 | } 24 | 25 | /** 26 | * Solve a given sudoku. Returns undefined if there is no solution. 27 | * 28 | * @param {number[][]} sudoku - The input sudoku with some cell values equal to zero 29 | * @returns {number[][] | undefined} - The full sudoku, or undefined if no solution exists 30 | */ 31 | function solveSudoku(sudoku) { 32 | return solveSudokuInternal(sudoku, true); 33 | } 34 | 35 | function solveSudokuInternal(sudoku, deterministic, possible) { 36 | // find *a* compatible solution to the sudoku - not checking for uniqueness 37 | // if deterministic = true: always take the smallest possible cell value 38 | // if deterministic = false: take random compatible values 39 | if (possible === undefined) { 40 | possible = possibleFromSudoku(sudoku); 41 | sudoku = cloneSudoku(sudoku); 42 | } 43 | while (true) { 44 | let [i, j, n] = cellWithFewestPossible(sudoku, possible); 45 | 46 | // no free values => sudoku is solved! 47 | if (n === Infinity) return sudoku; 48 | 49 | if (n === 1) { 50 | let x = chooseFirstPossible(i, j, possible); 51 | // console.log('determined', x, 'at', i, j); 52 | sudoku[i][j] = x; 53 | fixValue(i, j, x, possible); 54 | continue; 55 | } 56 | 57 | while (true) { 58 | let x = deterministic 59 | ? chooseFirstPossible(i, j, possible) 60 | : chooseRandomPossible(i, j, possible); 61 | 62 | // no values possible! we failed to get a solution 63 | if (x === 0) return; 64 | 65 | let sudoku_ = cloneSudoku(sudoku); 66 | let possible_ = cloneSudoku(possible); 67 | sudoku_[i][j] = x; 68 | fixValue(i, j, x, possible_); 69 | 70 | let solution = solveSudokuInternal(sudoku_, deterministic, possible_); 71 | 72 | // found a solution? return it! 73 | if (solution !== undefined) return solution; 74 | 75 | // there is no solution with x at i, j! 76 | // mark this value as impossible and try again 77 | possible[i][j][x - 1] = 0; 78 | } 79 | } 80 | } 81 | 82 | // possible = {0,1}^(9x9x9) tensor of possibilities 83 | // possible[i][j][x] contains a 1 if entry x \in {1,...,9} is possible in sudoku[i][j], 0 otherwise 84 | // => allows us to quickly determine cells where few values are possible 85 | function possibleFromSudoku(sudoku) { 86 | let possible = Array.from({ length: 9 }, () => 87 | Array.from({ length: 9 }, () => Array(9).fill(1)) 88 | ); 89 | for (let i = 0; i < 9; i++) { 90 | for (let j = 0; j < 9; j++) { 91 | let x = sudoku[i][j]; 92 | if (x !== 0) { 93 | fixValue(i, j, x, possible); 94 | } 95 | } 96 | } 97 | return possible; 98 | } 99 | 100 | function chooseFirstPossible(i, j, possible) { 101 | let possibleIJ = possible[i][j]; 102 | let x = 0; 103 | for (let k = 0; k < 9; k++) { 104 | if (possibleIJ[k] === 1) { 105 | x = k + 1; 106 | break; 107 | } 108 | } 109 | return x; 110 | } 111 | 112 | function chooseRandomPossible(i, j, possible) { 113 | let possibleValues = possible[i][j] 114 | .map((b, m) => b && m + 1) 115 | .filter((b) => b); 116 | let n = possibleValues.length; 117 | if (n === 0) return 0; 118 | let k = Math.floor(Math.random() * n); 119 | return possibleValues[k]; 120 | } 121 | 122 | function fixValue(i, j, x, possible) { 123 | // mark the value as impossible in the same row 124 | for (let k = 0; k < 9; k++) { 125 | if (k === j) { 126 | possible[i][k][x - 1] = 1; 127 | } else { 128 | possible[i][k][x - 1] = 0; 129 | } 130 | } 131 | // mark the value as impossible in the same column 132 | for (let k = 0; k < 9; k++) { 133 | if (k === i) { 134 | possible[k][j][x - 1] = 1; 135 | } else { 136 | possible[k][j][x - 1] = 0; 137 | } 138 | } 139 | // mark the value as impossible in the same square 140 | let [i0, i1] = divmod(i, 3); 141 | let [j0, j1] = divmod(j, 3); 142 | for (let k = 0; k < 9; k++) { 143 | let [ii, jj] = divmod(k, 3); 144 | if (ii === i1 && jj === j1) { 145 | possible[3 * i0 + ii][3 * j0 + jj][x - 1] = 1; 146 | } else { 147 | possible[3 * i0 + ii][3 * j0 + jj][x - 1] = 0; 148 | } 149 | } 150 | // mark all other values as impossible in the same cell 151 | for (let k = 0; k < 9; k++) { 152 | if (k === x - 1) { 153 | possible[i][j][k] = 1; 154 | } else { 155 | possible[i][j][k] = 0; 156 | } 157 | } 158 | } 159 | 160 | function cellWithFewestPossible(sudoku, possible) { 161 | let i0, j0; 162 | let fewest = Infinity; 163 | for (let i = 0; i < 9; i++) { 164 | for (let j = 0; j < 9; j++) { 165 | if (sudoku[i][j] !== 0) continue; 166 | let possibleIJ = possible[i][j]; 167 | let n = 0; 168 | for (let k = 0; k < 9; k++) { 169 | if (possibleIJ[k] === 1) n++; 170 | } 171 | if (n < fewest) { 172 | if (n === 1 || n === 0) return [i, j, n]; 173 | fewest = n; 174 | [i0, j0] = [i, j]; 175 | } 176 | } 177 | } 178 | return [i0, j0, fewest]; 179 | } 180 | 181 | function divmod(k, n) { 182 | let q = Math.floor(k / n); 183 | return [q, k - q * n]; 184 | } 185 | 186 | /** 187 | * Clones a sudoku. 188 | * 189 | * @template T 190 | * @param {T[]} sudoku 191 | * @returns {T[]} 192 | */ 193 | function cloneSudoku(sudoku) { 194 | if (Array.isArray(sudoku[0])) { 195 | return sudoku.map((x) => cloneSudoku(x)); 196 | } else { 197 | return [...sudoku]; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /example/mina_zkapp/src/sudoku.test.ts: -------------------------------------------------------------------------------- 1 | import { Sudoku, SudokuZkApp } from './sudoku'; 2 | import { cloneSudoku, generateSudoku, solveSudoku } from './sudoku-lib'; 3 | import { PrivateKey, PublicKey, Mina, AccountUpdate } from 'o1js'; 4 | 5 | describe('sudoku', () => { 6 | let zkApp: SudokuZkApp, 7 | zkAppPrivateKey: PrivateKey, 8 | zkAppAddress: PublicKey, 9 | sudoku: number[][], 10 | sender: Mina.TestPublicKey, 11 | senderKey: PrivateKey; 12 | 13 | beforeEach(async () => { 14 | let Local = await Mina.LocalBlockchain({ proofsEnabled: false }); 15 | Mina.setActiveInstance(Local); 16 | sender = Local.testAccounts[0]; 17 | senderKey = sender.key; 18 | zkAppPrivateKey = PrivateKey.random(); 19 | zkAppAddress = zkAppPrivateKey.toPublicKey(); 20 | zkApp = new SudokuZkApp(zkAppAddress); 21 | sudoku = generateSudoku(0.5); 22 | }); 23 | 24 | it('accepts a correct solution', async () => { 25 | await deploy(zkApp, zkAppPrivateKey, sudoku, sender, senderKey); 26 | 27 | let isSolved = zkApp.isSolved.get().toBoolean(); 28 | expect(isSolved).toBe(false); 29 | 30 | let solution = solveSudoku(sudoku); 31 | if (solution === undefined) throw Error('cannot happen'); 32 | let tx = await Mina.transaction(sender, async () => { 33 | let zkApp = new SudokuZkApp(zkAppAddress); 34 | await zkApp.submitSolution(Sudoku.from(sudoku), Sudoku.from(solution!)); 35 | }); 36 | await tx.prove(); 37 | await tx.sign([senderKey]).send(); 38 | 39 | isSolved = zkApp.isSolved.get().toBoolean(); 40 | expect(isSolved).toBe(true); 41 | }); 42 | 43 | it('rejects an incorrect solution', async () => { 44 | await deploy(zkApp, zkAppPrivateKey, sudoku, sender, senderKey); 45 | 46 | let solution = solveSudoku(sudoku); 47 | if (solution === undefined) throw Error('cannot happen'); 48 | 49 | let noSolution = cloneSudoku(solution); 50 | noSolution[0][0] = (noSolution[0][0] % 9) + 1; 51 | 52 | await expect(async () => { 53 | let tx = await Mina.transaction(sender, async () => { 54 | let zkApp = new SudokuZkApp(zkAppAddress); 55 | await zkApp.submitSolution( 56 | Sudoku.from(sudoku), 57 | Sudoku.from(noSolution) 58 | ); 59 | }); 60 | await tx.prove(); 61 | await tx.sign([senderKey]).send(); 62 | }).rejects.toThrow(/array contains the numbers 1...9/); 63 | 64 | let isSolved = zkApp.isSolved.get().toBoolean(); 65 | expect(isSolved).toBe(false); 66 | }); 67 | }); 68 | 69 | async function deploy( 70 | zkApp: SudokuZkApp, 71 | zkAppPrivateKey: PrivateKey, 72 | sudoku: number[][], 73 | sender: PublicKey, 74 | senderKey: PrivateKey 75 | ) { 76 | let tx = await Mina.transaction(sender, async () => { 77 | AccountUpdate.fundNewAccount(sender); 78 | await zkApp.deploy(); 79 | await zkApp.update(Sudoku.from(sudoku)); 80 | }); 81 | await tx.prove(); 82 | // this tx needs .sign(), because `deploy()` adds an account update that requires signature authorization 83 | await tx.sign([zkAppPrivateKey, senderKey]).send(); 84 | } 85 | -------------------------------------------------------------------------------- /example/mina_zkapp/src/sudoku.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Field, 3 | SmartContract, 4 | method, 5 | Bool, 6 | state, 7 | State, 8 | Poseidon, 9 | Struct, 10 | Provable, 11 | } from 'o1js'; 12 | 13 | export { Sudoku, SudokuZkApp }; 14 | 15 | class Sudoku extends Struct({ 16 | value: Provable.Array(Provable.Array(Field, 9), 9), 17 | }) { 18 | static from(value: number[][]) { 19 | return new Sudoku({ value: value.map((row) => row.map(Field)) }); 20 | } 21 | 22 | hash() { 23 | return Poseidon.hash(this.value.flat()); 24 | } 25 | } 26 | 27 | class SudokuZkApp extends SmartContract { 28 | @state(Field) sudokuHash = State(); 29 | @state(Bool) isSolved = State(); 30 | 31 | /** 32 | * by making this a `@method`, we ensure that a proof is created for the state initialization. 33 | * alternatively (and, more efficiently), we could have used `super.init()` inside `update()` below, 34 | * to ensure the entire state is overwritten. 35 | * however, it's good to have an example which tests the CLI's ability to handle init() decorated with `@method`. 36 | */ 37 | @method async init() { 38 | super.init(); 39 | } 40 | 41 | @method async update(sudokuInstance: Sudoku) { 42 | this.sudokuHash.set(sudokuInstance.hash()); 43 | this.isSolved.set(Bool(false)); 44 | } 45 | 46 | @method async submitSolution( 47 | sudokuInstance: Sudoku, 48 | solutionInstance: Sudoku 49 | ) { 50 | let sudoku = sudokuInstance.value; 51 | let solution = solutionInstance.value; 52 | 53 | // first, we check that the passed solution is a valid sudoku 54 | 55 | // define helpers 56 | let range9 = Array.from({ length: 9 }, (_, i) => i); 57 | let oneTo9 = range9.map((i) => Field(i + 1)); 58 | 59 | function assertHas1To9(array: Field[]) { 60 | oneTo9 61 | .map((k) => range9.map((i) => array[i].equals(k)).reduce(Bool.or)) 62 | .reduce(Bool.and) 63 | .assertTrue('array contains the numbers 1...9'); 64 | } 65 | 66 | // check all rows 67 | for (let i = 0; i < 9; i++) { 68 | let row = solution[i]; 69 | assertHas1To9(row); 70 | } 71 | // check all columns 72 | for (let j = 0; j < 9; j++) { 73 | let column = solution.map((row) => row[j]); 74 | assertHas1To9(column); 75 | } 76 | // check 3x3 squares 77 | for (let k = 0; k < 9; k++) { 78 | let [i0, j0] = divmod(k, 3); 79 | let square = range9.map((m) => { 80 | let [i1, j1] = divmod(m, 3); 81 | return solution[3 * i0 + i1][3 * j0 + j1]; 82 | }); 83 | assertHas1To9(square); 84 | } 85 | 86 | // next, we check that the solution extends the initial sudoku 87 | for (let i = 0; i < 9; i++) { 88 | for (let j = 0; j < 9; j++) { 89 | let cell = sudoku[i][j]; 90 | let solutionCell = solution[i][j]; 91 | // either the sudoku has nothing in it (indicated by a cell value of 0), 92 | // or it is equal to the solution 93 | Bool.or(cell.equals(0), cell.equals(solutionCell)).assertTrue( 94 | `solution cell (${i + 1},${j + 1}) matches the original sudoku` 95 | ); 96 | } 97 | } 98 | 99 | // finally, we check that the sudoku is the one that was originally deployed 100 | let sudokuHash = this.sudokuHash.getAndRequireEquals(); 101 | 102 | sudokuInstance 103 | .hash() 104 | .assertEquals(sudokuHash, 'sudoku matches the one committed on-chain'); 105 | 106 | // all checks passed => the sudoku is solved! 107 | this.isSolved.set(Bool(true)); 108 | } 109 | } 110 | 111 | function divmod(k: number, n: number) { 112 | let q = Math.floor(k / n); 113 | return [q, k - q * n]; 114 | } 115 | -------------------------------------------------------------------------------- /example/mina_zkapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2022", 5 | "lib": ["dom", "esnext"], 6 | "outDir": "./build", 7 | "rootDir": ".", 8 | "strict": true, 9 | "strictPropertyInitialization": false, // to enable generic constructors, e.g. on CircuitValue 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "esModuleInterop": true, 13 | "moduleResolution": "node", 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "allowJs": true, 17 | "declaration": true, 18 | "sourceMap": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "allowSyntheticDefaultImports": true, 21 | "useDefineForClassFields": false, 22 | }, 23 | "include": ["./src"], 24 | } 25 | -------------------------------------------------------------------------------- /img/batch_verification_evaluation_proofs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/batch_verification_evaluation_proofs.png -------------------------------------------------------------------------------- /img/commitments_to_quotient_poly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/commitments_to_quotient_poly.png -------------------------------------------------------------------------------- /img/commitments_to_secret_poly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/commitments_to_secret_poly.png -------------------------------------------------------------------------------- /img/consensus01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus01.png -------------------------------------------------------------------------------- /img/consensus02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus02.png -------------------------------------------------------------------------------- /img/consensus03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus03.png -------------------------------------------------------------------------------- /img/consensus04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus04.png -------------------------------------------------------------------------------- /img/consensus05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus05.png -------------------------------------------------------------------------------- /img/consensus06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus06.png -------------------------------------------------------------------------------- /img/consensus07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus07.png -------------------------------------------------------------------------------- /img/consensus08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/consensus08.png -------------------------------------------------------------------------------- /img/example_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/example_diagram.png -------------------------------------------------------------------------------- /img/palas_vesta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/palas_vesta.png -------------------------------------------------------------------------------- /img/pickles_step_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_01.png -------------------------------------------------------------------------------- /img/pickles_step_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_02.png -------------------------------------------------------------------------------- /img/pickles_step_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_03.png -------------------------------------------------------------------------------- /img/pickles_step_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_04.png -------------------------------------------------------------------------------- /img/pickles_step_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_05.png -------------------------------------------------------------------------------- /img/pickles_step_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_06.png -------------------------------------------------------------------------------- /img/pickles_step_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_07.png -------------------------------------------------------------------------------- /img/pickles_step_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_step_08.png -------------------------------------------------------------------------------- /img/pickles_structure_drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/pickles_structure_drawio.png -------------------------------------------------------------------------------- /img/prover_provides_evaluations_linearization_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/prover_provides_evaluations_linearization_01.png -------------------------------------------------------------------------------- /img/prover_provides_evaluations_linearization_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/prover_provides_evaluations_linearization_02.png -------------------------------------------------------------------------------- /img/step_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/step_diagram.png -------------------------------------------------------------------------------- /img/verifier_produces_evaluation_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/img/verifier_produces_evaluation_point.png -------------------------------------------------------------------------------- /srs/pallas.srs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/srs/pallas.srs -------------------------------------------------------------------------------- /srs/vesta.srs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdaclass/mina_bridge/61d83abecdf30422ca014907169ce345564c12f3/srs/vesta.srs --------------------------------------------------------------------------------