diff --git a/.gitmodules b/.gitmodules index f34d21e..29700af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "circuits/plonky2x/verifier/lib/foundry-devops"] path = circuits/plonky2x/verifier/lib/foundry-devops url = https://github.com/chainaccelorg/foundry-devops +[submodule "circuits/plonky2x/contract/lib/solidity-bytes-utils"] + path = circuits/plonky2x/contract/lib/solidity-bytes-utils + url = https://github.com/GNSPS/solidity-bytes-utils diff --git a/Cargo.lock b/Cargo.lock index f5e9f0f..c6dda70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1105,6 +1105,7 @@ dependencies = [ [[package]] name = "curta" version = "0.1.0" +source = "git+https://github.com/dndll/starkyx.git#7f676e81dcf13393d9ca3bb825bf8e2a7c046cba" dependencies = [ "anyhow", "bincode", @@ -3158,7 +3159,7 @@ dependencies = [ "near-primitives-core", "pretty_env_logger", "protobuf 3.2.0", - "rand 0.8.5", + "rand 0.7.3", "reqwest", "serde", "serde_json", @@ -3182,7 +3183,7 @@ dependencies = [ "near-primitives", "near-primitives-core", "pretty_env_logger", - "rand 0.8.5", + "rand 0.7.3", "serde", "serde_json", "test-utils", @@ -3207,7 +3208,7 @@ dependencies = [ "near-primitives", "near-primitives-core", "pretty_env_logger", - "rand 0.8.5", + "rand 0.7.3", "serde", "serde_json", "thiserror", @@ -3215,7 +3216,7 @@ dependencies = [ ] [[package]] -name = "near-light-client-succint-operator" +name = "near-light-client-succinct-operator" version = "0.2.0" dependencies = [ "cfg-if", @@ -3583,7 +3584,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.48", @@ -4179,6 +4180,7 @@ source = "git+https://github.com/mir-protocol/plonky2.git#b600142cd454b95eba403f [[package]] name = "plonky2x" version = "0.1.0" +source = "git+https://github.com/dndll/succinctx.git#b2540e29d54bcf97bc922d57b50acfe89665398a" dependencies = [ "anyhow", "array-macro", @@ -4220,6 +4222,7 @@ dependencies = [ [[package]] name = "plonky2x-derive" version = "0.1.0" +source = "git+https://github.com/dndll/succinctx.git#b2540e29d54bcf97bc922d57b50acfe89665398a" dependencies = [ "proc-macro2", "quote", @@ -5695,7 +5698,7 @@ dependencies = [ "near-primitives-core", "pretty_assertions", "pretty_env_logger", - "rand 0.8.5", + "rand 0.7.3", "serde", "serde_json", "thiserror", @@ -6862,8 +6865,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "curta" -version = "0.1.0" -source = "git+https://github.com/dndll/starkyx.git#7f676e81dcf13393d9ca3bb825bf8e2a7c046cba" diff --git a/Cargo.toml b/Cargo.toml index 961b35b..fdeb375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,9 +52,9 @@ test-utils = { path = "crates/test-utils" } curta = { git = "https://github.com/dndll/starkyx.git" } [patch."https://github.com/succinctlabs/starkyx.git"] -curta = { path = "./vendor/starkyx/curta" } -#curta = { git = "https://github.com/dndll/starkyx.git" } +#curta = { path = "./vendor/starkyx/curta" } +curta = { git = "https://github.com/dndll/starkyx.git" } [patch."https://github.com/succinctlabs/succinctx.git"] -plonky2x = { path = "./vendor/succinctx/plonky2x/core" } -#plonky2x = { git = "https://github.com/dndll/succinctx.git" } +#plonky2x = { path = "./vendor/succinctx/plonky2x/core" } +plonky2x = { git = "https://github.com/dndll/succinctx.git" } diff --git a/Makefile b/Makefile index cd22ecc..e89be5d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .EXPORT_ALL_VARIABLES: -include .env +-include .env TAG_PREFIX?=near IMAGE_TAG?=0.0.1 @@ -26,7 +26,7 @@ build-verify-circuit: SYNC_FUNCTION_ID=0x350c2939eb7ff2185612710a2b641b4b46faab68e1e2c57b6f15e0af0674f5e9 VERIFY_FUNCTION_ID=0x39fb2562b80725bb7538dd7d850126964e565a1a837d2d7f2a018e185b08fc0e -ETH_RPC=https://goerli.gateway.tenderly.co +ETH_RPC=https://rpc.goerli.eth.gateway.fm NEAR_CHECKPOINT_HEADER_HASH=0x63b87190ffbaa36d7dab50f918fe36f70ab26910a0e9d797161e2356561598e3 CHAIN_ID=5 CD_CONTRACTS=cd ./circuits/plonky2x/contract @@ -43,12 +43,33 @@ deploy: build-contracts --verifier etherscan initialise: - cd $(ETH_CONTRACTS_PATH) && forge script Initialise \ + $(CD_CONTRACTS) && forge script Initialise \ --rpc-url $(ETH_RPC) \ --private-key $$ETH_PRIVATE_KEY \ --broadcast \ --verify \ --verifier etherscan +upgrade: + $(CD_CONTRACTS) && forge script Upgrade \ + --rpc-url $(ETH_RPC) \ + --private-key $$ETH_PRIVATE_KEY \ + --broadcast \ + --verify \ + --verifier etherscan +verify: + $(CD_CONTRACTS) && forge script Verify \ + --rpc-url $(ETH_RPC) \ + --private-key $$ETH_PRIVATE_KEY \ + --broadcast \ + --verify \ + --verifier etherscan + +# verify-contract: +# cd circuits/plonky2x/contracts/ && forge verify-contract \ +# --chain=5 \ +# --watch \ +# 0x438634f4dF74CdD6963c750c30E3e9bf9F029838 \ +# src/NearX.sol:NearX # TODO: upgrade diff --git a/bin/operator/Cargo.toml b/bin/operator/Cargo.toml index ad3a43e..e53fe6a 100644 --- a/bin/operator/Cargo.toml +++ b/bin/operator/Cargo.toml @@ -1,11 +1,9 @@ [package] edition.workspace = true license.workspace = true -name = "near-light-client-succint-operator" +name = "near-light-client-succinct-operator" version.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [[bin]] name = "sync" path = "src/main.rs" diff --git a/circuits/plonky2x/contract/foundry.toml b/circuits/plonky2x/contract/foundry.toml index 15402f9..f78b70d 100644 --- a/circuits/plonky2x/contract/foundry.toml +++ b/circuits/plonky2x/contract/foundry.toml @@ -1,7 +1,8 @@ [profile.default] fs_permissions = [ { access = "read", path = "./broadcast" } ] libs = [ "lib" ] +optimizer = true +optimizer-runs = 1_000_000 out = "out" remappings = [ "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts", "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts" ] src = "src" -# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/circuits/plonky2x/contract/lib/solidity-bytes-utils b/circuits/plonky2x/contract/lib/solidity-bytes-utils new file mode 160000 index 0000000..e0115c4 --- /dev/null +++ b/circuits/plonky2x/contract/lib/solidity-bytes-utils @@ -0,0 +1 @@ +Subproject commit e0115c4d231910df47ce3b60625ce562fe4af985 diff --git a/circuits/plonky2x/contract/script/Verify.s.sol b/circuits/plonky2x/contract/script/Verify.s.sol new file mode 100644 index 0000000..0b9f306 --- /dev/null +++ b/circuits/plonky2x/contract/script/Verify.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Script} from "forge-std/Script.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; +import {NearX, TransactionOrReceiptId} from "../src/NearX.sol"; + +contract Verify is Script { + function run() external { + address proxyAddress = DevOpsTools.get_most_recent_deployment( + "ERC1967Proxy", + block.chainid + ); + TransactionOrReceiptId[] memory ids = new TransactionOrReceiptId[](2); + + ids[0].isTransaction = true; + bytes32 txId = hex"2c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d79"; + ids[0].id = txId; + bytes + memory txAccount = hex"7a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; + ids[0].account = txAccount; + + ids[1].isTransaction = false; + bytes32 rxId = hex"7ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e"; + ids[1].id = rxId; + bytes + memory rxAccount = hex"70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; + ids[1].account = rxAccount; + + vm.startBroadcast(); + + NearX lightClient = NearX(payable(proxyAddress)); + + lightClient.requestVerify(ids); + + vm.stopBroadcast(); + } +} diff --git a/circuits/plonky2x/contract/src/NearX.sol b/circuits/plonky2x/contract/src/NearX.sol index a71adb4..0838af4 100644 --- a/circuits/plonky2x/contract/src/NearX.sol +++ b/circuits/plonky2x/contract/src/NearX.sol @@ -5,7 +5,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {ISuccinctGateway} from "./interfaces/ISuccinctGateway.sol"; -import {INearX, TransactionOrReceiptId, encodePackedIds, decodePackedIds} from "./interfaces/INearX.sol"; +import {INearX, TransactionOrReceiptId, ProofVerificationResult, encodePackedIds, decodePackedIds, decodePackedResults} from "./interfaces/INearX.sol"; /// @notice The NearX contract is a light client for Near. contract NearX is INearX, Initializable, OwnableUpgradeable, UUPSUpgradeable { @@ -117,13 +117,13 @@ contract NearX is INearX, Initializable, OwnableUpgradeable, UUPSUpgradeable { emit VerifyRequested(latestHeader, ids); } - function handleVerify(bytes memory _output, bytes memory _context) + function handleVerify(bytes calldata _output, bytes memory _context) external { if (msg.sender != gateway || !ISuccinctGateway(gateway).isCallback()) { revert NotFromSuccinctGateway(msg.sender); } - TransactionOrReceiptId[] memory ids = decodePackedIds(_output); - emit VerifyResult(ids); + ProofVerificationResult[] memory results = decodePackedResults(_output); + emit VerifyResult(results); } } diff --git a/circuits/plonky2x/contract/src/interfaces/Bytes.sol b/circuits/plonky2x/contract/src/interfaces/Bytes.sol new file mode 100644 index 0000000..7ce2504 --- /dev/null +++ b/circuits/plonky2x/contract/src/interfaces/Bytes.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (utils/Bytes.sol) +pragma solidity ^0.8.4; + +/** + * @dev Provides a set of functions to operate with packed data in bytes array. + * + * _Available since v4.8.1 + */ +library Bytes { + /** + * @dev Read uint from input bytes array + */ + function _readUint( + bytes memory input, + uint256 offset, + uint256 length + ) private pure returns (uint256 result) { + require(offset + length <= input.length, "Bytes: Out of range"); + assembly { + // Read 256 bits at the given offset + result := mload(add(add(input, 0x20), offset)) + } + } + + /** + * @dev Convert bytes to bytes32 array + */ + function toBytes32Array(bytes memory input) + internal + pure + returns (bytes32[] memory) + { + require(input.length % 32 == 0, "Bytes: Invalid input length"); + bytes32[] memory result = new bytes32[](input.length / 32); + assembly { + // Read length of input + let length := mload(input) + + // Seek offset to the beginning + let offset := add(input, 0x20) + + // Point result offset to the beginging + let resultOffset := add(result, 0x20) + + for { + let i := 0 + } lt(i, length) { + i := add(i, 0x20) + } { + // Copy 32 bytes to bytes32 array + mstore(resultOffset, mload(add(offset, i))) + // resultOffset += 32 + resultOffset := add(resultOffset, 0x20) + } + } + return result; + } + + /** + * @dev Read sub bytes array from input bytes array + */ + function readBytes( + bytes memory input, + uint256 offset, + uint256 length + ) internal pure returns (bytes memory result, uint256 nextOffset) { + nextOffset = offset + length; + require(nextOffset <= input.length, "Bytes: Out of range"); + result = new bytes(length); + assembly { + // Set seek to the given offset of the input + let seek := add(add(input, 0x20), offset) + let resultOffset := add(result, 0x20) + + for { + let i := 0 + } lt(i, length) { + i := add(i, 0x20) + } { + mstore(add(resultOffset, i), mload(add(seek, i))) + } + } + } + + /** + * @dev Read uint256 from input bytes array + */ + function readUint256(bytes memory input, uint256 offset) + internal + pure + returns (uint256 result, uint256 nextOffset) + { + return (_readUint(input, offset, 32), offset + 32); + } + + /** + * @dev Read uint160 from input bytes array + */ + function readUint160(bytes memory input, uint256 offset) + internal + pure + returns (uint160 result, uint256 nextOffset) + { + return (uint160(_readUint(input, offset, 20) >> 96), offset + 20); + } + + /** + * @dev Read uint128 from input bytes array + */ + function readUint128(bytes memory input, uint256 offset) + internal + pure + returns (uint128 result, uint256 nextOffset) + { + return (uint128(_readUint(input, offset, 16) >> 128), offset + 16); + } + + /** + * @dev Read uint64 from input bytes array + */ + function readUint64(bytes memory input, uint256 offset) + internal + pure + returns (uint64 result, uint256 nextOffset) + { + return (uint64(_readUint(input, offset, 8) >> 192), offset + 8); + } + + /** + * @dev Read uint32 from input bytes array + */ + function readUint32(bytes memory input, uint256 offset) + internal + pure + returns (uint32 result, uint256 nextOffset) + { + return (uint32(_readUint(input, offset, 4) >> 224), offset + 4); + } + + /** + * @dev Read uint16 from input bytes array + */ + function readUint16(bytes memory input, uint256 offset) + internal + pure + returns (uint16 result, uint256 nextOffset) + { + return (uint16(_readUint(input, offset, 2) >> 240), offset + 2); + } +} diff --git a/circuits/plonky2x/contract/src/interfaces/INearX.sol b/circuits/plonky2x/contract/src/interfaces/INearX.sol index 59d1f3a..db3f8f4 100644 --- a/circuits/plonky2x/contract/src/interfaces/INearX.sol +++ b/circuits/plonky2x/contract/src/interfaces/INearX.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import "./Bytes.sol"; + interface INearX { /// @notice Emits event with the new head update. event HeadUpdate(bytes32 headerHash); @@ -31,7 +33,7 @@ interface INearX { error FunctionIdsNotInitialised(); /// @notice The result of the verification request - event VerifyResult(TransactionOrReceiptId[] ids); + event VerifyResult(ProofVerificationResult[] results); } uint256 constant MAX_LEN = 64; @@ -48,12 +50,20 @@ function encodePackedIds(TransactionOrReceiptId[] memory ids) { bytes memory output; for (uint256 i = 0; i < ids.length; i++) { - output = abi.encodePacked( - output, - ids[i].isTransaction, - ids[i].id, - ids[i].account - ); + if (i > 0) { + output = abi.encodePacked( + output, + ids[i].isTransaction, + ids[i].id, + ids[i].account + ); + } else { + output = abi.encodePacked( + ids[i].isTransaction, + ids[i].id, + ids[i].account + ); + } } return output; } @@ -62,13 +72,17 @@ function decodePackedIds(bytes memory _input) pure returns (TransactionOrReceiptId[] memory) { - uint256 idsLength = _input.length / 1 + 32 + MAX_LEN; + uint256 iterationLength = 1 + 32 + MAX_LEN; + uint256 idsLength = _input.length / iterationLength; TransactionOrReceiptId[] memory ids = new TransactionOrReceiptId[]( idsLength ); + bytes memory nextBytes; + uint256 offset = 0; for (uint256 i = 0; i < idsLength; i++) { - ids[i] = decodeTransactionOrReceiptId(_input); + (nextBytes, offset) = Bytes.readBytes(_input, offset, iterationLength); + ids[i] = decodeTransactionOrReceiptId(nextBytes); } return ids; } @@ -77,9 +91,52 @@ function decodeTransactionOrReceiptId(bytes memory _input) pure returns (TransactionOrReceiptId memory id) { - id.isTransaction = abi.decode(_input, (bool)); - id.id = abi.decode(_input, (bytes32)); - bytes32 accountX = abi.decode(_input, (bytes32)); - bytes32 accountY = abi.decode(_input, (bytes32)); - id.account = abi.encodePacked(accountX, accountY); + bytes memory nextBytes; + uint256 offset = 0; + + (nextBytes, offset) = Bytes.readBytes(_input, offset, 1); + id.isTransaction = uint8(bytes1(nextBytes)) != 0; + + (nextBytes, offset) = Bytes.readBytes(_input, offset, 32); + id.id = abi.decode(nextBytes, (bytes32)); + + (nextBytes, offset) = Bytes.readBytes(_input, offset, MAX_LEN); + id.account = nextBytes; +} + +struct ProofVerificationResult { + bytes32 id; + bool result; +} + +function decodePackedResults(bytes memory _input) + pure + returns (ProofVerificationResult[] memory) +{ + uint256 iterationLength = 1 + 32; + uint256 idsLength = _input.length / iterationLength; + ProofVerificationResult[] memory results = new ProofVerificationResult[]( + idsLength + ); + + bytes memory nextBytes; + uint256 offset = 0; + for (uint256 i = 0; i < idsLength; i++) { + (nextBytes, offset) = Bytes.readBytes(_input, offset, iterationLength); + results[i] = decodeProofVerificationResult(nextBytes); + } + return results; +} + +function decodeProofVerificationResult(bytes memory _input) + pure + returns (ProofVerificationResult memory result) +{ + bytes memory nextBytes; + uint256 offset = 0; + (nextBytes, offset) = Bytes.readBytes(_input, offset, 32); + result.id = abi.decode(nextBytes, (bytes32)); + + (nextBytes, offset) = Bytes.readBytes(_input, offset, 1); + result.result = uint8(bytes1(nextBytes)) != 0; } diff --git a/circuits/plonky2x/contract/test/NearX.t.sol b/circuits/plonky2x/contract/test/NearX.t.sol index 3859530..e168c10 100644 --- a/circuits/plonky2x/contract/test/NearX.t.sol +++ b/circuits/plonky2x/contract/test/NearX.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import "../src/NearX.sol"; +import {Bytes} from "../src/interfaces/Bytes.sol"; contract NearXTest is Test { NearX public lightClient; @@ -18,26 +19,61 @@ contract NearXTest is Test { console.logBytes(encodedInput); } - function testGetEncodePackedVerify() public view { + function testGetEncodePackedVerify() public { // TODO: bytes32 header = hex"63b87190ffbaa36d7dab50f918fe36f70ab26910a0e9d797161e2356561598e3"; - bytes tx = hex"012c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d797a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; - bytes rx = hex"007ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; + bytes memory txIsAccount = hex"01"; + bytes32 txId = hex"2c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d79"; + bytes + memory txAccount = hex"7a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; + + bytes memory rxIsAccount = hex"00"; + bytes32 rxId = hex"7ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e"; + bytes + memory rxAccount = hex"70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; TransactionOrReceiptId[] memory ids = new TransactionOrReceiptId[](2); + ids[0].isTransaction = true; - ids[0] - .id = hex"63b87190ffbaa36d7dab50f918fe36f70ab26910a0e9d797161e2356561598e3"; + ids[0].id = txId; + ids[0].account = txAccount; + ids[1].isTransaction = false; - ids[1] - .id = hex"63b87190ffbaa36d7dab50f918fe36f70ab26910a0e9d797161e2356561598e3"; - ids[1] - .account = hex"0000000000000000000000000000000000000000000000000000000000000000"; - bytes memory encodedInput = abi.encodePacked( - latestHeader, - encodePackedIds(ids) + ids[1].id = rxId; + ids[1].account = rxAccount; + + bytes memory encodedIds = encodePackedIds(ids); + bytes memory encodedInput = abi.encodePacked(header, encodedIds); + + bytes + memory expected = hex"63b87190ffbaa36d7dab50f918fe36f70ab26910a0e9d797161e2356561598e3012c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d797a61766f64696c2e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c007ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e70726963656f7261636c652e746573746e65742c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"; + + assertEq(encodedInput, expected); + + bytes32 encodedHeader = bytes32(encodedInput); + assertEq(header, encodedHeader); + + TransactionOrReceiptId[] memory decodedIds = decodePackedIds( + encodedIds ); + assertEq(ids[0].isTransaction, decodedIds[0].isTransaction); + assertEq(ids[0].id, decodedIds[0].id); + assertEq(ids[0].account, decodedIds[0].account); + assertEq(ids[1].isTransaction, decodedIds[1].isTransaction); + assertEq(ids[1].id, decodedIds[1].id); + assertEq(ids[1].account, decodedIds[1].account); + } - console.logBytes(encodedInput); + function testDecodeResult() public { + bytes + memory inputData = hex"7ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e012c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d7901"; + ProofVerificationResult[] memory results = decodePackedResults( + inputData + ); + bytes + memory outputTest = hex"2c53bcfe871da28decc45c3437f5864568d91af6d990dbc2662f11ce44c18d79017ff581f8517ec58459099a5af2465d5232fdcdd7c4da9c3d42a887bf6bd5457e01"; + ProofVerificationResult[] memory expected = decodePackedResults( + outputTest + ); } } diff --git a/circuits/plonky2x/src/builder.rs b/circuits/plonky2x/src/builder.rs index 79f96e4..5cf9835 100644 --- a/circuits/plonky2x/src/builder.rs +++ b/circuits/plonky2x/src/builder.rs @@ -3,6 +3,7 @@ use plonky2x::prelude::*; use pretty_assertions::assert_eq; use crate::{ + hint::FetchHeaderInputs, merkle::{MerklePathVariable, NearMerkleTree}, variables::{ ApprovalMessage, BlockHeightVariable, BlockVariable, BpsApprovals, BpsArr, diff --git a/circuits/plonky2x/src/circuits/sync.rs b/circuits/plonky2x/src/circuits/sync.rs index 206946c..0cd61a5 100644 --- a/circuits/plonky2x/src/circuits/sync.rs +++ b/circuits/plonky2x/src/circuits/sync.rs @@ -23,13 +23,7 @@ impl Circuit for SyncCircuit { plonky2::plonk::config::AlgebraicHasher<>::Field>, { let trusted_header_hash = b.evm_read::(); - b.watch(&trusted_header_hash, "trusted_header_hash"); - - let untrusted = FetchHeaderInputs(NETWORK.into()).fetch(b, &trusted_header_hash); - let untrusted_hash = untrusted.hash(b); - b.watch(&untrusted_hash, "untrusted_hash"); - b.assert_is_equal(trusted_header_hash, untrusted_hash); - let head = untrusted; + let head = FetchHeaderInputs(NETWORK.into()).fetch(b, &trusted_header_hash); // This is a very interesting trick to be able to get the BPS for the next epoch // without the need to store the BPS, we verify the hash of the BPS in the diff --git a/circuits/plonky2x/src/circuits/verify.rs b/circuits/plonky2x/src/circuits/verify.rs index cd5d103..31d7c9a 100644 --- a/circuits/plonky2x/src/circuits/verify.rs +++ b/circuits/plonky2x/src/circuits/verify.rs @@ -3,12 +3,13 @@ pub use plonky2x::{self, backend::circuit::Circuit, prelude::*}; use plonky2x::{ frontend::{hint::simple::hint::Hint, mapreduce::generator::MapReduceDynamicGenerator}, prelude::plonky2::plonk::config::{AlgebraicHasher, GenericConfig}, + register_watch_generator, }; use serde::{Deserialize, Serialize}; use crate::{ builder::Verify, - hint::{FetchProofInputs, ProofInputVariable}, + hint::{FetchHeaderInputs, FetchProofInputs, ProofInputVariable}, variables::{ byte_from_bool, CryptoHashVariable, EncodeInner, HeaderVariable, TransactionOrReceiptIdVariable, @@ -34,22 +35,29 @@ impl Circuit <>::Config as GenericConfig>::Hasher: AlgebraicHasher<>::Field>, { - let trusted_head = b.evm_read::(); + let trusted_header_hash = b.evm_read::(); + let head = FetchHeaderInputs(NETWORK.into()).fetch(b, &trusted_header_hash); + let mut ids = vec![]; for _ in 0..N { ids.push(b.evm_read::()); } - let proofs = FetchProofInputs::(NETWORK.into()).fetch(b, &trusted_head, &ids); + let proofs = FetchProofInputs::(NETWORK.into()).fetch(b, &head, &ids); + // Init a default result for N let zero = b.constant::([0u8; 32].into()); let _false = b._false(); + let default = ProofVerificationResultVariable { + id: zero, + result: _false, + }; // TODO: write some outputs here for each ID - let output = b.mapreduce_dynamic::<(), ProofInputVariable, ArrayVariable, Self, B, _, _>( - (), + let output = b.mapreduce_dynamic::, Self, B, _, _>( + default, proofs.data, - |_, proofs, b| { + |default, proofs, b| { let mut results = vec![]; // TODO[Optimisation]: could parallelise these @@ -60,19 +68,16 @@ impl Circuit results.resize( N, - ProofVerificationResultVariable { - id: zero, - result: _false, - }, + default, ); results.into() }, |_, l, r, b| MergeProofHint::.merge(b, &l, &r), ); + b.watch_slice(&output.data, "output"); for r in output.data { b.evm_write::(r.id); - let _true = b._true(); let passed = byte_from_bool(b, r.result); b.evm_write::(passed); } @@ -84,6 +89,7 @@ impl Circuit { registry.register_async_hint::>(); registry.register_hint::>(); + registry.register_async_hint::(); // We hash in verify registry.register_hint::(); @@ -92,7 +98,7 @@ impl Circuit registry.register_simple::, Self, @@ -100,7 +106,7 @@ impl Circuit D, >>(dynamic_id); - // TODO: register_watch_generator! + register_watch_generator!(registry, L, D, ProofVerificationResultVariable); } } @@ -130,14 +136,23 @@ pub struct MergeProofHint; impl, const D: usize, const N: usize> Hint for MergeProofHint { fn hint(&self, input_stream: &mut ValueStream, output_stream: &mut ValueStream) { let left = input_stream.read_value::>(); + log::debug!("Left results: {:?}", left); let right = input_stream.read_value::>(); + log::debug!("Right results: {:?}", right); let mut results = left .into_iter() .chain(right.into_iter()) - .filter_map(|r| if r.id.0 != [0u8; 32] { Some(r) } else { None }) + .filter_map(|r| { + if r.id.0 != [0u8; 32] && r.id.0 != [255u8; 32] { + Some(r) + } else { + None + } + }) .collect_vec(); + log::debug!("Merged results: {:?}", results); results.resize( N, ProofVerificationResultVariableValue:: { @@ -171,12 +186,10 @@ impl MergeProofHint { mod beefy_tests { use std::str::FromStr; - use ::test_utils::CryptoHash; use near_light_client_protocol::prelude::Itertools; use near_primitives::types::TransactionOrReceiptId; - use plonky2x::frontend::vars::EvmVariable; use serial_test::serial; - use test_utils::fixture; + use test_utils::{fixture, CryptoHash}; use super::*; use crate::{ @@ -227,7 +240,7 @@ mod beefy_tests { VerifyCircuit::::define(b); }; let writer = |input: &mut PI| { - input.evm_write::(header.into()); + input.evm_write::(header.hash().0.into()); for tx in txs { input.evm_write::(tx.into()); } @@ -268,7 +281,7 @@ mod beefy_tests { VerifyCircuit::::define(b); }; let writer = |input: &mut PI| { - input.write::(header.into()); + input.evm_write::(header.hash().0.into()); input.write::>(ids.into()); }; let assertions = |mut output: PO| { diff --git a/circuits/plonky2x/src/hint.rs b/circuits/plonky2x/src/hint.rs index a9166e1..b7d7d65 100644 --- a/circuits/plonky2x/src/hint.rs +++ b/circuits/plonky2x/src/hint.rs @@ -73,16 +73,20 @@ impl, const D: usize> AsyncHint for FetchHeaderInput } impl FetchHeaderInputs { + /// Fetches a header based on its known hash and witnesses the result. pub fn fetch, const D: usize>( self, b: &mut CircuitBuilder, - hash: &CryptoHashVariable, + trusted_hash: &CryptoHashVariable, ) -> HeaderVariable { let mut input_stream = VariableStream::new(); - input_stream.write::(hash); + input_stream.write::(trusted_hash); let output_stream = b.async_hint(input_stream, self); - output_stream.read::(b) + let untrusted = output_stream.read::(b); + let untrusted_hash = untrusted.hash(b); + b.assert_is_equal(*trusted_hash, untrusted_hash); + untrusted } } // TODO: refactor into some client-like carrier for all hints that is serdeable @@ -136,7 +140,7 @@ impl, const D: usize, const B: usize> AsyncHint assert_eq!(proofs.len(), B, "Invalid number of proofs"); - log::info!("Fetched {} proofs", proofs.len()); + log::debug!("Fetched {} proofs", proofs.len()); for (k, p) in proofs.into_iter() { output_stream.write_value::(k.0.into()); @@ -166,6 +170,11 @@ impl FetchProofInputs { proof: output_stream.read::(b), }); } + // Witness that each head block root in each proof is the same as the trusted + // head + inputs.iter().for_each(|x| { + b.assert_is_equal(x.proof.head_block_root, head.inner_lite.block_merkle_root) + }); inputs.into() } } diff --git a/circuits/plonky2x/src/merkle.rs b/circuits/plonky2x/src/merkle.rs index ddf38b7..7667292 100644 --- a/circuits/plonky2x/src/merkle.rs +++ b/circuits/plonky2x/src/merkle.rs @@ -3,7 +3,7 @@ use plonky2x::prelude::*; /// This is an unprefixed merkle tree without collision resistance, this should /// probably adapt the tendermint tree or introduce this functionality to -/// succintx's simple tree +/// succinct's simple tree pub trait NearMerkleTree { fn get_root_from_merkle_proof_hashed_leaf_unindex( &mut self, diff --git a/vendor/succinctx b/vendor/succinctx index 9698a57..b2540e2 160000 --- a/vendor/succinctx +++ b/vendor/succinctx @@ -1 +1 @@ -Subproject commit 9698a57ab5c6681eabc8dc105869581c4ce7d21b +Subproject commit b2540e29d54bcf97bc922d57b50acfe89665398a