diff --git a/.vscode/settings.json b/.vscode/settings.json index 794812452..232d73025 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,10 +14,10 @@ "rust-analyzer.linkedProjects": [ "./Cargo.toml", "./crates/distributor/Cargo.toml", - // "./crates/guest/assessor/assessor-guest/Cargo.toml", + "./crates/guest/assessor/assessor-guest/Cargo.toml", // "./crates/guest/util/echo/Cargo.toml", // "./crates/guest/util/identity/Cargo.toml", - // "./examples/counter/Cargo.toml", + "./examples/counter/Cargo.toml", // "./examples/smart-contract-requestor/Cargo.toml", // "./examples/counter-with-callback/Cargo.toml" ], diff --git a/Cargo.lock b/Cargo.lock index 3975818f0..1f8ecd2db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,6 +1043,7 @@ dependencies = [ "digest 0.10.7", "fnv", "merlin", + "rayon", "sha2 0.10.9", ] @@ -1075,6 +1076,7 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "rayon", "zeroize", ] @@ -1133,6 +1135,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "zeroize", ] @@ -1217,6 +1220,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", + "rayon", ] [[package]] @@ -1232,6 +1236,7 @@ dependencies = [ "educe", "fnv", "hashbrown 0.15.4", + "rayon", ] [[package]] @@ -1295,6 +1300,7 @@ dependencies = [ "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", + "rayon", ] [[package]] @@ -1348,6 +1354,7 @@ checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -2410,6 +2417,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -2465,11 +2485,23 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bce8d6acc5286a16e94c29e9c885d1869358885e08a6feeb6bc54e36fe20055" dependencies = [ - "duplicate", + "duplicate 1.0.0", "maybe-async", "reqwest", "serde", "thiserror 1.0.69", +] + +[[package]] +name = "bonsai-sdk" +version = "1.4.1" +source = "git+https://github.com/risc0/risc0?branch=flaub%2Fshrink-bitvm2#6ed3f0321638ae7eb9c097b8aa8becba3f4b1350" +dependencies = [ + "duplicate 2.0.0", + "maybe-async", + "reqwest", + "serde", + "thiserror 2.0.12", "tokio", ] @@ -2549,7 +2581,7 @@ dependencies = [ "alloy", "anyhow", "bincode", - "bonsai-sdk", + "bonsai-sdk 1.4.1", "boundless-assessor", "boundless-market", "boundless-market-test-utils", @@ -2569,8 +2601,10 @@ dependencies = [ "serde_json", "serde_yaml 0.9.34+deprecated", "shadow-rs", + "shrink_bitvm2", "sqlx", "tempfile", + "test-log", "tokio", "tracing", "tracing-subscriber 0.3.19", @@ -2680,6 +2714,7 @@ dependencies = [ "guest-assessor", "guest-set-builder", "guest-util", + "hex", "risc0-aggregation", "risc0-circuit-recursion", "risc0-ethereum-contracts", @@ -2737,13 +2772,19 @@ dependencies = [ "alloy", "alloy-chains", "anyhow", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", "async-channel 2.5.0", "async-trait", "aws-config", "aws-sdk-s3", "aws-smithy-http-client", "bincode", - "bonsai-sdk", + "blake3", + "bonsai-sdk 1.4.1", "boundless-assessor", "boundless-market", "boundless-market-test-utils", @@ -2759,6 +2800,8 @@ dependencies = [ "httpmock", "moka", "notify", + "num-bigint 0.4.6", + "num-traits", "proptest", "proptest-derive", "rand 0.9.1", @@ -2767,14 +2810,17 @@ dependencies = [ "reqwest-retry", "risc0-aggregation", "risc0-ethereum-contracts", + "risc0-groth16", "risc0-zkvm", "serde", "serde_json", "serial_test", "sha2 0.10.9", + "shrink_bitvm2", "sqlx", "temp-env", "tempfile", + "test-log", "thiserror 2.0.12", "tokio", "tokio-util", @@ -3179,6 +3225,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -3797,6 +3849,17 @@ dependencies = [ "proc-macro-error", ] +[[package]] +name = "duplicate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "proc-macro2-diagnostics", +] + [[package]] name = "dyn-clone" version = "1.0.19" @@ -7123,6 +7186,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.7.0" @@ -8059,8 +8135,6 @@ dependencies = [ [[package]] name = "risc0-ethereum-contracts" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3a6978f67c13fcf3e9123d28882368a3a3d686f881a35bb0caaebc585f93d6" dependencies = [ "alloy", "alloy-primitives 1.2.1", @@ -8192,7 +8266,7 @@ dependencies = [ "addr2line 0.22.0", "anyhow", "bincode", - "bonsai-sdk", + "bonsai-sdk 1.4.0", "borsh", "bytemuck", "bytes", @@ -9056,6 +9130,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shrink_bitvm2" +version = "0.14.0" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "blake3", + "hex", + "num-bigint 0.4.6", + "num-traits", + "risc0-groth16", + "risc0-zkvm", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" diff --git a/Cargo.toml b/Cargo.toml index 8c2814263..75d6b605b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "crates/indexer", "crates/ops-lambdas/indexer-monitor", "crates/order-generator", - "crates/order-stream", + "crates/order-stream", "crates/shrink_bitvm2", "crates/slasher", "documentation/doc-test", ] @@ -34,6 +34,7 @@ distributor = { path = "crates/distributor" } guest-assessor = { path = "crates/guest/assessor" } guest-util = { path = "crates/guest/util" } order-stream = { path = "crates/order-stream" } +shrink_bitvm2 = { path = "crates/shrink_bitvm2" } async-stream = "0.3" alloy = { version = "1.0.24", default-features = false } @@ -47,7 +48,7 @@ aws-sdk-s3 = "1.34" # used for minio for max compatibility axum = "0.8" axum-extra = { version = "0.10" } bincode = "1.3" -bonsai-sdk = { version = "1.4", features = ["non_blocking"] } +bonsai-sdk = { git = "https://github.com/risc0/risc0", branch = "flaub/shrink-bitvm2", features = ["non_blocking", "shrink_bitvm2"] } bs58 = "0.5" bytemuck = "1.16" clap = { version = "4.5", features = ["derive", "env"] } @@ -97,3 +98,6 @@ lto = true # [profile.release] # lto = "fat" # codegen-units = 1 + +[patch.crates-io] +risc0-ethereum-contracts = { path = "/home/etu/risc0-ethereum/contracts" } diff --git a/contracts/deployment-test/Deploymnet.t.sol b/contracts/deployment-test/Deploymnet.t.sol index c0cdfe485..ae23c38bf 100644 --- a/contracts/deployment-test/Deploymnet.t.sol +++ b/contracts/deployment-test/Deploymnet.t.sol @@ -22,7 +22,7 @@ import {Input, InputType} from "../src/types/Input.sol"; import {Requirements} from "../src/types/Requirements.sol"; import {Offer} from "../src/types/Offer.sol"; import {ProofRequest} from "../src/types/ProofRequest.sol"; -import {Predicate, PredicateType} from "../src/types/Predicate.sol"; +import {PredicateLibrary} from "../src/types/Predicate.sol"; import {RequestId, RequestIdLibrary} from "../src/types/RequestId.sol"; import {RequestLock} from "../src/types/RequestLock.sol"; @@ -105,7 +105,10 @@ contract DeploymentTest is Test { function testRouterIsDeployed() external view { require(address(verifier) != address(0), "no verifier (router) address is set"); require(keccak256(address(verifier).code) != keccak256(bytes("")), "verifier code is empty"); - require(address(verifier) == address(BoundlessMarket(address(boundlessMarket)).VERIFIER()), "verifier address does not match boundless market"); + require( + address(verifier) == address(BoundlessMarket(address(boundlessMarket)).VERIFIER()), + "verifier address does not match boundless market" + ); } function testSetVerifierIsDeployed() external view { @@ -191,9 +194,7 @@ contract DeploymentTest is Test { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, address(testProver), result.fills[0]); - boundlessMarket.priceAndFulfill( - requests, clientSignatures, result.fills, result.assessorReceipt - ); + boundlessMarket.priceAndFulfill(requests, clientSignatures, result.fills, result.assessorReceipt); Fulfillment memory fill = result.fills[0]; assertTrue(boundlessMarket.requestIsFulfilled(fill.id), "Request should have fulfilled status"); } @@ -230,8 +231,7 @@ contract Client { function defaultRequirements() public pure returns (Requirements memory) { return Requirements({ - imageId: bytes32(APP_IMAGE_ID), - predicate: Predicate({predicateType: PredicateType.PrefixMatch, data: hex"53797374"}), + predicate: PredicateLibrary.createPrefixMatchPredicate(bytes32(APP_IMAGE_ID), hex"53797374"), callback: Callback({gasLimit: 0, addr: address(0)}), selector: bytes4(0) }); diff --git a/contracts/scripts/Deploy.s.sol b/contracts/scripts/Deploy.s.sol index 0fec60a0c..1c5cecc0d 100644 --- a/contracts/scripts/Deploy.s.sol +++ b/contracts/scripts/Deploy.s.sol @@ -10,6 +10,7 @@ import "forge-std/Test.sol"; import {IRiscZeroSelectable} from "risc0/IRiscZeroSelectable.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; +import {RiscZeroBitvm2Groth16Verifier} from "risc0/bitvm/RiscZeroBitvm2Groth16Verifier.sol"; import {RiscZeroSetVerifier} from "risc0/RiscZeroSetVerifier.sol"; import {RiscZeroVerifierRouter} from "risc0/RiscZeroVerifierRouter.sol"; import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; @@ -66,6 +67,16 @@ contract Deploy is Script, RiscZeroCheats { IRiscZeroSelectable selectable = IRiscZeroSelectable(address(_verifier)); bytes4 selector = selectable.SELECTOR(); verifierRouter.addVerifier(selector, _verifier); + console2.log("Added Groth16 verifier to router with selector"); + console2.logBytes4(selector); + + + IRiscZeroVerifier _bitvm2_verifier = deployRiscZeroBitvm2Verifier(); + IRiscZeroSelectable bitvm2_selectable = IRiscZeroSelectable(address(_bitvm2_verifier)); + bytes4 bitvm_selector = bitvm2_selectable.SELECTOR(); + verifierRouter.addVerifier(bitvm_selector, _bitvm2_verifier); + console2.log("Added BitVM2 verifier to router with selector"); + console2.logBytes4(bitvm_selector); // TODO: Create a more robust way of getting a URI for guests, and ensure that it is // in-sync with the configured image ID. diff --git a/contracts/snapshots/BoundlessMarketBasicTest.json b/contracts/snapshots/BoundlessMarketBasicTest.json index b2d8ac978..e22aa98c7 100644 --- a/contracts/snapshots/BoundlessMarketBasicTest.json +++ b/contracts/snapshots/BoundlessMarketBasicTest.json @@ -1,42 +1,43 @@ { "ERC20 approve: required for depositStake": "45966", - "bytecode size implementation": "24381", + "bytecode size implementation": "23562", "bytecode size proxy": "89", - "deposit: first ever deposit": "50920", - "deposit: second deposit": "33820", - "depositStake: 1 HP (tops up market account)": "59400", - "depositStake: full (drains testProver account)": "49800", - "depositStakeWithPermit: 1 HP (tops up market account)": "72272", - "depositStakeWithPermit: full (drains testProver account)": "72262", - "fulfill: a batch of 8": "402078", - "fulfill: a locked request": "90511", - "fulfill: a locked request (locked via prover signature)": "90511", - "fulfill: a locked request with 10kB journal": "427539", - "fulfill: another prover fulfills without payment": "85473", - "fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)": "80772", - "fulfillAndWithdraw: a batch of 8": "414256", - "fulfillAndWithdraw: a locked request": "102689", - "lockinRequest: base case": "146877", - "lockinRequest: with prover signature": "156522", - "priceAndFulfill: a single request": "108760", - "priceAndFulfill: a single request (smart contract signature)": "118938", - "priceAndFulfill: a single request (with selector)": "111059", - "priceAndFulfill: a single request that was not locked": "108760", - "priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "108760", - "priceAndFulfill: fulfill already fulfilled was locked request": "102475", - "slash: base case": "100967", - "slash: fulfilled request after lock deadline": "80532", - "submitRequest: with maxPrice ether": "51904", - "submitRequest: without ether": "45061", - "submitRootAndFulfill: a batch of 2 requests": "168676", - "submitRootAndFulfill: a locked request": "124555", - "submitRootAndFulfill: a locked request (locked via prover signature)": "124555", - "submitRootAndFulfillAndWithdraw: a locked request": "136476", - "submitRootAndPriceAndFulfill: a single request": "141098", - "submitRootAndPriceAndFulfill: a single request that was not locked": "141110", - "submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "141110", - "withdraw: 1 ether": "40314", - "withdraw: full balance": "40326", + "deposit: first ever deposit": "50832", + "deposit: second deposit": "33732", + "depositStake: 1 HP (tops up market account)": "59334", + "depositStake: full (drains testProver account)": "49734", + "depositStakeWithPermit: 1 HP (tops up market account)": "72254", + "depositStakeWithPermit: full (drains testProver account)": "72244", + "fulfill (no journal): a batch of 8": "382896", + "fulfill: a batch of 8": "411904", + "fulfill: a locked request": "91719", + "fulfill: a locked request (locked via prover signature)": "91719", + "fulfill: a locked request with 10kB journal": "424337", + "fulfill: another prover fulfills without payment": "86681", + "fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)": "80587", + "fulfillAndWithdraw: a batch of 8": "423818", + "fulfillAndWithdraw: a locked request": "103633", + "lockinRequest: base case": "146972", + "lockinRequest: with prover signature": "156614", + "priceAndFulfill: a single request": "109593", + "priceAndFulfill: a single request (smart contract signature)": "119783", + "priceAndFulfill: a single request (with selector)": "111891", + "priceAndFulfill: a single request that was not locked": "109593", + "priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "109593", + "priceAndFulfill: fulfill already fulfilled was locked request": "101914", + "slash: base case": "100945", + "slash: fulfilled request after lock deadline": "80510", + "submitRequest: with maxPrice ether": "52637", + "submitRequest: without ether": "45794", + "submitRootAndFulfill: a batch of 2 requests": "170677", + "submitRootAndFulfill: a locked request": "126392", + "submitRootAndFulfill: a locked request (locked via prover signature)": "126392", + "submitRootAndFulfillAndWithdraw: a locked request": "137741", + "submitRootAndPriceAndFulfill: a single request": "142867", + "submitRootAndPriceAndFulfill: a single request that was not locked": "142867", + "submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "142867", + "withdraw: 1 ether": "40336", + "withdraw: full balance": "40348", "withdrawStake: 1 HP balance": "68743", "withdrawStake: full balance": "51739" } \ No newline at end of file diff --git a/contracts/snapshots/BoundlessMarketBench.json b/contracts/snapshots/BoundlessMarketBench.json index e7220706c..e0a6eb16a 100644 --- a/contracts/snapshots/BoundlessMarketBench.json +++ b/contracts/snapshots/BoundlessMarketBench.json @@ -1,22 +1,22 @@ { - "fulfill (with callback): batch of 001": "132142", - "fulfill (with callback): batch of 002": "218443", - "fulfill (with callback): batch of 004": "392147", - "fulfill (with callback): batch of 008": "739579", - "fulfill (with callback): batch of 016": "1272464", - "fulfill (with callback): batch of 032": "2373148", - "fulfill (with selector): batch of 001": "92745", - "fulfill (with selector): batch of 002": "139726", - "fulfill (with selector): batch of 004": "236258", - "fulfill (with selector): batch of 008": "420231", - "fulfill (with selector): batch of 016": "790425", - "fulfill (with selector): batch of 032": "1558877", - "fulfill: batch of 001": "90487", - "fulfill: batch of 002": "135232", - "fulfill: batch of 004": "227181", - "fulfill: batch of 008": "402117", - "fulfill: batch of 016": "754179", - "fulfill: batch of 032": "1485129", - "fulfill: batch of 064": "3014348", - "fulfill: batch of 128": "6255291" + "fulfill (with callback): batch of 001": "133209", + "fulfill (with callback): batch of 002": "220644", + "fulfill (with callback): batch of 004": "396534", + "fulfill (with callback): batch of 008": "748185", + "fulfill (with callback): batch of 016": "1288456", + "fulfill (with callback): batch of 032": "2401400", + "fulfill (with selector): batch of 001": "93954", + "fulfill (with selector): batch of 002": "142226", + "fulfill (with selector): batch of 004": "241198", + "fulfill (with selector): batch of 008": "430013", + "fulfill (with selector): batch of 016": "809193", + "fulfill (with selector): batch of 032": "1591730", + "fulfill: batch of 001": "91707", + "fulfill: batch of 002": "137716", + "fulfill: batch of 004": "232231", + "fulfill: batch of 008": "411955", + "fulfill: batch of 016": "772671", + "fulfill: batch of 032": "1518851", + "fulfill: batch of 064": "3065787", + "fulfill: batch of 128": "6295955" } \ No newline at end of file diff --git a/contracts/src/BoundlessMarket.sol b/contracts/src/BoundlessMarket.sol index fe4f4e729..a978303cc 100644 --- a/contracts/src/BoundlessMarket.sol +++ b/contracts/src/BoundlessMarket.sol @@ -24,8 +24,11 @@ import {Account} from "./types/Account.sol"; import {AssessorJournal} from "./types/AssessorJournal.sol"; import {AssessorCallback} from "./types/AssessorCallback.sol"; import {AssessorCommitment} from "./types/AssessorCommitment.sol"; +import {FulfillmentData} from "./types/FulfillmentData.sol"; import {Fulfillment} from "./types/Fulfillment.sol"; +import {FulfillmentData, FulfillmentDataLibrary, FulfillmentDataType} from "./types/FulfillmentData.sol"; import {AssessorReceipt} from "./types/AssessorReceipt.sol"; +import {PredicateType} from "./types/Predicate.sol"; import {ProofRequest} from "./types/ProofRequest.sol"; import {LockRequest, LockRequestLibrary} from "./types/LockRequest.sol"; import {RequestId} from "./types/RequestId.sol"; @@ -255,16 +258,14 @@ contract BoundlessMarket is // Verify the application receipts. for (uint256 i = 0; i < fills.length; i++) { Fulfillment calldata fill = fills[i]; - - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); - leaves[i] = AssessorCommitment(i, fill.id, fill.requestDigest, claimDigest).eip712Digest(); + leaves[i] = AssessorCommitment(i, fill.id, fill.requestDigest, fill.claimDigest).eip712Digest(); // If the requestor did not specify a selector, we verify with DEFAULT_MAX_GAS_FOR_VERIFY gas limit. // This ensures that by default, client receive proofs that can be verified cheaply as part of their applications. if (!hasSelector[i]) { - VERIFIER.verifyIntegrity{gas: DEFAULT_MAX_GAS_FOR_VERIFY}(Receipt(fill.seal, claimDigest)); + VERIFIER.verifyIntegrity{gas: DEFAULT_MAX_GAS_FOR_VERIFY}(Receipt(fill.seal, fill.claimDigest)); } else { - VERIFIER.verifyIntegrity(Receipt(fill.seal, claimDigest)); + VERIFIER.verifyIntegrity(Receipt(fill.seal, fill.claimDigest)); } } @@ -333,9 +334,26 @@ contract BoundlessMarket is } uint256 callbackIndexPlusOne = fillToCallbackIndexPlusOne[i]; - if (callbackIndexPlusOne > 0) { - AssessorCallback calldata callback = assessorReceipt.callbacks[callbackIndexPlusOne - 1]; - _executeCallback(fill.id, callback.addr, callback.gasLimit, fill.imageId, fill.journal, fill.seal); + + if (fill.fulfillmentDataType == FulfillmentDataType.ImageIdAndJournal) { + (bytes32 imageId, bytes calldata journal) = + FulfillmentDataLibrary.decodeFulfillmentData(fill.fulfillmentData); + // We should only get to this point if the journal has been authenticated because the claimDigest + // passed the integrity check in verifyDelivery and we just recalculated it from the imageId and journal + // and compared to make sure they match. + // That is to say, if the claimDigest isn't a Risc0 zkvm claimDigest (e.g. blake3), journals + // cannot be authenticated and therefore we cannot use them in a callback and we revert with ClaimDigestMismatch. + if (callbackIndexPlusOne > 0) { + AssessorCallback calldata callback = assessorReceipt.callbacks[callbackIndexPlusOne - 1]; + _executeCallback(fill.id, callback.addr, callback.gasLimit, imageId, journal, fill.seal); + } + } else if (fill.fulfillmentDataType == FulfillmentDataType.None) { + // A callback was requested, but it cannot be fulfilled, so revert. + if (callbackIndexPlusOne > 0) { + revert UnfulfillableCallback(); + } + } else { + revert UnsupportedFulfillmentData(); } } } diff --git a/contracts/src/IBoundlessMarket.sol b/contracts/src/IBoundlessMarket.sol index 6e037c243..6532d2125 100644 --- a/contracts/src/IBoundlessMarket.sol +++ b/contracts/src/IBoundlessMarket.sol @@ -165,6 +165,14 @@ interface IBoundlessMarket { /// @dev selector efc954a6 error BatchSizeExceedsLimit(uint256 batchSize, uint256 limit); + /// @notice Error when the fulfillment data type is not supported + /// TODO(ec2): selector + error UnsupportedFulfillmentData(); + + /// @notice Error when the fulfillment has a unfulfillable callback + /// TODO(ec2): selector + error UnfulfillableCallback(); + /// @notice Check if the given request has been locked (i.e. accepted) by a prover. /// @dev When a request is locked, only the prover it is locked to can be paid to fulfill the job. /// @param requestId The ID of the request. diff --git a/contracts/src/types/AssessorJournal.sol b/contracts/src/types/AssessorJournal.sol index dfa3920f2..2cc9ffdf8 100644 --- a/contracts/src/types/AssessorJournal.sol +++ b/contracts/src/types/AssessorJournal.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {AssessorCallback} from "./AssessorCallback.sol"; +import {PredicateType} from "./Predicate.sol"; import {Selector} from "./Selector.sol"; /// @title Assessor Journal Struct diff --git a/contracts/src/types/Fulfillment.sol b/contracts/src/types/Fulfillment.sol index f21eee4ec..442d1c7e4 100644 --- a/contracts/src/types/Fulfillment.sol +++ b/contracts/src/types/Fulfillment.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {RequestId} from "./RequestId.sol"; +import {FulfillmentDataType} from "./FulfillmentData.sol"; using FulfillmentLibrary for Fulfillment global; @@ -15,16 +16,12 @@ struct Fulfillment { RequestId id; /// @notice EIP-712 digest of request struct. bytes32 requestDigest; - /// @notice Image ID of the guest that was verifiably executed to satisfy the request. - /// @dev Must match the value in the request's requirements. - bytes32 imageId; - // TODO: Add a flag in the request to decide whether to post the journal. Note that - // if the journal and journal digest do not need to be delivered to the client, imageId will - // be replaced with claim digest, since it is captured in the requirements on the request, - // checked by the Assessor guest. - /// @notice Journal committed by the guest program execution. - /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. - bytes journal; + /// @notice Claim Digest + bytes32 claimDigest; + /// @notice The type of data included in the fulfillment + FulfillmentDataType fulfillmentDataType; + /// @notice The fulfillment data + bytes fulfillmentData; /// @notice Cryptographic proof for the validity of the execution results. /// @dev This will be sent to the `IRiscZeroVerifier` associated with this contract. bytes seal; diff --git a/contracts/src/types/FulfillmentData.sol b/contracts/src/types/FulfillmentData.sol new file mode 100644 index 000000000..00ee97d06 --- /dev/null +++ b/contracts/src/types/FulfillmentData.sol @@ -0,0 +1,69 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.24; + +using FulfillmentDataLibrary for FulfillmentData global; + +enum FulfillmentDataType { + None, + ImageIdAndJournal +} + +/// @title FulfillmentData Struct and Library +/// @notice Represents a fulfillment configuration for proof delivery +struct FulfillmentData { + /// @notice Image ID of the guest that was verifiably executed to satisfy the request. + bytes32 imageId; + /// @notice Journal committed by the guest program execution. + /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. + bytes journal; +} + +library FulfillmentDataLibrary { + /// @notice Decodes a bytes calldata into a FulfillmentData struct. + /// @param data The bytes calldata to decode. + /// @return fillData The decoded FulfillmentData struct. + function decode(bytes calldata data) public pure returns (FulfillmentData memory fillData) { + bytes32 imageId; + bytes calldata journal; + assembly { + imageId := calldataload(add(data.offset, 0x20)) + let journalOffset := calldataload(add(data.offset, 0x40)) + let journalPtr := add(data.offset, add(0x20, journalOffset)) + let journalLength := calldataload(journalPtr) + journal.offset := add(journalPtr, 0x20) + journal.length := journalLength + } + fillData = FulfillmentData(imageId, journal); + } + + /// @notice Decodes the fulfillment data from a bytes calldata. + /// @param data The bytes calldata to decode. + /// @return imageId The decoded image ID. + /// @return journal The decoded journal. + function decodeFulfillmentData(bytes calldata data) + internal + pure + returns (bytes32 imageId, bytes calldata journal) + { + assembly { + // Extract imageId (first 32 bytes after length) + imageId := calldataload(add(data.offset, 0x20)) + // Extract journal offset and create calldata slice + let journalOffset := calldataload(add(data.offset, 0x40)) + let journalPtr := add(data.offset, add(0x20, journalOffset)) + let journalLength := calldataload(journalPtr) + journal.offset := add(journalPtr, 0x20) + journal.length := journalLength + } + } + + /// @notice Encodes the fulfillment data into bytes. + /// @param fillData The FulfillmentData struct to encode. + /// @return The encoded bytes. + function encode(FulfillmentData memory fillData) public pure returns (bytes memory) { + return abi.encode(fillData); + } +} diff --git a/contracts/src/types/Predicate.sol b/contracts/src/types/Predicate.sol index 255faab1d..e6ce08fd7 100644 --- a/contracts/src/types/Predicate.sol +++ b/contracts/src/types/Predicate.sol @@ -5,10 +5,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; + using PredicateLibrary for Predicate global; +using ReceiptClaimLib for ReceiptClaim; /// @title Predicate Struct and Library -/// @notice Represents a predicate and provides functions to create and evaluate predicates. +/// @notice A predicate is a function over the claim that determines whether it meets the clients requirements. +/// The data field is used to store the specific data associated with the predicate. +/// - DigestMatch: (bytes32, bytes32) -> abi.encodePacked(imageId, journalHash) +/// - PrefixMatch: (bytes32, bytes) -> abi.encodePacked(imageId, prefix) +/// - ClaimDigestMatch: (bytes32) -> abi.encode(claimDigest) struct Predicate { PredicateType predicateType; bytes data; @@ -16,7 +23,8 @@ struct Predicate { enum PredicateType { DigestMatch, - PrefixMatch + PrefixMatch, + ClaimDigestMatch } library PredicateLibrary { @@ -26,15 +34,26 @@ library PredicateLibrary { /// @notice Creates a digest match predicate. /// @param hash The hash to match. /// @return A Predicate struct with type DigestMatch and the provided hash. - function createDigestMatchPredicate(bytes32 hash) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(hash)}); + function createDigestMatchPredicate(bytes32 imageId, bytes32 hash) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encodePacked(imageId, hash)}); } /// @notice Creates a prefix match predicate. /// @param prefix The prefix to match. /// @return A Predicate struct with type PrefixMatch and the provided prefix. - function createPrefixMatchPredicate(bytes memory prefix) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.PrefixMatch, data: prefix}); + function createPrefixMatchPredicate(bytes32 imageId, bytes memory prefix) + internal + pure + returns (Predicate memory) + { + return Predicate({predicateType: PredicateType.PrefixMatch, data: abi.encodePacked(imageId, prefix)}); + } + + /// @notice Creates a claim digest match predicate. + /// @param claimDigest The claimDigest to match. + /// @return A Predicate struct with type ClaimDigestMatch and the provided claimDigest. + function createClaimDigestMatchPredicate(bytes32 claimDigest) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)}); } /// @notice Evaluates the predicate against the given journal and journal digest. @@ -42,15 +61,19 @@ library PredicateLibrary { /// @param journal The journal to evaluate against. /// @param journalDigest The digest of the journal. /// @return True if the predicate is satisfied, false otherwise. - function eval(Predicate memory predicate, bytes memory journal, bytes32 journalDigest) + function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal, bytes32 journalDigest) internal pure returns (bool) { if (predicate.predicateType == PredicateType.DigestMatch) { - return bytes32(predicate.data) == journalDigest; + bytes memory dataJournal = _sliceToEnd(predicate.data, 32); + return bytes32(dataJournal) == journalDigest; } else if (predicate.predicateType == PredicateType.PrefixMatch) { - return startsWith(journal, predicate.data); + bytes memory dataJournal = _sliceToEnd(predicate.data, 32); + return startsWith(journal, dataJournal); + } else if (predicate.predicateType == PredicateType.ClaimDigestMatch) { + return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest(); } else { revert("Unreachable code"); } @@ -83,3 +106,17 @@ library PredicateLibrary { return keccak256(abi.encode(PREDICATE_TYPEHASH, predicate.predicateType, keccak256(predicate.data))); } } + +/// Taken from Openzepplin util Bytes.sol +function _sliceToEnd(bytes memory buffer, uint256 start) pure returns (bytes memory) { + // sanitize + uint256 end = buffer.length; + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; +} diff --git a/contracts/src/types/Requirements.sol b/contracts/src/types/Requirements.sol index 79e032d76..99a84f60d 100644 --- a/contracts/src/types/Requirements.sol +++ b/contracts/src/types/Requirements.sol @@ -10,15 +10,13 @@ import {Callback, CallbackLibrary} from "./Callback.sol"; using RequirementsLibrary for Requirements global; struct Requirements { - bytes32 imageId; Callback callback; Predicate predicate; bytes4 selector; } library RequirementsLibrary { - string constant REQUIREMENTS_TYPE = - "Requirements(bytes32 imageId,Callback callback,Predicate predicate,bytes4 selector)"; + string constant REQUIREMENTS_TYPE = "Requirements(Callback callback,Predicate predicate,bytes4 selector)"; bytes32 constant REQUIREMENTS_TYPEHASH = keccak256(abi.encodePacked(REQUIREMENTS_TYPE, CallbackLibrary.CALLBACK_TYPE, PredicateLibrary.PREDICATE_TYPE)); @@ -29,7 +27,6 @@ library RequirementsLibrary { return keccak256( abi.encode( REQUIREMENTS_TYPEHASH, - requirements.imageId, CallbackLibrary.eip712Digest(requirements.callback), PredicateLibrary.eip712Digest(requirements.predicate), requirements.selector diff --git a/contracts/test/BoundlessMarket.t.sol b/contracts/test/BoundlessMarket.t.sol index daf28dc32..328391692 100644 --- a/contracts/test/BoundlessMarket.t.sol +++ b/contracts/test/BoundlessMarket.t.sol @@ -19,7 +19,7 @@ import { ReceiptClaimLib, VerificationFailed } from "risc0/IRiscZeroVerifier.sol"; -import {TestReceipt} from "risc0/../test/TestReceipt.sol"; +import {TestReceipt} from "risc0/../test/TestReceiptV2_1.sol"; import {RiscZeroMockVerifier} from "risc0/test/RiscZeroMockVerifier.sol"; import {TestUtils} from "./TestUtils.sol"; import {Client} from "./clients/Client.sol"; @@ -34,6 +34,7 @@ import {HitPoints} from "../src/HitPoints.sol"; import {BoundlessMarket} from "../src/BoundlessMarket.sol"; import {Callback} from "../src/types/Callback.sol"; +import {FulfillmentData, FulfillmentDataLibrary} from "../src/types/FulfillmentData.sol"; import {RequestId, RequestIdLibrary} from "../src/types/RequestId.sol"; import {AssessorJournal} from "../src/types/AssessorJournal.sol"; import {AssessorCallback} from "../src/types/AssessorCallback.sol"; @@ -45,11 +46,12 @@ import {LockRequest} from "../src/types/LockRequest.sol"; import {Account} from "../src/types/Account.sol"; import {RequestLock} from "../src/types/RequestLock.sol"; import {Fulfillment} from "../src/types/Fulfillment.sol"; +import {FulfillmentDataType} from "../src/types/FulfillmentData.sol"; import {AssessorReceipt} from "../src/types/AssessorReceipt.sol"; import {AssessorJournal} from "../src/types/AssessorJournal.sol"; import {Offer} from "../src/types/Offer.sol"; import {Requirements} from "../src/types/Requirements.sol"; -import {Predicate, PredicateType} from "../src/types/Predicate.sol"; +import {Predicate, PredicateLibrary, PredicateType} from "../src/types/Predicate.sol"; import {Input, InputType} from "../src/types/Input.sol"; import {IBoundlessMarket} from "../src/IBoundlessMarket.sol"; @@ -371,19 +373,35 @@ contract BoundlessMarketTest is Test { view returns (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt, bytes32 root) { - // initialize the fullfillments; one for each request; + // initialize the fulfillments; one for each request; // the seal is filled in later, by calling fillInclusionProof fills = new Fulfillment[](requests.length); Selector[] memory selectors = new Selector[](0); AssessorCallback[] memory callbacks = new AssessorCallback[](0); + for (uint8 i = 0; i < requests.length; i++) { + bytes32 claimDigest; + FulfillmentDataType fillType; + bytes memory fulfillmentData; + bytes memory journal = journals[i]; + PredicateType predicateType = requests[i].requirements.predicate.predicateType; + bytes32 imageId = bytesToBytes32(requests[i].requirements.predicate.data); + if (predicateType != PredicateType.ClaimDigestMatch) { + claimDigest = ReceiptClaimLib.ok(imageId, sha256(journal)).digest(); + fulfillmentData = abi.encode(FulfillmentData({imageId: imageId, journal: journal})); + fillType = FulfillmentDataType.ImageIdAndJournal; + } else { + claimDigest = bytesToBytes32(requests[i].requirements.predicate.data); + fillType = FulfillmentDataType.None; + } Fulfillment memory fill = Fulfillment({ id: requests[i].id, requestDigest: MessageHashUtils.toTypedDataHash( boundlessMarket.eip712DomainSeparator(), requests[i].eip712Digest() ), - imageId: requests[i].requirements.imageId, - journal: journals[i], + claimDigest: claimDigest, + fulfillmentData: fulfillmentData, + fulfillmentDataType: fillType, seal: bytes("") }); fills[i] = fill; @@ -482,6 +500,14 @@ contract BoundlessMarketTest is Test { journals[i] = APP_JOURNAL; } } + + function bytesToBytes32(bytes memory b) internal pure returns (bytes32) { + bytes32 out; + for (uint256 i = 0; i < 32; i++) { + out |= bytes32(b[i] & 0xFF) >> (i * 8); + } + return out; + } } contract BoundlessMarketBasicTest is BoundlessMarketTest { @@ -845,7 +871,7 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // the way it is hashed for signatures. Find a good way to avoid this. vm.expectRevert( abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x72C929E83beDC7370921131d8BF11B50d656aCE5) + IBoundlessMarket.InsufficientBalance.selector, address(0xc62E3b806D3750d2C89fF568e3c9A9D8E6D2619A) ) ); boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); @@ -869,7 +895,7 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // the way it is hashed for signatures. Find a good way to avoid this. vm.expectRevert( abi.encodeWithSelector( - IBoundlessMarket.InsufficientBalance.selector, address(0x73F8229890F1F0120B8786926fb44F0656b9416D) + IBoundlessMarket.InsufficientBalance.selector, address(0x2342A914306E62d082692F86bc79DcFf8729fa99) ) ); boundlessMarket.lockRequestWithSignature(request, clientSignature, badProverSignature); @@ -2466,6 +2492,66 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { expectMarketBalanceUnchanged(); } + // Fulfill a batch of locked ClaimDigestMatch requests with no journal + function testFulfillLockedRequestsNoJournal() public { + // Provide a batch definition as an array of clients and how many requests each submits. + uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; + uint256 batchSize = 0; + for (uint256 i = 0; i < batch.length; i++) { + batchSize += batch[i]; + } + ProofRequest[] memory requests = new ProofRequest[](batchSize); + bytes[] memory journals = new bytes[](batchSize); + uint256 expectedRevenue = 0; + uint256 idx = 0; + + for (uint256 i = 0; i < batch.length; i++) { + Client client = getClient(i); + + for (uint256 j = 0; j < batch[i]; j++) { + ProofRequest memory request = client.request(uint32(j)); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + + request.requirements.predicate = Predicate({ + predicateType: PredicateType.ClaimDigestMatch, + data: abi.encode(ReceiptClaimLib.ok(imageId, sha256(APP_JOURNAL)).digest()) + }); + + // TODO: This is a fragile part of this test. It should be improved. + uint256 desiredPrice = uint256(1.5 ether); + vm.warp(request.offer.timeAtPrice(desiredPrice)); + expectedRevenue += desiredPrice; + + boundlessMarket.lockRequestWithSignature( + request, client.sign(request), testProver.signLockRequest(LockRequest({request: request})) + ); + + requests[idx] = request; + journals[idx] = APP_JOURNAL; + idx++; + } + } + + (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = + createFillsAndSubmitRoot(requests, journals, testProverAddress); + + for (uint256 i = 0; i < fills.length; i++) { + vm.expectEmit(true, true, true, true); + emit IBoundlessMarket.RequestFulfilled(fills[i].id, testProverAddress, fills[i]); + vm.expectEmit(true, true, true, false); + emit IBoundlessMarket.ProofDelivered(fills[i].id, testProverAddress, fills[i]); + } + boundlessMarket.fulfill(fills, assessorReceipt); + vm.snapshotGasLastCall(string.concat("fulfill (no journal): a batch of ", vm.toString(batchSize))); + for (uint256 i = 0; i < fills.length; i++) { + // Check that the proof was submitted + expectRequestFulfilled(fills[i].id); + } + + testProver.expectBalanceChange(int256(uint256(expectedRevenue))); + expectMarketBalanceUnchanged(); + } + // Testing that reordering request IDs in a batch will cause the fulfill to revert. function testFulfillShuffleIds() public { uint256[5] memory batch = [uint256(1), 2, 1, 3, 1]; @@ -2523,9 +2609,9 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { // Second request client = getClient(1); request = client.request(uint32(1)); + request.requirements = Requirements({ - imageId: bytes32(APP_IMAGE_ID_2), - predicate: Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(sha256(APP_JOURNAL_2))}), + predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID_2), sha256(APP_JOURNAL_2)), selector: bytes4(0), callback: Callback({addr: address(0), gasLimit: 0}) }); @@ -2538,14 +2624,14 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { (Fulfillment[] memory fills, AssessorReceipt memory assessorReceipt) = createFillsAndSubmitRoot(requests, journals, testProverAddress); - bytes32 imageId0 = fills[0].imageId; - bytes memory journal0 = fills[0].journal; + bytes memory fulfillmentData0 = fills[0].fulfillmentData; + bytes32 claimDigest0 = fills[0].claimDigest; - fills[0].imageId = fills[1].imageId; - fills[1].imageId = imageId0; + fills[0].fulfillmentData = fills[1].fulfillmentData; + fills[1].fulfillmentData = fulfillmentData0; - fills[0].journal = fills[1].journal; - fills[1].journal = journal0; + fills[0].claimDigest = fills[1].claimDigest; + fills[1].claimDigest = claimDigest0; vm.expectRevert(VerificationFailed.selector); boundlessMarket.fulfill(fills, assessorReceipt); @@ -2842,7 +2928,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { bytes[] memory clientSignatures = new bytes[](1); clientSignatures[0] = clientSignature; - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); + FulfillmentData memory fulfillmentData = FulfillmentDataLibrary.decode(fill.fulfillmentData); + bytes32 claimDigest = ReceiptClaimLib.ok(fulfillmentData.imageId, sha256(fulfillmentData.journal)).digest(); // If no selector is specified, we expect the call to verifyIntegrity to use the default // gas limit when verifying the application. @@ -2877,7 +2964,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { bytes[] memory clientSignatures = new bytes[](1); clientSignatures[0] = clientSignature; - bytes32 claimDigest = ReceiptClaimLib.ok(fill.imageId, sha256(fill.journal)).digest(); + FulfillmentData memory fulfillmentData = FulfillmentDataLibrary.decode(fill.fulfillmentData); + bytes32 claimDigest = ReceiptClaimLib.ok(fulfillmentData.imageId, sha256(fulfillmentData.journal)).digest(); // If a selector is specified, we expect the call to verifyIntegrity to not use the default // gas limit, so the minimum gas it should have should exceed it. @@ -3438,7 +3526,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, true); emit IBoundlessMarket.ProofDelivered(request.id, testProverAddress, fill); vm.expectEmit(true, true, true, false); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); boundlessMarket.fulfill(fills, assessorReceipt); // Verify callback was called exactly once @@ -3520,7 +3609,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); vm.prank(otherProverAddress); boundlessMarket.fulfill(fills, assessorReceipt); @@ -3566,7 +3656,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); boundlessMarket.fulfill(fills, assessorReceipt); // Verify callback was called exactly once @@ -3637,7 +3728,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(request.id, otherProver.addr(), fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(request.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(request.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); // Verify callback was called exactly once @@ -3712,7 +3804,8 @@ contract BoundlessMarketBasicTest is BoundlessMarketTest { vm.expectEmit(true, true, true, false); emit IBoundlessMarket.ProofDelivered(requestB.id, testProverAddress, fill); vm.expectEmit(true, true, true, true); - emit MockCallback.MockCallbackCalled(requestB.requirements.imageId, APP_JOURNAL, fill.seal); + bytes32 imageId = bytesToBytes32(requestB.requirements.predicate.data); + emit MockCallback.MockCallbackCalled(imageId, APP_JOURNAL, fill.seal); boundlessMarket.priceAndFulfill(requests, clientSignatures, fills, assessorReceipt); // Verify only the second request's callback was called diff --git a/contracts/test/TestUtils.sol b/contracts/test/TestUtils.sol index 42be58d03..74424530f 100644 --- a/contracts/test/TestUtils.sol +++ b/contracts/test/TestUtils.sol @@ -26,10 +26,11 @@ library TestUtils { address prover ) internal pure returns (ReceiptClaim memory) { bytes32[] memory leaves = new bytes32[](fills.length); + for (uint256 i = 0; i < fills.length; i++) { - bytes32 claimDigest = ReceiptClaimLib.ok(fills[i].imageId, sha256(fills[i].journal)).digest(); - leaves[i] = AssessorCommitment(i, fills[i].id, fills[i].requestDigest, claimDigest).eip712Digest(); + leaves[i] = AssessorCommitment(i, fills[i].id, fills[i].requestDigest, fills[i].claimDigest).eip712Digest(); } + bytes32 root = MerkleProofish.processTree(leaves); bytes memory journal = @@ -54,7 +55,7 @@ library TestUtils { { bytes32[] memory claimDigests = new bytes32[](fills.length); for (uint256 i = 0; i < fills.length; i++) { - claimDigests[i] = ReceiptClaimLib.ok(fills[i].imageId, sha256(fills[i].journal)).digest(); + claimDigests[i] = fills[i].claimDigest; } // compute the merkle tree of the batch (batchRoot, tree) = computeMerkleTree(claimDigests); diff --git a/contracts/test/clients/BaseClient.sol b/contracts/test/clients/BaseClient.sol index 51a18d741..b39790fec 100644 --- a/contracts/test/clients/BaseClient.sol +++ b/contracts/test/clients/BaseClient.sol @@ -21,10 +21,11 @@ import {Account} from "../../src/types/Account.sol"; import {RequestLock} from "../../src/types/RequestLock.sol"; import {LockRequest} from "../../src/types/LockRequest.sol"; import {Fulfillment} from "../../src/types/Fulfillment.sol"; +import {FulfillmentDataType} from "../../src/types/FulfillmentData.sol"; import {AssessorJournal} from "../../src/types/AssessorJournal.sol"; import {Offer} from "../../src/types/Offer.sol"; import {Requirements} from "../../src/types/Requirements.sol"; -import {Predicate, PredicateType} from "../../src/types/Predicate.sol"; +import {PredicateLibrary, PredicateType} from "../../src/types/Predicate.sol"; import {Input, InputType} from "../../src/types/Input.sol"; import {IBoundlessMarket} from "../../src/IBoundlessMarket.sol"; @@ -78,8 +79,7 @@ abstract contract BaseClient { function defaultRequirements() public pure returns (Requirements memory) { return Requirements({ - imageId: bytes32(APP_IMAGE_ID), - predicate: Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(sha256(APP_JOURNAL))}), + predicate: PredicateLibrary.createDigestMatchPredicate(bytes32(APP_IMAGE_ID), sha256(APP_JOURNAL)), selector: bytes4(0), callback: Callback({addr: address(0), gasLimit: 0}) }); diff --git a/contracts/test/types/Predicate.t.sol b/contracts/test/types/Predicate.t.sol index 024a23825..a1a5b1c2f 100644 --- a/contracts/test/types/Predicate.t.sol +++ b/contracts/test/types/Predicate.t.sol @@ -6,12 +6,17 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; import {Predicate, PredicateLibrary, PredicateType} from "../../src/types/Predicate.sol"; +bytes32 constant IMAGE_ID = keccak256("ImageId for testing purposes"); + contract PredicateTest is Test { + using ReceiptClaimLib for ReceiptClaim; + function testEvalDigestMatch() public pure { bytes32 hash = keccak256("test"); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(hash); + Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); assertEq( uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" ); @@ -19,13 +24,13 @@ contract PredicateTest is Test { bytes memory journal = "test"; bytes32 journalDigest = keccak256(journal); - bool result = predicate.eval(journal, journalDigest); + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); assertTrue(result, "Predicate evaluation should be true for matching digest"); } function testEvalDigestMatchFail() public pure { bytes32 hash = keccak256("test"); - Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(hash); + Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash); assertEq( uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch" ); @@ -33,25 +38,58 @@ contract PredicateTest is Test { bytes memory journal = "different test"; bytes32 journalDigest = keccak256(journal); - bool result = predicate.eval(journal, journalDigest); + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); assertFalse(result, "Predicate evaluation should be false for non-matching digest"); } function testEvalPrefixMatch() public pure { bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(prefix); + Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); bytes memory journal = "prefix and more"; - bool result = predicate.eval(journal, keccak256(journal)); + bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal)); assertTrue(result, "Predicate evaluation should be true for matching prefix"); } function testEvalPrefixMatchFail() public pure { bytes memory prefix = "prefix"; - Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(prefix); + Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix); bytes memory journal = "different prefix"; - bool result = predicate.eval(journal, keccak256(journal)); + bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal)); assertFalse(result, "Predicate evaluation should be false for non-matching prefix"); } + + function testEvalClaimDigestMatch() public pure { + bytes memory journal = "test"; + bytes32 journalDigest = keccak256(journal); + bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest(); + Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); + assertEq( + uint8(predicate.predicateType), + uint8(PredicateType.ClaimDigestMatch), + "Predicate type should be ClaimDigestMatch" + ); + + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); + assertTrue(result, "Predicate evaluation should be true for matching digest"); + } + + function testEvalClaimDigestMatchFail() public pure { + bytes memory journal = "test"; + bytes32 journalDigest = keccak256(journal); + bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest(); + Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest); + assertEq( + uint8(predicate.predicateType), + uint8(PredicateType.ClaimDigestMatch), + "Predicate type should be ClaimDigestMatch" + ); + + journal = "different test"; + journalDigest = keccak256(journal); + + bool result = predicate.eval(IMAGE_ID, journal, journalDigest); + assertFalse(result, "Predicate evaluation should be false for non-matching digest"); + } } diff --git a/contracts/test/types/ProofRequest.t.sol b/contracts/test/types/ProofRequest.t.sol index e1b9aeb10..42980ffd3 100644 --- a/contracts/test/types/ProofRequest.t.sol +++ b/contracts/test/types/ProofRequest.t.sol @@ -12,6 +12,7 @@ import {Requirements} from "../../src/types/Requirements.sol"; import {Input, InputType} from "../../src/types/Input.sol"; import {Predicate, PredicateType, PredicateLibrary} from "../../src/types/Predicate.sol"; import {Callback} from "../../src/types/Callback.sol"; +import {FulfillmentDataType} from "../../src/types/FulfillmentData.sol"; import {Offer} from "../../src/types/Offer.sol"; import {Account} from "../../src/types/Account.sol"; import {RequestId, RequestIdLibrary} from "../../src/types/RequestId.sol"; @@ -70,11 +71,7 @@ contract ProofRequestTest is Test { defaultProofRequest = ProofRequest({ id: RequestIdLibrary.from(wallet, idx), requirements: Requirements({ - imageId: APP_IMAGE_ID, - predicate: Predicate({ - predicateType: PredicateType.DigestMatch, - data: abi.encode(sha256(bytes("GUEST JOURNAL"))) - }), + predicate: PredicateLibrary.createDigestMatchPredicate(APP_IMAGE_ID, sha256(bytes("GUEST JOURNAL"))), callback: Callback({gasLimit: 0, addr: address(0)}), selector: bytes4(0) }), diff --git a/crates/assessor/src/lib.rs b/crates/assessor/src/lib.rs index f36a01dc2..cea0799e4 100644 --- a/crates/assessor/src/lib.rs +++ b/crates/assessor/src/lib.rs @@ -9,8 +9,13 @@ use alloy_primitives::{Address, Keccak256, Signature, SignatureError}; use alloy_sol_types::{Eip712Domain, SolStruct}; -use boundless_market::contracts::{EIP712DomainSaltless, ProofRequest, RequestError}; -use risc0_zkvm::{sha::Digest, ReceiptClaim}; +use boundless_market::contracts::{ + EIP712DomainSaltless, FulfillmentClaimData, ProofRequest, RequestError, +}; +use risc0_zkvm::{ + sha::{Digest, Digestible}, + ReceiptClaim, +}; use serde::{Deserialize, Serialize}; /// Errors that may occur in the assessor. @@ -39,8 +44,8 @@ pub enum Error { }, /// Predicate evaluation failure from [ProofRequest] [Requirements] - #[error("predicate evaluation failed")] - PredicateEvaluationError, + #[error("fulfillment requirements evaluation failed")] + RequirementsEvaluationError, } /// Fulfillment contains a signed request, including offer and requirements, @@ -52,8 +57,8 @@ pub struct Fulfillment { pub request: ProofRequest, /// The EIP-712 signature over the request. pub signature: Vec, - /// The journal of the request. - pub journal: Vec, + /// The fulfillment data of the request. + pub fulfillment_data: FulfillmentClaimData, } impl Fulfillment { @@ -77,15 +82,20 @@ impl Fulfillment { } /// Evaluates the requirements of the request. pub fn evaluate_requirements(&self) -> Result<(), Error> { - if !self.request.requirements.predicate.eval(&self.journal) { - return Err(Error::PredicateEvaluationError); + if !self.request.requirements.predicate.eval(&self.fulfillment_data) { + return Err(Error::RequirementsEvaluationError); } Ok(()) } - /// Returns a [ReceiptClaim] for the fulfillment. - pub fn receipt_claim(&self) -> ReceiptClaim { - let image_id = Digest::from_bytes(self.request.requirements.imageId.0); - ReceiptClaim::ok(image_id, self.journal.clone()) + + /// Returns the claim digest for the fulfillment. + pub fn claim_digest(&self) -> Result { + match self.fulfillment_data { + FulfillmentClaimData::ClaimDigest(digest) => Ok(digest), + FulfillmentClaimData::ImageIdAndJournal(image_id, ref journal) => { + Ok(ReceiptClaim::ok(image_id, >::from(journal.clone())).digest()) + } + } } } @@ -161,8 +171,8 @@ mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - eip712_domain, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, - RequestInputType, Requirements, + eip712_domain, Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, + Requirements, }; use guest_assessor::ASSESSOR_GUEST_ELF; use guest_util::{ECHO_ELF, ECHO_ID}; @@ -175,10 +185,7 @@ mod tests { fn proving_request(id: u32, signer: Address, image_id: B256, prefix: Vec) -> ProofRequest { ProofRequest::new( RequestId::new(signer, id), - Requirements::new( - Digest::from_bytes(image_id.0), - Predicate { predicateType: PredicateType::PrefixMatch, data: prefix.into() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from_bytes(image_id.0), prefix)), "test", RequestInput { inputType: RequestInputType::Url, data: Default::default() }, Offer { @@ -207,7 +214,10 @@ mod tests { let claim = Fulfillment { request: proving_request, signature: signature.as_bytes().to_vec(), - journal: vec![1, 2, 3], + fulfillment_data: FulfillmentClaimData::ImageIdAndJournal( + Digest::from_bytes(B256::ZERO.0), + vec![1].into(), + ), }; claim.verify_signature(&eip712_domain(Address::ZERO, 1).alloy_struct()).unwrap(); @@ -225,16 +235,12 @@ mod tests { async fn setup_proving_request_and_signature( signer: &PrivateKeySigner, - ) -> (ProofRequest, Vec) { - let request = proving_request( - 1, - signer.address(), - to_b256(ECHO_ID.into()), - "test".as_bytes().to_vec(), - ); + ) -> (ProofRequest, B256, Vec) { + let image_id = to_b256(ECHO_ID.into()); + let request = proving_request(1, signer.address(), image_id, "test".as_bytes().to_vec()); let signature = request.sign_request(signer, Address::ZERO, 1).await.unwrap().as_bytes().to_vec(); - (request, signature) + (request, image_id, signature) } fn echo(input: &str) -> Receipt { @@ -273,14 +279,18 @@ mod tests { async fn test_assessor_e2e_singleton() { let signer = PrivateKeySigner::random(); // 1. Mock and sign a request - let (request, signature) = setup_proving_request_and_signature(&signer).await; + let (request, image_id, signature) = setup_proving_request_and_signature(&signer).await; // 2. Prove the request via the application guest let application_receipt = echo("test"); let journal = application_receipt.journal.bytes.clone(); // 3. Prove the Assessor - let claims = vec![Fulfillment { request, signature, journal }]; + let claims = vec![Fulfillment { + request, + signature, + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal(*image_id, journal), + }]; assessor(claims, vec![application_receipt]); } @@ -289,12 +299,16 @@ mod tests { async fn test_assessor_e2e_two_leaves() { let signer = PrivateKeySigner::random(); // 1. Mock and sign a request - let (request, signature) = setup_proving_request_and_signature(&signer).await; + let (request, image_id, signature) = setup_proving_request_and_signature(&signer).await; // 2. Prove the request via the application guest let application_receipt = echo("test"); let journal = application_receipt.journal.bytes.clone(); - let claim = Fulfillment { request, signature, journal }; + let claim = Fulfillment { + request, + signature, + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal(*image_id, journal), + }; // 3. Prove the Assessor reusing the same leaf twice let claims = vec![claim.clone(), claim]; diff --git a/crates/bench/src/lib.rs b/crates/bench/src/lib.rs index 676c07a47..2a398b6a4 100644 --- a/crates/bench/src/lib.rs +++ b/crates/bench/src/lib.rs @@ -276,10 +276,10 @@ pub async fn run(args: &MainArgs) -> Result<()> { id: boundless_client.boundless_market.request_id_from_rand().await?, offer: Offer { biddingStart: bidding_start, ..initial_offer }, input: request_input, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::digest_match( image_id, - Predicate::digest_match(journal.digest()), - ), + journal.digest(), + )), imageUrl: inital_request.imageUrl.clone(), }; tracing::debug!("Request: {:?}", request); diff --git a/crates/boundless-cli/Cargo.toml b/crates/boundless-cli/Cargo.toml index c7f37bdae..bb1f1684b 100644 --- a/crates/boundless-cli/Cargo.toml +++ b/crates/boundless-cli/Cargo.toml @@ -34,7 +34,9 @@ serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } shadow-rs = { version = "1.1", default-features = false } +shrink_bitvm2 = { workspace = true } sqlx = { workspace = true, features = ["postgres", "runtime-tokio", "tls-rustls", "chrono"] } +tempfile = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } @@ -53,9 +55,11 @@ order-stream = { workspace = true } sqlx = { workspace = true, features = ["postgres", "runtime-tokio", "tls-rustls", "chrono"] } tempfile = { workspace = true } tracing-test = { workspace = true } +test-log = { workspace = true } +shrink_bitvm2 = { workspace = true } [features] # Enables the prove feature on risc0-zkvm to build the prover directly into this CLI. prove = ["risc0-zkvm/prove"] # Enables the cuda feature on risc0-zkvm to build the prover directly into this CLI, with CUDA support. -cuda = ["prove", "risc0-zkvm/cuda"] +cuda = ["prove", "risc0-zkvm/cuda"] \ No newline at end of file diff --git a/crates/boundless-cli/src/bin/boundless.rs b/crates/boundless-cli/src/bin/boundless.rs index 4cecf9c4c..b33903dae 100644 --- a/crates/boundless-cli/src/bin/boundless.rs +++ b/crates/boundless-cli/src/bin/boundless.rs @@ -68,7 +68,7 @@ use boundless_cli::{convert_timestamp, DefaultProver, OrderFulfilled}; use clap::{Args, CommandFactory, Parser, Subcommand}; use clap_complete::aot::Shell; use risc0_aggregation::SetInclusionReceiptVerifierParameters; -use risc0_ethereum_contracts::{set_verifier::SetVerifierService, IRiscZeroVerifier}; +use risc0_ethereum_contracts::{set_verifier::SetVerifierService, IRiscZeroVerifier, Receipt}; use risc0_zkvm::{ compute_image_id, default_executor, sha::{Digest, Digestible}, @@ -82,11 +82,12 @@ use url::Url; use boundless_market::{ contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx, UnlockedRequest}, - Offer, ProofRequest, RequestInputType, Selector, + FulfillmentClaimData, FulfillmentData, Offer, PredicateType, ProofRequest, + RequestInputType, Selector, }, input::GuestEnv, request_builder::{OfferParams, RequirementParams}, - selector::ProofType, + selector::{is_shrink_bitvm2_selector, ProofType}, storage::{fetch_url, StorageProvider, StorageProviderConfig}, Client, Deployment, StandardClient, }; @@ -661,29 +662,60 @@ async fn handle_request_command(cmd: &RequestCommands, client: StandardClient) - } RequestCommands::GetProof { request_id } => { tracing::info!("Fetching proof for request 0x{:x}", request_id); - let (journal, seal) = + let (fulfillment_data, seal) = client.boundless_market.get_request_fulfillment(*request_id).await?; tracing::info!("Successfully retrieved proof for request 0x{:x}", request_id); tracing::info!( - "Journal: {} - Seal: {}", - serde_json::to_string_pretty(&journal)?, + "Fulfillment Data: {} - Seal: {}", + serde_json::to_string_pretty(&fulfillment_data)?, serde_json::to_string_pretty(&seal)? ); Ok(()) } RequestCommands::VerifyProof { request_id, image_id } => { tracing::info!("Verifying proof for request 0x{:x}", request_id); - let (journal, seal) = - client.boundless_market.get_request_fulfillment(*request_id).await?; - let journal_digest = <[u8; 32]>::from(Journal::new(journal.to_vec()).digest()).into(); + let verifier_address = client.deployment.verifier_router_address.context("no address provided for the verifier router; specify a verifier address with --verifier-address")?; let verifier = IRiscZeroVerifier::new(verifier_address, client.provider()); - - verifier - .verify(seal, *image_id, journal_digest) - .call() - .await - .map_err(|_| anyhow::anyhow!("Verification failed"))?; + let (fulfillment_data, seal) = + client.boundless_market.get_request_fulfillment(*request_id).await?; + let (req, _) = client.boundless_market.get_submitted_request(*request_id, None).await?; + let predicate = req.requirements.predicate; + match predicate.predicateType { + PredicateType::ClaimDigestMatch => { + let claim_digest = >::try_from(predicate.data.as_ref())?; + verifier + .verifyIntegrity(Receipt { seal, claimDigest: claim_digest }) + .call() + .await + .map_err(|_| anyhow::anyhow!("Verification failed"))?; + todo!() + } + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + let FulfillmentData { imageId, journal } = + FulfillmentData::abi_decode(&fulfillment_data)?; + ensure!( + imageId == *image_id, + "Image ID mismatch: expected {:?}, got {:?}", + imageId, + *image_id + ); + let journal_digest = + <[u8; 32]>::from(Journal::new(journal.to_vec()).digest()).into(); + + verifier + .verify(seal, *image_id, journal_digest) + .call() + .await + .map_err(|_| anyhow::anyhow!("Verification failed"))?; + } + _ => { + bail!( + "Unsupported predicate type for verification: {:?}", + predicate.predicateType + ); + } + } tracing::info!("Successfully verified proof for request 0x{:x}", request_id); Ok(()) @@ -716,9 +748,35 @@ async fn handle_proving_command(cmd: &ProvingCommands, client: StandardClient) - let session_info = execute(&request).await?; let journal = session_info.journal.bytes; - if !request.requirements.predicate.eval(&journal) { - tracing::error!("Predicate evaluation failed for request"); - bail!("Predicate evaluation failed"); + match request.requirements.predicate.predicateType { + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + request + .requirements + .image_id() + .ok_or_else(|| anyhow::anyhow!("Missing image ID"))?, + journal.clone(), + ); + if !request.requirements.predicate.eval(&fulfillment_data) { + tracing::error!( + "Predicate evaluation failed for request 0x{:x}", + request.id + ); + bail!("Predicate evaluation failed"); + } + } + PredicateType::ClaimDigestMatch => { + tracing::debug!( + "Skipping predicate evaluation for request 0x{:x} because it we might not be able to calculate it yet", + request.id + ); + } + _ => { + bail!( + "Unsupported predicate type: {:?}", + request.requirements.predicate.predicateType + ); + } } tracing::info!("Successfully executed request 0x{:x}", request.id); @@ -1102,6 +1160,7 @@ async fn submit_offer(client: StandardClient, args: &SubmitOfferArgs) -> Result< ProofType::Inclusion => requirements.selector(Selector::SetVerifierV0_7 as u32), ProofType::Groth16 => requirements.selector(Selector::Groth16V2_2 as u32), ProofType::Any => &mut requirements, + ProofType::ShrinkBitvm2 => requirements.selector(Selector::ShrinkBitvm2V0_1 as u32), ty => bail!("unsupported proof type provided in proof-type flag: {:?}", ty), }; let request = request.with_requirements(requirements); @@ -1126,15 +1185,15 @@ async fn submit_offer(client: StandardClient, args: &SubmitOfferArgs) -> Result< // Wait for fulfillment if requested if args.wait { tracing::info!("Waiting for request fulfillment..."); - let (journal, seal) = client + let (fulfillment_data, seal) = client .boundless_market .wait_for_request_fulfillment(request_id, Duration::from_secs(5), expires_at) .await?; tracing::info!("Request fulfilled!"); tracing::info!( - "Journal: {} - Seal: {}", - serde_json::to_string_pretty(&journal)?, + "Fulfillment Data: {} - Seal: {}", + serde_json::to_string_pretty(&fulfillment_data)?, serde_json::to_string_pretty(&seal)? ); } @@ -1187,24 +1246,57 @@ where // Verify image ID if available if let Some(claim) = session_info.receipt_claim { - ensure!( - claim.pre.digest().as_bytes() == request.requirements.imageId.as_slice(), - "Image ID mismatch: requirements ({}) do not match the given program ({})", - hex::encode(request.requirements.imageId), - hex::encode(claim.pre.digest().as_bytes()) - ); + if let Some(image_id) = request.requirements.image_id() { + ensure!( + claim.pre.digest() == image_id, + "Image ID mismatch: requirements ({}) do not match the given program ({})", + image_id, + claim.pre.digest(), + ); + } + tracing::debug!("Skipping image ID check, no image ID provided"); } else { tracing::debug!("Cannot check image ID; session info doesn't have receipt claim"); } - // Verify predicate - ensure!( - request.requirements.predicate.eval(&journal), - "Preflight failed: Predicate evaluation failed. Journal: {}, Predicate type: {:?}, Predicate data: {}", - hex::encode(&journal), - request.requirements.predicate.predicateType, - hex::encode(&request.requirements.predicate.data) - ); + match request.requirements.predicate.predicateType { + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + request + .requirements + .image_id() + .ok_or_else(|| anyhow::anyhow!("Missing image ID"))?, + journal.clone(), + ); + ensure!( + request.requirements.predicate.eval(&fulfillment_data), + "Preflight failed: Predicate evaluation failed. Journal: {}, Predicate type: {:?}, Predicate data: {}", + hex::encode(&journal), + request.requirements.predicate.predicateType, + hex::encode(&request.requirements.predicate.data) + ); + } + PredicateType::ClaimDigestMatch => { + tracing::debug!( + "Skipping predicate evaluation for request 0x{:x} because it we might not be able to calculate it yet", + request.id + ); + } + _ => { + bail!( + "Unsupported predicate type: {:?}", + request.requirements.predicate.predicateType + ); + } + } + + // TODO(ec2): fixme + // if is_shrink_bitvm2_selector(request.requirements.selector) && journal.len() != 32 { + // bail!( + // "Preflight failed: Journal must be exactly 32 bytes for Shrink Bitvm2, got {} bytes", + // journal.len() + // ); + // } tracing::info!("Preflight check passed"); } else { @@ -1416,10 +1508,8 @@ async fn handle_config_command(args: &MainArgs) -> Result<()> { mod tests { use std::net::{Ipv4Addr, SocketAddr}; - use alloy::primitives::aliases::U96; - use boundless_market::contracts::{ - Predicate, PredicateType, RequestId, RequestInput, Requirements, - }; + use alloy::primitives::{aliases::U96, Bytes}; + use boundless_market::contracts::{Predicate, RequestId, RequestInput, Requirements}; use super::*; @@ -1445,10 +1535,7 @@ mod tests { fn generate_request(id: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, id), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().write_slice(&[0x41, 0x41, 0x41, 0x41]).build_inline().unwrap(), Offer { @@ -2033,7 +2120,7 @@ mod tests { config: config.clone(), command: Command::Request(Box::new(RequestCommands::VerifyProof { request_id, - image_id: request.requirements.imageId, + image_id: <[u8; 32]>::from(request.requirements.image_id().unwrap()).into(), })), }) .await diff --git a/crates/boundless-cli/src/lib.rs b/crates/boundless-cli/src/lib.rs index 7da83eb58..9db370961 100644 --- a/crates/boundless-cli/src/lib.rs +++ b/crates/boundless-cli/src/lib.rs @@ -36,14 +36,16 @@ use risc0_zkvm::{ use boundless_market::{ contracts::{ - AssessorJournal, AssessorReceipt, EIP712DomainSaltless, - Fulfillment as BoundlessFulfillment, RequestInputType, + boundless_market_contract::FulfillmentData, AssessorJournal, AssessorReceipt, + EIP712DomainSaltless, Fulfillment as BoundlessFulfillment, FulfillmentClaimData, + FulfillmentDataType, PredicateType, RequestInputType, }, input::GuestEnv, - selector::{is_groth16_selector, SupportedSelectors}, + selector::{is_groth16_selector, is_shrink_bitvm2_selector, SupportedSelectors}, storage::fetch_url, ProofRequest, }; +use tempfile::tempdir; alloy::sol!( #[sol(all_derives)] @@ -175,6 +177,30 @@ impl DefaultProver { .await? } + pub(crate) async fn shrink_bitvm2(&self, receipt: &Receipt) -> Result { + if receipt.journal.bytes.len() != 32 { + bail!( + "Shrink BitVM2 requires a journal of 32 bytes, got {}", + receipt.journal.bytes.len() + ); + } + if is_dev_mode() { + return Ok(receipt.clone()); + } + let succinct_receipt = receipt.inner.succinct().unwrap(); + let p254_receipt = risc0_zkvm::recursion::identity_p254(succinct_receipt) + .context("identity predicate failed")?; + let temp_dir = tempdir().context("Failed to crate tmpdir")?; + let receipt = shrink_bitvm2::prove_and_verify( + "boundless_cli", + temp_dir.path(), + p254_receipt, + receipt.journal.clone(), + ) + .await?; + Ok(receipt) + } + // Finalizes the set builder. pub(crate) async fn finalize( &self, @@ -248,11 +274,14 @@ impl DefaultProver { let order_claim = ReceiptClaim::ok(order_image_id, order_journal.clone()); let order_claim_digest = order_claim.digest(); - let fill = Fulfillment { - request: req.clone(), - signature: sig.into(), - journal: order_journal.clone(), + let fulfillment_data = match req.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => FulfillmentClaimData::from_claim_digest( + req.requirements.predicate.claim_digest().unwrap(), + ), + _ => FulfillmentClaimData::from_image_id_and_journal(order_image_id, order_journal), }; + let fill = + Fulfillment { request: req.clone(), signature: sig.into(), fulfillment_data }; Ok::<_, anyhow::Error>((order_receipt, order_claim, order_claim_digest, fill)) }); @@ -303,15 +332,44 @@ impl DefaultProver { let order_seal = if is_groth16_selector(req.requirements.selector) { let receipt = self.compress(&receipts[i]).await?; encode_seal(&receipt)? + } else if is_shrink_bitvm2_selector(req.requirements.selector) { + let receipt = self.shrink_bitvm2(&receipts[i]).await?; + encode_seal(&receipt)? } else { order_inclusion_receipt.abi_encode_seal()? }; + // For now, we default to not providing journals with claim digest match, but you could if it is a R0 ZKVM commit digest. + let (claim_digest, fulfillment_data, fulfillment_data_type) = + match req.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => ( + <[u8; 32]>::from(fills[i].fulfillment_data.claim_digest().unwrap()).into(), + vec![], + FulfillmentDataType::None, + ), + PredicateType::PrefixMatch | PredicateType::DigestMatch => ( + <[u8; 32]>::from(claims[i].digest()).into(), + FulfillmentData { + imageId: <[u8; 32]>::from( + fills[i].fulfillment_data.image_id().unwrap(), + ) + .into(), + journal: fills[i].fulfillment_data.journal().unwrap().clone(), + } + .abi_encode(), + FulfillmentDataType::ImageIdAndJournal, + ), + _ => { + bail!("Invalid predicate type"); + } + }; + let fulfillment = BoundlessFulfillment { + claimDigest: claim_digest, + fulfillmentData: fulfillment_data.into(), + fulfillmentDataType: fulfillment_data_type, id: req.id, requestDigest: req.eip712_signing_hash(&self.domain.alloy_struct()), - imageId: req.requirements.imageId, - journal: fills[i].journal.clone().into(), seal: order_seal.into(), }; @@ -384,6 +442,7 @@ mod tests { }; use boundless_market_test_utils::{ASSESSOR_GUEST_ELF, ECHO_ID, ECHO_PATH, SET_BUILDER_ELF}; use risc0_ethereum_contracts::selector::Selector; + use shrink_bitvm2::ShrinkBitvm2ReceiptClaim; async fn setup_proving_request_and_signature( signer: &PrivateKeySigner, @@ -391,7 +450,7 @@ mod tests { ) -> (ProofRequest, Signature) { let request = ProofRequest::new( RequestId::new(signer.address(), 0), - Requirements::new(Digest::from(ECHO_ID), Predicate::prefix_match(vec![1])) + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), vec![1])) .with_selector(match selector { Some(selector) => FixedBytes::from(selector as u32), None => UNSPECIFIED_SELECTOR, @@ -439,6 +498,34 @@ mod tests { ) .expect("failed to create prover"); + prover.fulfill(&[(request, signature.as_bytes().into())]).await.unwrap(); + } + #[tokio::test] + #[test_log::test] + async fn test_shrink() { + let input = [255u8; 32].to_vec(); // Example output data + let blake3_claim_digest = + ShrinkBitvm2ReceiptClaim::ok(Digest::from(ECHO_ID), input.clone()).digest(); + let signer = PrivateKeySigner::random(); + let request = ProofRequest::new( + RequestId::new(signer.address(), 0), + Requirements::new(Predicate::claim_digest_match(blake3_claim_digest)) + .with_selector(FixedBytes::from(Selector::ShrinkBitvm2V0_1 as u32)), + format!("file://{ECHO_PATH}"), + RequestInput::builder().write_slice(&input).build_inline().unwrap(), + Offer::default(), + ); + + let signature = request.sign_request(&signer, Address::ZERO, 1).await.unwrap(); + let domain = eip712_domain(Address::ZERO, 1); + let prover = DefaultProver::new( + SET_BUILDER_ELF.to_vec(), + ASSESSOR_GUEST_ELF.to_vec(), + Address::ZERO, + domain, + ) + .expect("failed to create prover"); + prover.fulfill(&[(request, signature.as_bytes().into())]).await.unwrap(); } } diff --git a/crates/boundless-market/Cargo.toml b/crates/boundless-market/Cargo.toml index 11364c108..08cb44d9c 100644 --- a/crates/boundless-market/Cargo.toml +++ b/crates/boundless-market/Cargo.toml @@ -22,6 +22,7 @@ serde = { workspace = true } sha2 = { workspace = true } thiserror = { workspace = true } url = { workspace = true } +hex = { workspace = true } # Host dependencies [target.'cfg(not(target_os = "zkvm"))'.dependencies] @@ -35,7 +36,6 @@ clap = { workspace = true } dashmap = "6" futures = "0.3" futures-util = { workspace = true } -hex = { workspace = true } httpmock = "0.7" rand = { workspace = true } reqwest = { workspace = true, features = ["json", "multipart"] } diff --git a/crates/boundless-market/build.rs b/crates/boundless-market/build.rs index 25e6b2c14..ada3042dd 100644 --- a/crates/boundless-market/build.rs +++ b/crates/boundless-market/build.rs @@ -25,7 +25,7 @@ const EXCLUDE_CONTRACTS: [&str; 2] = [ ]; // Contracts to copy bytecode for. Used for deploying contracts in tests. -const ARTIFACT_TARGET_CONTRACTS: [&str; 8] = [ +const ARTIFACT_TARGET_CONTRACTS: [&str; 9] = [ "BoundlessMarket", "HitPoints", "RiscZeroMockVerifier", @@ -33,6 +33,7 @@ const ARTIFACT_TARGET_CONTRACTS: [&str; 8] = [ "ERC1967Proxy", "RiscZeroVerifierRouter", "RiscZeroGroth16Verifier", + "RiscZeroBitvm2Groth16Verifier", "MockCallback", ]; @@ -266,6 +267,9 @@ fn get_interfaces(contract: &str) -> &str { "RiscZeroGroth16Verifier" => { r#"constructor(bytes32 control_root, bytes32 bn254_control_id) {}"# } + "RiscZeroBitvm2Groth16Verifier" => { + r#"constructor(bytes32 control_root, bytes32 bn254_control_id) {}"# + } "MockCallback" => { r#"constructor(address verifier, address boundlessMarket, bytes32 imageId, uint256 _targetGas) {} function getCallCount() external view returns (uint256) {}"# diff --git a/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol b/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol index dfa3920f2..2cc9ffdf8 100644 --- a/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol +++ b/crates/boundless-market/src/contracts/artifacts/AssessorJournal.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {AssessorCallback} from "./AssessorCallback.sol"; +import {PredicateType} from "./Predicate.sol"; import {Selector} from "./Selector.sol"; /// @title Assessor Journal Struct diff --git a/crates/boundless-market/src/contracts/artifacts/AssessorJournalCallback.sol b/crates/boundless-market/src/contracts/artifacts/AssessorJournalCallback.sol deleted file mode 100644 index 1b6aa75b0..000000000 --- a/crates/boundless-market/src/contracts/artifacts/AssessorJournalCallback.sol +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2025 RISC Zero, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.20; - -struct AssessorJournalCallback { - /// @notice The index of the fill in the request - uint16 index; - /// @notice The address of the contract to call back - address addr; - /// @notice Maximum gas to use for the callback - uint96 gasLimit; -} diff --git a/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol b/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol index f21eee4ec..442d1c7e4 100644 --- a/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol +++ b/crates/boundless-market/src/contracts/artifacts/Fulfillment.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import {RequestId} from "./RequestId.sol"; +import {FulfillmentDataType} from "./FulfillmentData.sol"; using FulfillmentLibrary for Fulfillment global; @@ -15,16 +16,12 @@ struct Fulfillment { RequestId id; /// @notice EIP-712 digest of request struct. bytes32 requestDigest; - /// @notice Image ID of the guest that was verifiably executed to satisfy the request. - /// @dev Must match the value in the request's requirements. - bytes32 imageId; - // TODO: Add a flag in the request to decide whether to post the journal. Note that - // if the journal and journal digest do not need to be delivered to the client, imageId will - // be replaced with claim digest, since it is captured in the requirements on the request, - // checked by the Assessor guest. - /// @notice Journal committed by the guest program execution. - /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. - bytes journal; + /// @notice Claim Digest + bytes32 claimDigest; + /// @notice The type of data included in the fulfillment + FulfillmentDataType fulfillmentDataType; + /// @notice The fulfillment data + bytes fulfillmentData; /// @notice Cryptographic proof for the validity of the execution results. /// @dev This will be sent to the `IRiscZeroVerifier` associated with this contract. bytes seal; diff --git a/crates/boundless-market/src/contracts/artifacts/FulfillmentData.sol b/crates/boundless-market/src/contracts/artifacts/FulfillmentData.sol new file mode 100644 index 000000000..00ee97d06 --- /dev/null +++ b/crates/boundless-market/src/contracts/artifacts/FulfillmentData.sol @@ -0,0 +1,69 @@ +// Copyright 2025 RISC Zero, Inc. +// +// Use of this source code is governed by the Business Source License +// as found in the LICENSE-BSL file. +pragma solidity ^0.8.24; + +using FulfillmentDataLibrary for FulfillmentData global; + +enum FulfillmentDataType { + None, + ImageIdAndJournal +} + +/// @title FulfillmentData Struct and Library +/// @notice Represents a fulfillment configuration for proof delivery +struct FulfillmentData { + /// @notice Image ID of the guest that was verifiably executed to satisfy the request. + bytes32 imageId; + /// @notice Journal committed by the guest program execution. + /// @dev The journal is checked to satisfy the predicate specified on the request's requirements. + bytes journal; +} + +library FulfillmentDataLibrary { + /// @notice Decodes a bytes calldata into a FulfillmentData struct. + /// @param data The bytes calldata to decode. + /// @return fillData The decoded FulfillmentData struct. + function decode(bytes calldata data) public pure returns (FulfillmentData memory fillData) { + bytes32 imageId; + bytes calldata journal; + assembly { + imageId := calldataload(add(data.offset, 0x20)) + let journalOffset := calldataload(add(data.offset, 0x40)) + let journalPtr := add(data.offset, add(0x20, journalOffset)) + let journalLength := calldataload(journalPtr) + journal.offset := add(journalPtr, 0x20) + journal.length := journalLength + } + fillData = FulfillmentData(imageId, journal); + } + + /// @notice Decodes the fulfillment data from a bytes calldata. + /// @param data The bytes calldata to decode. + /// @return imageId The decoded image ID. + /// @return journal The decoded journal. + function decodeFulfillmentData(bytes calldata data) + internal + pure + returns (bytes32 imageId, bytes calldata journal) + { + assembly { + // Extract imageId (first 32 bytes after length) + imageId := calldataload(add(data.offset, 0x20)) + // Extract journal offset and create calldata slice + let journalOffset := calldataload(add(data.offset, 0x40)) + let journalPtr := add(data.offset, add(0x20, journalOffset)) + let journalLength := calldataload(journalPtr) + journal.offset := add(journalPtr, 0x20) + journal.length := journalLength + } + } + + /// @notice Encodes the fulfillment data into bytes. + /// @param fillData The FulfillmentData struct to encode. + /// @return The encoded bytes. + function encode(FulfillmentData memory fillData) public pure returns (bytes memory) { + return abi.encode(fillData); + } +} diff --git a/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol b/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol index 6e037c243..6532d2125 100644 --- a/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol +++ b/crates/boundless-market/src/contracts/artifacts/IBoundlessMarket.sol @@ -165,6 +165,14 @@ interface IBoundlessMarket { /// @dev selector efc954a6 error BatchSizeExceedsLimit(uint256 batchSize, uint256 limit); + /// @notice Error when the fulfillment data type is not supported + /// TODO(ec2): selector + error UnsupportedFulfillmentData(); + + /// @notice Error when the fulfillment has a unfulfillable callback + /// TODO(ec2): selector + error UnfulfillableCallback(); + /// @notice Check if the given request has been locked (i.e. accepted) by a prover. /// @dev When a request is locked, only the prover it is locked to can be paid to fulfill the job. /// @param requestId The ID of the request. diff --git a/crates/boundless-market/src/contracts/artifacts/Predicate.sol b/crates/boundless-market/src/contracts/artifacts/Predicate.sol index 255faab1d..e6ce08fd7 100644 --- a/crates/boundless-market/src/contracts/artifacts/Predicate.sol +++ b/crates/boundless-market/src/contracts/artifacts/Predicate.sol @@ -5,10 +5,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; +import {ReceiptClaim, ReceiptClaimLib} from "risc0/IRiscZeroVerifier.sol"; + using PredicateLibrary for Predicate global; +using ReceiptClaimLib for ReceiptClaim; /// @title Predicate Struct and Library -/// @notice Represents a predicate and provides functions to create and evaluate predicates. +/// @notice A predicate is a function over the claim that determines whether it meets the clients requirements. +/// The data field is used to store the specific data associated with the predicate. +/// - DigestMatch: (bytes32, bytes32) -> abi.encodePacked(imageId, journalHash) +/// - PrefixMatch: (bytes32, bytes) -> abi.encodePacked(imageId, prefix) +/// - ClaimDigestMatch: (bytes32) -> abi.encode(claimDigest) struct Predicate { PredicateType predicateType; bytes data; @@ -16,7 +23,8 @@ struct Predicate { enum PredicateType { DigestMatch, - PrefixMatch + PrefixMatch, + ClaimDigestMatch } library PredicateLibrary { @@ -26,15 +34,26 @@ library PredicateLibrary { /// @notice Creates a digest match predicate. /// @param hash The hash to match. /// @return A Predicate struct with type DigestMatch and the provided hash. - function createDigestMatchPredicate(bytes32 hash) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encode(hash)}); + function createDigestMatchPredicate(bytes32 imageId, bytes32 hash) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.DigestMatch, data: abi.encodePacked(imageId, hash)}); } /// @notice Creates a prefix match predicate. /// @param prefix The prefix to match. /// @return A Predicate struct with type PrefixMatch and the provided prefix. - function createPrefixMatchPredicate(bytes memory prefix) internal pure returns (Predicate memory) { - return Predicate({predicateType: PredicateType.PrefixMatch, data: prefix}); + function createPrefixMatchPredicate(bytes32 imageId, bytes memory prefix) + internal + pure + returns (Predicate memory) + { + return Predicate({predicateType: PredicateType.PrefixMatch, data: abi.encodePacked(imageId, prefix)}); + } + + /// @notice Creates a claim digest match predicate. + /// @param claimDigest The claimDigest to match. + /// @return A Predicate struct with type ClaimDigestMatch and the provided claimDigest. + function createClaimDigestMatchPredicate(bytes32 claimDigest) internal pure returns (Predicate memory) { + return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)}); } /// @notice Evaluates the predicate against the given journal and journal digest. @@ -42,15 +61,19 @@ library PredicateLibrary { /// @param journal The journal to evaluate against. /// @param journalDigest The digest of the journal. /// @return True if the predicate is satisfied, false otherwise. - function eval(Predicate memory predicate, bytes memory journal, bytes32 journalDigest) + function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal, bytes32 journalDigest) internal pure returns (bool) { if (predicate.predicateType == PredicateType.DigestMatch) { - return bytes32(predicate.data) == journalDigest; + bytes memory dataJournal = _sliceToEnd(predicate.data, 32); + return bytes32(dataJournal) == journalDigest; } else if (predicate.predicateType == PredicateType.PrefixMatch) { - return startsWith(journal, predicate.data); + bytes memory dataJournal = _sliceToEnd(predicate.data, 32); + return startsWith(journal, dataJournal); + } else if (predicate.predicateType == PredicateType.ClaimDigestMatch) { + return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest(); } else { revert("Unreachable code"); } @@ -83,3 +106,17 @@ library PredicateLibrary { return keccak256(abi.encode(PREDICATE_TYPEHASH, predicate.predicateType, keccak256(predicate.data))); } } + +/// Taken from Openzepplin util Bytes.sol +function _sliceToEnd(bytes memory buffer, uint256 start) pure returns (bytes memory) { + // sanitize + uint256 end = buffer.length; + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; +} diff --git a/crates/boundless-market/src/contracts/artifacts/Requirements.sol b/crates/boundless-market/src/contracts/artifacts/Requirements.sol index 79e032d76..99a84f60d 100644 --- a/crates/boundless-market/src/contracts/artifacts/Requirements.sol +++ b/crates/boundless-market/src/contracts/artifacts/Requirements.sol @@ -10,15 +10,13 @@ import {Callback, CallbackLibrary} from "./Callback.sol"; using RequirementsLibrary for Requirements global; struct Requirements { - bytes32 imageId; Callback callback; Predicate predicate; bytes4 selector; } library RequirementsLibrary { - string constant REQUIREMENTS_TYPE = - "Requirements(bytes32 imageId,Callback callback,Predicate predicate,bytes4 selector)"; + string constant REQUIREMENTS_TYPE = "Requirements(Callback callback,Predicate predicate,bytes4 selector)"; bytes32 constant REQUIREMENTS_TYPEHASH = keccak256(abi.encodePacked(REQUIREMENTS_TYPE, CallbackLibrary.CALLBACK_TYPE, PredicateLibrary.PREDICATE_TYPE)); @@ -29,7 +27,6 @@ library RequirementsLibrary { return keccak256( abi.encode( REQUIREMENTS_TYPEHASH, - requirements.imageId, CallbackLibrary.eip712Digest(requirements.callback), PredicateLibrary.eip712Digest(requirements.predicate), requirements.selector diff --git a/crates/boundless-market/src/contracts/artifacts/TransientPrice.sol b/crates/boundless-market/src/contracts/artifacts/TransientPrice.sol deleted file mode 100644 index 5a396213e..000000000 --- a/crates/boundless-market/src/contracts/artifacts/TransientPrice.sol +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2025 RISC Zero, Inc. -// -// Use of this source code is governed by the Business Source License -// as found in the LICENSE-BSL file. -pragma solidity ^0.8.24; - -using TransientPriceLibrary for TransientPrice global; - -/// Struct encoding the validated price for a request, intended for use with transient storage. -struct TransientPrice { - /// Boolean set to true to indicate the request was validated. - bool valid; - uint96 price; -} - -library TransientPriceLibrary { - /// Packs the struct into a uint256. - function pack(TransientPrice memory x) internal pure returns (uint256) { - return (uint256(x.valid ? 1 : 0) << 96) | uint256(x.price); - } - - /// Unpacks the struct from a uint256. - function unpack(uint256 packed) internal pure returns (TransientPrice memory) { - return TransientPrice({valid: (packed & (1 << 96)) > 0, price: uint96(packed & uint256(type(uint96).max))}); - } - - /// Packs and stores the object to transient storage. - function store(TransientPrice memory x, bytes32 requestDigest) internal { - uint256 packed = x.pack(); - assembly { - tstore(requestDigest, packed) - } - } - - /// Loads from transient storage and unpacks to TransientPrice. - function load(bytes32 requestDigest) internal view returns (TransientPrice memory) { - uint256 packed; - assembly { - packed := tload(requestDigest) - } - return unpack(packed); - } -} diff --git a/crates/boundless-market/src/contracts/boundless_market.rs b/crates/boundless-market/src/contracts/boundless_market.rs index 9246051ac..8d1921bac 100644 --- a/crates/boundless-market/src/contracts/boundless_market.rs +++ b/crates/boundless-market/src/contracts/boundless_market.rs @@ -1035,7 +1035,7 @@ impl BoundlessMarketService

{ if let Some((event, _)) = logs.first() { return Ok(( - event.fulfillment.journal.clone(), + event.fulfillment.fulfillmentData.clone(), event.fulfillment.seal.clone(), event.prover, )); @@ -1101,7 +1101,7 @@ impl BoundlessMarketService

{ Err(MarketError::RequestNotFound(request_id)) } - /// Returns journal and seal if the request is fulfilled. + /// Returns fulfillment data containing the journal and image id (if available) and seal if the request is fulfilled. pub async fn get_request_fulfillment( &self, request_id: U256, @@ -1109,8 +1109,9 @@ impl BoundlessMarketService

{ match self.get_status(request_id, None).await? { RequestStatus::Expired => Err(MarketError::RequestHasExpired(request_id)), RequestStatus::Fulfilled => { - let (journal, seal, _) = self.query_fulfilled_event(request_id, None, None).await?; - Ok((journal, seal)) + let (fulfillment_data, seal, _) = + self.query_fulfilled_event(request_id, None, None).await?; + Ok((fulfillment_data, seal)) } _ => Err(MarketError::RequestNotFulfilled(request_id)), } @@ -1154,7 +1155,7 @@ impl BoundlessMarketService

{ self.query_request_submitted_event(request_id, None, None).await } - /// Returns journal and seal if the request is fulfilled. + /// Returns the fulfillment data and seal if the request is fulfilled. /// /// This method will poll the status of the request until it is Fulfilled or Expired. /// Polling is done at intervals of `retry_interval` until the request is Fulfilled, Expired or @@ -1170,9 +1171,9 @@ impl BoundlessMarketService

{ match status { RequestStatus::Expired => return Err(MarketError::RequestHasExpired(request_id)), RequestStatus::Fulfilled => { - let (journal, seal, _) = + let (fulfillment_data, seal, _) = self.query_fulfilled_event(request_id, None, None).await?; - return Ok((journal, seal)); + return Ok((fulfillment_data, seal)); } _ => { tracing::info!( diff --git a/crates/boundless-market/src/contracts/bytecode.rs b/crates/boundless-market/src/contracts/bytecode.rs index a3e72cab0..8672be2d8 100644 --- a/crates/boundless-market/src/contracts/bytecode.rs +++ b/crates/boundless-market/src/contracts/bytecode.rs @@ -1,7 +1,7 @@ // Auto-generated file, do not edit manually alloy::sol! { - #[sol(rpc, bytecode = "610100346101b957601f61610f38819003918201601f19168301916001600160401b038311848410176101bd578084926060946040528339810103126101b9578051906001600160a01b03821682036101b9576020810151604090910151916001600160a01b03831683036101b9573060805260a05260c05260e0527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166101aa576002600160401b03196001600160401b03821601610141575b604051615f3d90816101d2823960805181818161199a0152611a2d015260a0518181816124590152818161311d0152818161360a01526136b8015260c051818181610c9701528181610fc40152613187015260e0518181816113f40152818161153a015281816118fe01528181611dda015281816122ec0152614f0f0152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f6100c1565b63f92ee8a960e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816308c84e70146124475750806317e828c41461242e57806318e0e161146123a15780631ce030241461238357806325d5971f1461224c5780632abff1f2146121415780632e1a7d4d146121235780632f13a90a1461209b57806331750cc714612083578063380f9c381461201157806341451f9414611f6057806345bc4d1014611bf2578063487c7b8c14611bd75780634f1ef286146119ee57806352d1902d14611987578063553c02481461196b5780635b07fdd8146119485780635f37a5031461192d5780635fbb4921146118e857806360dfd4a914611850578063635556241461178c57806370a0823114611749578063715018a6146116ca57806379ba50971461167d5780637ed75e36146115fb5780638040fdc0146114925780638094e614146113ce57806380bd38db146113ad57806381bf6c241461136457806384b0196e146110d85780638da5cb5b146110a35780638e3b694514611021578063956b0960146110045780639f04f42014610fe75780639fe9428c14610fac578063ad3cb1cc14610f63578063ae7330f114610eba578063b4206dd214610e7d578063c515c15f14610df8578063c5fe2fac14610dcb578063cb09e7c014610d85578063cb74db1114610d5c578063cb82cc8f14610d3e578063cdc9712314610c44578063cfbebd8b14610945578063d0e30db01461092e578063e30c3978146108f9578063f2800f1a146108a2578063f2fde38b1461081c578063f399e22e1461026d5763ffa1ad741461024f575f80fd5b3461026a578060031936011261026a57602060405160018152f35b80fd5b503461026a57604036600319011261026a57610287612508565b906024356001600160401b038111610818576102a7903690600401612488565b90925f80516020615eb1833981519152549060ff8260401c1615916001600160401b03811680159081610810575b6001149081610806575b1590816107fd575b506107ee5767ffffffffffffffff1981166001175f80516020615eb183398151915255826107c2575b5061031961598f565b61032161598f565b6001600160a01b038116156107ae5761033990614e74565b61034161598f565b604091825161035084826127a8565b601081526f12509bdd5b991b195cdcd3585c9ad95d60821b602082015283519061037a85836127a8565b60018252603160f81b602083015261039061598f565b61039861598f565b8051906001600160401b03821161079a576103c05f80516020615df183398151915254612d1d565b601f811161073e575b50602090601f83116001146106c2576103f992918891836105cb575b50508160011b915f199060031b1c19161790565b5f80516020615df1833981519152555b8051906001600160401b0382116106ae576104315f80516020615e3183398151915254612d1d565b601f8111610652575b50602090601f83116001146105d65761046992918791836105cb5750508160011b915f199060031b1c19161790565b5f80516020615e31833981519152555b835f80516020615e5183398151915255835f80516020615ed1833981519152556001600160401b0381116105b7576104bb816104b6600254612d1d565b612d55565b83601f821160011461054857819085966104ea9495969261053d5750508160011b915f199060031b1c19161790565b6002555b6104f6575080f35b5f80516020615eb1833981519152805460ff60401b1916905551600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a180f35b013590505f806103e5565b60028552601f198216955f80516020615db183398151915291865b88811061059f57508360019596979810610586575b505050811b016002556104ee565b01355f19600384901b60f8161c191690555f8080610578565b90926020600181928686013581550194019101610563565b634e487b7160e01b84526041600452602484fd5b015190505f806103e5565b5f80516020615e3183398151915287528187209190601f198416885b81811061063a5750908460019594939210610622575b505050811b015f80516020615e3183398151915255610479565b01515f1960f88460031b161c191690555f8080610608565b929360206001819287860151815501950193016105f2565b5f80516020615e3183398151915287525f80516020615f11833981519152601f840160051c810191602085106106a4575b601f0160051c01905b818110610699575061043a565b87815560010161068c565b9091508190610683565b634e487b7160e01b86526041600452602486fd5b5f80516020615df183398151915288528188209190601f198416895b818110610726575090846001959493921061070e575b505050811b015f80516020615df183398151915255610409565b01515f1960f88460031b161c191690555f80806106f4565b929360206001819287860151815501950193016106de565b5f80516020615df183398151915288525f80516020615e71833981519152601f840160051c81019160208510610790575b601f0160051c01905b81811061078557506103c9565b888155600101610778565b909150819061076f565b634e487b7160e01b87526041600452602487fd5b631e4fbdf760e01b84526004849052602484fd5b68ffffffffffffffffff191668010000000000000001175f80516020615eb1833981519152555f610310565b63f92ee8a960e01b8552600485fd5b9050155f6102e7565b303b1591506102df565b8491506102d5565b5080fd5b503461026a57602036600319011261026a57610836612508565b61083e6147e7565b5f80516020615ef183398151915280546001600160a01b0319166001600160a01b039283169081179091555f80516020615e11833981519152549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b503461026a57602036600319011261026a57600435906108c182613e5c565b156108e7576040816020936001600160401b039352808452205460a01c16604051908152f35b60249163d2be005d60e01b8252600452fd5b503461026a578060031936011261026a575f80516020615ef1833981519152546040516001600160a01b039091168152602090f35b508060031936011261026a57610942613e89565b80f35b503461026a57606036600319011261026a576004356001600160401b0381116108185761016081600401916003199036030112610818576024356001600160401b038111610c405761099b903690600401612488565b916044356001600160401b038111610c3c576109bb903690600401612488565b8235916109c783614a0b565b9290916109dc6109d73688612a24565b61418b565b6109ed816109e861592e565b6153e5565b95600160c01b1615610bfb57604051630b135d3f60e11b815260208180610a198d8d8c60048501612cbb565b03816001600160a01b0389165afa908115610bf0578b91610bc1575b506001600160e01b0319166374eca2c160e11b01610bb2575b604051610a5c6060826127a8565b60218152602081017f4c6f636b526571756573742850726f6f66526571756573742072657175657374815260408201602960f81b9052610a9a613f78565b90610aa3613fc2565b8d610aac614007565b610ab46140c1565b610abc613ef1565b91610ac561410e565b94604051978897602089019a5180918c5e880160208101918783528051926020849201905e0160200185815281516020819301825e0184815281516020819301825e0183815281516020819301825e0182815281516020819301825e0190815281516020819301825e018d815203601f1981018252610b4490826127a8565b519020906040519060208201928352604082015260408152610b676060826127a8565b519020610b7261592e565b90610b7c916153e5565b913690610b88926127e4565b610b91916159ba565b610b9d919592956159f4565b610ba685614607565b96610942989196615019565b638baa579f60e01b8a5260048afd5b610be3915060203d602011610be9575b610bdb81836127a8565b810190614506565b5f610a35565b503d610bd1565b6040513d8d823e3d90fd5b610c1b610c12610c0c368c8c6127e4565b886159ba565b909291926159f4565b6001600160a01b03858116911614610a4e57638baa579f60e01b8a5260048afd5b8480fd5b8280fd5b503461026a578060031936011261026a57604051908060025490610c6782612d1d565b8085529160018116908115610d175750600114610ccd575b610cc984610c8f818603826127a8565b6040519182917f00000000000000000000000000000000000000000000000000000000000000008352604060208401526040830190612635565b0390f35b600281525f80516020615db1833981519152939250905b808210610cfd57509091508101602001610c8f82610c7f565b919260018160209254838588010152019101909291610ce4565b60ff191660208087019190915292151560051b85019092019250610c8f9150839050610c7f565b503461026a57602036600319011261026a5761094260043533614edf565b503461026a57602036600319011261026a576020610d7b600435613e5c565b6040519015158152f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b03610db6612508565b16815260018452205460601c16604051908152f35b503461026a57610cc9610dec610de0366128c7565b95949094939193613e20565b60405191829182612659565b503461026a57602036600319011261026a57604060e091600435815280602052208054906001600160601b0360026001830154920154916040519360018060a01b03811685526001600160401b038160a01c16602086015262ffffff81871c16604086015260f81c6060850152818116608085015260601c1660a083015260c0820152f35b503461026a57610942610e8f366124b5565b91610e9a8135614a0b565b90610ea785858386614526565b610eb084614607565b9690953395615019565b503461026a57606036600319011261026a5780610ed5612508565b6044356001600160401b038111610f5f57610ef4903690600401612488565b6001600160a01b0390921691823b15610f5a57610f2d92849283604051809681958294636691f64760e01b845260243560048501612cbb565b03925af18015610f4f57610f3e5750f35b81610f48916127a8565b61026a5780f35b6040513d84823e3d90fd5b505050fd5b5050fd5b503461026a578060031936011261026a5750610cc9604051610f866040826127a8565b60058152640352e302e360dc1b6020820152604051918291602083526020830190612635565b503461026a578060031936011261026a5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b503461026a578060031936011261026a57602060405161c3508152f35b503461026a578060031936011261026a5760206040516107d08152f35b503461026a5761103036612838565b969095919490936001600160a01b039092169190823b15610c3c5791611071939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f5761108e575b610cc9610dec868686613aa1565b6110998280926127a8565b61026a5780611080565b503461026a578060031936011261026a575f80516020615e11833981519152546040516001600160a01b039091168152602090f35b503461026a578060031936011261026a575f80516020615e5183398151915254158061134e575b15611311576040519080825f80516020615df1833981519152549161112383612d1d565b80835292600181169081156112f2575060011461129b575b611147925003836127a8565b6040519080825f80516020615e31833981519152549161116683612d1d565b808352926001811690811561127c5750600114611225575b61119291939250936111c9959403836127a8565b60206111d7604051936111a583866127a8565b8385525f368137604051968796600f60f81b885260e08589015260e0880190612635565b908682036040880152612635565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b82811061120e57505050500390f35b8351855286955093810193928101926001016111ff565b505f80516020615e3183398151915283529082905f80516020615f118339815191525b8183106112605750509060206111929282010161117e565b6020919350806001915483858901015201910190918492611248565b6020925061119294915060ff191682840152151560051b82010161117e565b505f80516020615df183398151915283529082905f80516020615e718339815191525b8183106112d65750509060206111479282010161113b565b60209193508060019154838589010152019101909184926112be565b6020925061114794915060ff191682840152151560051b82010161113b565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b505f80516020615ed183398151915254156110ff565b503461026a57602036600319011261026a576113a16020916040611389600435614a0b565b6001600160a01b039091168352600185529120614a45565b90506040519015158152f35b503461026a57610cc9610dec6113c2366128c7565b95949094939193613dde565b503461026a5760a036600319011261026a576004358160443560ff8116809103610818577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690813b15610c4057829160e48392604051948593849263d505accf60e01b84523360048501523060248501528960448501526024356064850152608484015260643560a484015260843560c48401525af161147d575b506109428233614edf565b81611487916127a8565b61081857815f611472565b503461026a57602036600319011261026a576004356114af6147e7565b30825260016020526001600160601b03604083205460601c166001600160601b036114d9836147b6565b16116115e85761150f6114eb826147b6565b30845260016020526001600160601b03604085209181835460601c16031690612cd2565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156115dd5783916115ae575b501561159f576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203092a280f35b6312171d8360e31b8252600482fd5b6115d0915060203d6020116115d6575b6115c881836127a8565b810190612d05565b5f61156a565b503d6115be565b6040513d85823e3d90fd5b63112fed8b60e31b825230600452602482fd5b503461026a5761160a36612838565b969095919490936001600160a01b039092169190823b15610c3c579161164b939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f57611668575b610cc9610dec868686613d22565b6116738280926127a8565b61026a578061165a565b503461026a578060031936011261026a575f80516020615ef183398151915254336001600160a01b03909116036116b75761094233614e74565b63118cdaa760e01b815233600452602490fd5b503461026a578060031936011261026a576116e36147e7565b5f80516020615ef183398151915280546001600160a01b03199081169091555f80516020615e118339815191528054918216905581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b0361177a612508565b16815260018452205416604051908152f35b503461026a57602036600319011261026a576004356117a96147e7565b30825260016020526001600160601b036040832054166001600160601b036117d0836147b6565b16116115e8576117df816147b6565b30835260016020526001600160601b03806040852092818454160316166001600160601b03198254161790558180808084335af161181b613d75565b501561159f576040519081527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6560203092a280f35b503461026a57602036600319011261026a5760046060604060209383358152808552206002604051916118828361270d565b805460018060a01b03811684526001600160401b038160a01c168785015262ffffff8160e01c16604085015260f81c848401526001600160601b0360018201548181166080860152851c1660a0840152015460c082015201511615156040519015158152f35b503461026a578060031936011261026a576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461026a57610cc9610dec611942366126b8565b91613d22565b503461026a578060031936011261026a57602061196361592e565b604051908152f35b503461026a578060031936011261026a57602090604051908152f35b503461026a578060031936011261026a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036119df5760206040515f80516020615e918339815191528152f35b63703e46dd60e11b8152600490fd5b50604036600319011261026a57611a03612508565b906024356001600160401b03811161081857611a2390369060040161281a565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115611bb5575b50611ba657611a656147e7565b6040516352d1902d60e01b8152926001600160a01b0381169190602085600481865afa80958596611b72575b50611aaa57634c9c8ce360e01b84526004839052602484fd5b9091845f80516020615e918339815191528103611b605750813b15611b4e575f80516020615e9183398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8480a28151839015611b345780836020611b3095519101845af4611b2a613d75565b91615d52565b5080f35b50505034611b3f5780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b8452600452602483fd5b632a87526960e21b8552600452602484fd5b9095506020813d602011611b9e575b81611b8e602093836127a8565b81010312610c3c5751945f611a91565b3d9150611b81565b63703e46dd60e11b8252600482fd5b5f80516020615e91833981519152546001600160a01b0316141590505f611a58565b503461026a57610cc9610dec611bec366126b8565b91613aa1565b503461026a57602036600319011261026a57600435611c33611c1382614a0b565b6001600160a01b0390911680855260016020526040852090929190614a45565b5015611f4c57818352826020526040832060405190611c518261270d565b805460018060a01b03811683526001600160401b038160a01c16602084015262ffffff8160e01c16604084015260f81c60608301526001810154600260808401926001600160601b03831684526001600160601b0360a086019360601c168352015460c08401526004606084015116611f38576001606084015116611f24576001600160401b03611ce1846149e9565b16421115611efb5784865260208690526040862080546001600160f81b03811660f891821c60041790911b6001600160f81b0319161781558690600101556001600160601b038151166107d08102908082046107d01490151715611ee757611d5e6001600160601b039392612710611d6393049485915116613a64565b6147b6565b936002606060018060a01b038651169501511615155f14611e8357505060018060a01b03821685526001602052611db460408620611dae856001600160601b03835460601c16613a71565b90612cd2565b60405163a9059cbb60e01b815261dead60048201526024810182905291602083604481897f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015611e78577f79ca7c80cf57b513ffdf8aa37ec70e40757f5e0d35219241860bb4b4c2fa7616946060946001600160601b0392611e5b575b5060405193845216602083015260018060a01b03166040820152a280f35b611e739060203d6020116115d6576115c881836127a8565b611e3d565b6040513d88823e3d90fd5b9092506001600160601b0330933088526001602052611eaf60408920611dae8885835460601c16613a71565b511690865260016020526001600160601b03611ed2604088209282845416613a71565b166001600160601b0319825416179055611db4565b634e487b7160e01b87526011600452602487fd5b6044866001600160401b0387611f10876149e9565b9063079c66ab60e41b845260045216602452fd5b631cfdeebb60e01b86526004859052602486fd5b633231064d60e11b86526004859052602486fd5b63d2be005d60e01b83526004829052602483fd5b503461026a57602036600319011261026a5760043590611f7f82613e5c565b156108e757604081602093612000935280845220600260405191611fa28361270d565b805460018060a01b03811684526001600160401b038160a01c168685015262ffffff8160e01c16604085015260f81c60608401526001600160601b036001820154818116608086015260601c1660a0840152015460c08201526149e9565b6001600160401b0360405191168152f35b507f514a642174f202700c54726383c13321326925ff87df30f0bfbf49d9adfc41a661203c366124b5565b9291909234612076575b61207060405192839260408452612060604085018361387c565b9184830360208601523596612c9b565b0390a280f35b61207e613e89565b612046565b503461026a57610942612095366126b8565b91612f16565b503461026a576120aa36612562565b9a93969297909960018060a09b949b9897981b031691823b15610c3c57916120ed939185809460405196879586948593636691f64760e01b855260048501612cbb565b03925af18015610f4f5761210e575b610cc9610dec8a8a8a8a8a8a8a613e20565b6121198280926127a8565b61026a57806120fc565b503461026a57602036600319011261026a576109426004353361481a565b503461026a57602036600319011261026a576004356001600160401b03811161081857612172903690600401612488565b61217d9291926147e7565b6001600160401b0381116122385761219a816104b6600254612d1d565b81601f82116001146121cd57819083946121c7949261053d5750508160011b915f199060031b1c19161790565b60025580f35b60028352601f198216935f80516020615db183398151915291845b8681106122205750836001959610612207575b505050811b0160025580f35b01355f19600384901b60f8161c191690555f80806121fb565b909260206001819286860135815501940191016121e8565b634e487b7160e01b82526041600452602482fd5b503461026a57602036600319011261026a5760043533825260016020526001600160601b03604083205460601c166001600160601b0361228b836147b6565b1611612370576122c161229d826147b6565b33845260016020526001600160601b03604085209181835460601c16031690612cd2565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af19081156115dd578391612351575b501561159f576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203392a280f35b61236a915060203d6020116115d6576115c881836127a8565b5f61231c565b63112fed8b60e31b825233600452602482fd5b503461026a578060031936011261026a576020604051620186a08152f35b3461242a576123af36612562565b97999598909691959294929091906001600160a01b0316803b1561242a576123f19a5f80946040519d8e9586948593636691f64760e01b855260048501612cbb565b03925af196871561241f57610cc998610dec9861240f575b50613dde565b5f612419916127a8565b5f612409565b6040513d5f823e3d90fd5b5f80fd5b3461242a5761244561243f366124b5565b91612bd3565b005b3461242a575f36600319011261242a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b9181601f8401121561242a578235916001600160401b03831161242a576020838186019501011161242a57565b90604060031983011261242a576004356001600160401b03811161242a57610160818403600319011261242a5760040191602435906001600160401b03821161242a5761250491600401612488565b9091565b600435906001600160a01b038216820361242a57565b35906001600160a01b038216820361242a57565b9181601f8401121561242a578235916001600160401b03831161242a576020808501948460051b01011161242a57565b60e060031982011261242a576004356001600160a01b038116810361242a5791602435916044356001600160401b03811161242a57816125a491600401612488565b929092916064356001600160401b03811161242a57816125c691600401612532565b929092916084356001600160401b03811161242a57816125e891600401612532565b9290929160a4356001600160401b03811161242a578161260a91600401612532565b9290929160c435906001600160401b03821161242a57608090829003600319011261242a5760040190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b83831061268b57505050505090565b90919293946020806126a9600193603f198682030187528951612635565b9701930193019193929061267c565b604060031982011261242a576004356001600160401b03811161242a57816126e291600401612532565b92909291602435906001600160401b03821161242a57608090829003600319011261242a5760040190565b60e081019081106001600160401b0382111761272857604052565b634e487b7160e01b5f52604160045260245ffd5b60a081019081106001600160401b0382111761272857604052565b608081019081106001600160401b0382111761272857604052565b604081019081106001600160401b0382111761272857604052565b606081019081106001600160401b0382111761272857604052565b90601f801991011681019081106001600160401b0382111761272857604052565b6001600160401b03811161272857601f01601f191660200190565b9291926127f0826127c9565b916127fe60405193846127a8565b82948184528183011161242a578281602093845f960137010152565b9080601f8301121561242a57816020612835933591016127e4565b90565b60a060031982011261242a576004356001600160a01b038116810361242a5791602435916044356001600160401b03811161242a578161287a91600401612488565b929092916064356001600160401b03811161242a578161289c91600401612532565b92909291608435906001600160401b03821161242a57608090829003600319011261242a5760040190565b608060031982011261242a576004356001600160401b03811161242a57816128f191600401612532565b929092916024356001600160401b03811161242a578161291391600401612532565b929092916044356001600160401b03811161242a578161293591600401612532565b92909291606435906001600160401b03821161242a57608090829003600319011261242a5760040190565b35906001600160601b038216820361242a57565b35906001600160e01b03198216820361242a57565b35906001600160401b038216820361242a57565b359063ffffffff8216820361242a57565b91908260e091031261242a576040516129c68161270d565b60c080829480358452602081013560208501526129e560408201612989565b60408501526129f66060820161299d565b6060850152612a076080820161299d565b6080850152612a1860a0820161299d565b60a08501520135910152565b91906101608382031261242a5760405190612a3e8261273c565b81938035835260208101356001600160401b03811161242a5781018083039060a0821261242a576040805192612a7384612757565b82358452601f19011261242a57604051612a8c81612772565b612a986020830161251e565b8152612aa660408301612960565b6020820152602083015260608101356001600160401b03811161242a57810160408186031261242a5760405191612adc83612772565b8135600281101561242a5783526020820135926001600160401b03841161242a57612b0e87612b1e956080950161281a565b6020820152604085015201612974565b6060820152602084015260408101356001600160401b03811161242a57810182601f8201121561242a5782816020612b58933591016127e4565b604084015260608101356001600160401b03811161242a5781019160408382031261242a5760405192612b8a84612772565b8035600281101561242a5784526020810135926001600160401b03841161242a57608094612bbe84612bce9688950161281a565b60208201526060870152016129ae565b910152565b91823591600160c01b831615612c7a57612bf99260201c6001600160a01b031684614526565b905b6040612c38611d5e612c28612c0f85614607565b90506001600160401b03429116109460803691016129ae565b6001600160401b034216906146d2565b6001600160601b03825191612c4c8361278d565b60018352602083018590521691018190526001607f1b9115612c74576001607e1b5b1717905d565b5f612c6e565b505050612c95612c8d6109d73684612a24565b6109e861592e565b90612bfb565b908060209392818452848401375f828201840152601f01601f1916010190565b604090612835949281528160208201520191612c9b565b80546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055565b9081602091031261242a5751801515810361242a5790565b90600182811c92168015612d4b575b6020831014612d3757565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612d2c565b601f8111612d61575050565b60025f5260205f20906020601f840160051c83019310612d9b575b601f0160051c01905b818110612d90575050565b5f8155600101612d85565b9091508190612d7c565b6001600160401b0381116127285760051b60200190565b903590601e198136030182121561242a57018035906001600160401b03821161242a57602001918160061b3603831361242a57565b9190811015612e015760061b0190565b634e487b7160e01b5f52603260045260245ffd5b3561ffff8116810361242a5790565b9190811015612e015760051b81013590609e198136030182121561242a570190565b903590601e198136030182121561242a57018035906001600160401b03821161242a5760200191813603831361242a57565b805115612e015760200190565b8051821015612e015760209160051b010190565b6020815260406020612eb5845183838601526060850190612635565b93015191015290565b903590601e198136030182121561242a57018035906001600160401b03821161242a5760200191606082023603831361242a57565b356001600160a01b038116810361242a5790565b359061ffff8216820361242a57565b61ffff821161381457612f2882612da5565b90612f3660405192836127a8565b828252601f19612f4584612da5565b01366020840137612f5583612da5565b90612f6360405192836127a8565b838252601f19612f7285612da5565b013660208401376040850193612f888587612dbc565b90505f5b81811061375a5750505f5b8181106132d25750505050612fab906148ec565b612fc4612fbb6020850185612ebe565b91909385612dbc565b612fd360608796939601612ef3565b9160405193612fe185612757565b612fea81612da5565b91612ff860405193846127a8565b818352606060208401920281019036821161242a57915b81831061328157505050835261302481612da5565b9461303260405196876127a8565b818652602086019160061b81019036821161242a57915b818310613242575050506020820193845260408201928352606082019060018060a01b031681526040519260208401946020865260c08501935193608060408701528451809152602060e087019501905f5b8181106131fd575050505192603f19858203016060860152602080855192838152019401905f5b8181106131cd5750509051608085015250516001600160a01b031660a0830152819003601f19810182526020925f9290916130fd90826127a8565b604051918291518091835e8101838152039060025afa1561241f575f51907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906131509080612e46565b9091803b1561242a575f92613184926040519586948593849363ab750e7560e01b8552606060048601526064850191612c9b565b907f00000000000000000000000000000000000000000000000000000000000000006024840152604483015203915afa801561241f576131c15750565b5f6131cb916127a8565b565b8251805161ffff1687526020908101516001600160e01b03191681880152604090960195909201916001016130c2565b8251805161ffff1688526020818101516001600160a01b0316818a01526040918201516001600160601b0316918901919091526060909701969092019160010161309b565b60408336031261242a576020604091825161325c81612772565b61326586612f07565b8152613272838701612974565b83820152815201920191613049565b60608336031261242a57602060609160405161329c8161278d565b6132a586612f07565b81526132b283870161251e565b838201526132c260408701612960565b604082015281520192019161300f565b6132dd818385612e24565b9060205f6132ee6060850185612e46565b908160405192839283378101838152039060025afa1561241f575f515f60806040516133198161273c565b82815282602082015260405161332e81612772565b838152836020820152604082015282606082015201526040519061335182612772565b5f82525f60208301526040519061336782612772565b8152602081015f815260205f600c6040516b1c9a5cd8cc0b93dd5d1c1d5d60a21b815260025afa1561241f576020915f918251915190516040519185830193845260408301526060820152600160f91b6080820152606281526133cb6082826127a8565b604051918291518091835e8101838152039060025afa1561241f575f51906040516133f58161273c565b6040850135815260208101907fa3acc27117418996340b84e5a90f3ef4c49d22c79e44aad822ec9c313e1eb8e282526040810192835260608101915f83526080820194855260205f60126040517172697363302e52656365697074436c61696d60701b815260025afa1561241f575f51925191519051945184515190600382101561374657945160209081015160408051808401978852908101959095526060850193909352608084019690965260a08301949094526001600160f81b031960f894851b811660c0840152931b90921660c4830152600160fa1b60c883015260aa82525f916134e560ca826127a8565b604051918291518091835e8101838152039060025afa1561241f575f516040519061350f82612757565b8282526020820191843583526040810192602086013584526060820193838552608094605660405161354188826127a8565b81815275742c6279746573333220636c61696d4469676573742960501b606060208301927f4173736573736f72436f6d6d69746d656e742875696e7432353620696e64657884527f2c75696e743235362069642c627974657333322072657175657374446967657360408201520152209351925191519051916040519360208501958652604085015260608401528583015260a082015260a081526135e760c0826127a8565b5190206135f4848a612e85565b526135ff8388612e85565b516136b257613656937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316926136409190810190612e46565b94906040519561364f87612772565b36916127e4565b84526020840152803b1561242a57613685925f916040518080968194631599ead560e01b835260048301612e99565b039161c350fa91821561241f576001926136a2575b505b01612f97565b5f6136ac916127a8565b5f61369a565b6136ee937f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316926136409190810190612e46565b84526020840152803b1561242a5761371d925f916040518080968194631599ead560e01b835260048301612e99565b03915afa91821561241f57600192613736575b5061369c565b5f613740916127a8565b5f613730565b634e487b7160e01b5f52602160045260245ffd5b60206137708261376a8a8c612dbc565b90612df1565b013563ffffffff60e01b811680910361242a576137b56137ab61ffff6137a361379e8661376a8f8f90612dbc565b612e15565b168688612e24565b6080810190612e46565b60049291921161242a5760016137ed61ffff6137e661379e878f978f61376a9163ffffffff60e01b90351699612dbc565b1689612e85565b528181036137ff575050600101612f8c565b632e2ce35360e21b5f5260045260245260445ffd5b506377e4aa5360e11b5f5260045261ffff60245260445ffd5b9035603e198236030181121561242a570190565b6002111561374657565b9035601e198236030181121561242a5701602081359101916001600160401b03821161242a57813603831361242a57565b90813581526020820135609e198336030181121561242a5782016101606020830152803561016083015260018060a01b036138b96020830161251e565b166101808301526001600160601b036138d460408301612960565b166101a08301526138e8606082018261382d565b9060a06101c0840152813591600283101561242a57613921613934918461391161396d96613841565b610200870152602081019061384b565b6040610220870152610240860191612c9b565b906001600160e01b03199061394b90608001612974565b166101e084015261395f604085018561384b565b908483036040860152612c9b565b61397a606084018461382d565b82820360608401528035600281101561242a576101409260406139b18594846139a56139c196613841565b8452602081019061384b565b9190928160208201520191612c9b565b936080810135608085015260a081013560a08501526001600160401b036139ea60c08301612989565b1660c085015263ffffffff613a0160e0830161299d565b1660e085015263ffffffff613a19610100830161299d565b1661010085015263ffffffff613a32610120830161299d565b16610120850152013591015290565b601f19810191908211613a5057565b634e487b7160e01b5f52601160045260245ffd5b91908203918211613a5057565b906001600160601b03809116911601906001600160601b038211613a5057565b9190811015612e01576060020190565b92919092613ab0828583612f16565b613ab984612da5565b91613ac760405193846127a8565b848352601f19613ad686612da5565b015f5b818110613d115750508294613aed81612da5565b613afa60405191826127a8565b818152601f19613b0983612da5565b013660208301376020830191613b1f8385612ebe565b90505f5b818110613cd25750505f5b818110613b3e5750505050505050565b613b49818388612e24565b90613b5f613b5960608801612ef3565b83614b58565b90613b6a838b612e85565b52613cc957613b798185612e85565b5180613b8c575b50600191505b01613b2e565b613b968688612ebe565b90915f19810191908211613a5057613bad92613a91565b916040613bbc60208501612ef3565b930135906001600160601b03821680920361242a57613bde6060820182612e46565b94613bec6080840184612e46565b966001600160a01b0390921694909190853b1561242a5760405f8787613c50839760019d613c3e87519b8c9a8b998a9763a12da43f60e01b895201356004880152606060248801526064870191612c9b565b84810360031901604486015291612c9b565b0393f19081613cb9575b50613cb2577f5c5960582bfc7a494183b4e9a66bfe8ecffc07a83a48d136e732400f7b98bf5090613c89613d75565b92613ca860405192839283526040602084015235946040830190612635565b0390a25b5f613b80565b5050613cac565b5f613cc3916127a8565b5f613c5a565b60019150613b86565b613ce681613ce08789612ebe565b90613a91565b9060018101808211613a5057613d0a61ffff613d03600195612e15565b1687612e85565b5201613b23565b806060602080938801015201613ad9565b82606092613d3292959495613aa1565b92016001600160a01b03613d4582612ef3565b165f5260016020526001600160601b0360405f20541680613d64575050565b613d706131cb92612ef3565b61481a565b3d15613d9f573d90613d86826127c9565b91613d9460405193846127a8565b82523d5f602084013e565b606090565b9190811015612e015760051b8101359061015e198136030182121561242a570190565b90821015612e01576125049160051b810190612e46565b919695949392905f5b818110613dfd5750505050612835939450613d22565b80613e1a8a61243f8387613e14600197898c613da4565b93613dc7565b01613de7565b919695949392905f5b818110613e3f5750505050612835939450613aa1565b80613e568a61243f8387613e14600197898c613da4565b01613e29565b613e68613e8591614a0b565b6001600160a01b039091165f908152600160205260409020614a45565b5090565b613e92346147b6565b335f5260016020526001600160601b03613eb360405f209282845416613a71565b166001600160601b03198254161790556040513481527fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c60203392a2565b60405190613f006080836127a8565b605a82527f6c2c496e70757420696e7075742c4f66666572206f66666572290000000000006060837f50726f6f66526571756573742875696e743235362069642c526571756972656d60208201527f656e747320726571756972656d656e74732c737472696e6720696d616765557260408201520152565b60405190613f876060836127a8565b60268252654c696d69742960d01b6040837f43616c6c6261636b286164647265737320616464722c75696e7439362067617360208201520152565b60405190613fd16060836127a8565b60218252602960f81b6040837f496e7075742875696e743820696e707574547970652c6279746573206461746160208201520152565b6040519061401660c0836127a8565b6084825263616b652960e01b60a0837f4f666665722875696e74323536206d696e50726963652c75696e74323536206d60208201527f617850726963652c75696e7436342062696464696e6753746172742c75696e7460408201527f33322072616d705570506572696f642c75696e743332206c6f636b54696d656f60608201527f75742c75696e7433322074696d656f75742c75696e74323536206c6f636b537460808201520152565b604051906140d06060836127a8565b602982526874657320646174612960b81b6040837f5072656469636174652875696e743820707265646963617465547970652c627960208201520152565b6040519061411d6080836127a8565b605382527274652c6279746573342073656c6563746f722960681b6060837f526571756972656d656e7473286279746573333220696d61676549642c43616c60208201527f6c6261636b2063616c6c6261636b2c507265646963617465207072656469636160408201520152565b614193613ef1565b61419b613f78565b6141a3613fc2565b906141ac614007565b6141b46140c1565b6141bc61410e565b916040519485946020860197805160208192018a5e860160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815203601f198101825261423290826127a8565b51902090805190602081015161424661410e565b61424e613f78565b6142566140c1565b90604051918291602083019480516020819201875e830160208101915f83528051926020849201905e016020015f815281516020819301825e015f815203601f19810182526142a590826127a8565b5190209080519060208101516142b9613f78565b8051906020012090600160a01b6001900381511690602001516001600160601b0316604051916020830193845260408301526060820152606081526142ff6080826127a8565b5190209060408101516143106140c1565b805190602001209080519061432482613841565b6020015180519060200120604051916020830193845261434381613841565b604083015260608201526060815261435c6080826127a8565b5190209063ffffffff60e01b90606001511691604051936020850195865260408501526060840152608083015260a082015260a0815261439d60c0826127a8565b5190209060408101518051906020012060608201516143ba613fc2565b80519060200120908051906143ce82613841565b602001518051906020012060405191602083019384526143ed81613841565b60408301526060820152606081526144066080826127a8565b5190209160800151614416614007565b60405180602081019280516020819201855e8101602081015f905203602001601f198101825261444690826127a8565b5190209080519060208101519060408101516001600160401b0316606082015163ffffffff16608083015163ffffffff169160a084015163ffffffff169360c0015194604051966020880198895260408801526060870152608086015260a085015260c084015260e083015261010082015261010081526144c9610120826127a8565b51902092604051946020860196875260408601526060850152608084015260a083015260c082015260c0815261450060e0826127a8565b51902090565b9081602091031261242a57516001600160e01b03198116810361242a5790565b9291614538612c8d6109d73687612a24565b9335600160c01b16156145d1579160209161456a93604051809581948293630b135d3f60e11b84528960048501612cbb565b03916001600160a01b0316620186a0fa90811561241f575f916145b2575b506001600160e01b0319166374eca2c160e11b016145a35790565b638baa579f60e01b5f5260045ffd5b6145cb915060203d602011610be957610bdb81836127a8565b5f614588565b6145e36145e9916145f29436916127e4565b846159ba565b909391936159f4565b6001600160a01b039081169116036145a35790565b6146159060803691016129ae565b9081516020830151106146a35763ffffffff606083015116608083019063ffffffff825116106146a35763ffffffff90511660a083019063ffffffff825116106146a3576146829063ffffffff6001600160401b03604061467587615402565b96015116915116906146b2565b9162ffffff6001600160401b036146998386614ff9565b16116146a3579190565b6341abc80160e01b5f5260045ffd5b906001600160401b03809116911601906001600160401b038211613a5057565b9060408201906001600160401b03808351169116908111156147b0576001600160401b036146ff84615402565b1681116147a9576001600160401b03825116906001600160401b03614730606086019363ffffffff855116906146b2565b16811115614742575050506020015190565b61476f906001600160401b0363ffffffff6147636020880151885190613a64565b94511694511690613a64565b925192818102918183041490151715613a5057811561479557048101809111613a505790565b634e487b7160e01b5f52601260045260245ffd5b5050505f90565b50505190565b6001600160601b0381116147d0576001600160601b031690565b6306dfcc6560e41b5f52606060045260245260445ffd5b5f80516020615e11833981519152546001600160a01b0316330361480757565b63118cdaa760e01b5f523360045260245ffd5b9060018060a01b03821691825f5260016020526001600160601b0360405f2054166001600160601b0361484c846147b6565b16116148d9575f8080848194614861826147b6565b88845260016020526001600160601b03806040862092818454160316166001600160601b03198254161790555af1614897613d75565b50156148ca5760207f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6591604051908152a2565b6312171d8360e31b5f5260045ffd5b8263112fed8b60e31b5f5260045260245ffd5b8051156146a35760018151146149e05780515b60018111614915575061491190612e78565b5190565b60018101808211613a505760011c905f5b8160011c81106149745750600180821614614942575b506148ff565b5f198101908111613a50576149579083612e85565b515f198201828111613a505761496d9084612e85565b525f61493c565b600181901b906001600160ff1b0381168103613a50576149948286612e85565b5160018301809311613a50576149ac60019387612e85565b5190818110156149d1575f5260205260405f205b6149ca8287612e85565b5201614926565b905f5260205260405f206149c0565b61491190612e78565b6128359062ffffff60406001600160401b0360208401511692015116906146b2565b906001600160c11b031982166146a357602082901c6001600160a01b03169163ffffffff1690565b6302000000821015612e015701905f90565b63ffffffff821691906020831015614a97576401fffffffe905460c01c9160011b169180830460021490151715613a50576001600160401b03906003831b1616901c9060026001831615159216151590565b91614aa29150613a41565b908160011b9180830460021481151715613a505760ff91614ad29160071c6001600160f81b031690600101614a33565b90549060031b1c9116906003821b16901c9060026001831615159216151590565b906128359160208152813560208201526020820135604082015260408201356060820152614b46614b3b614b2a606085018561384b565b60a0608086015260c0850191612c9b565b92608081019061384b565b9160a0601f1982860301910152612c9b565b90915f9180356060614b6982614a0b565b60018060a01b0382165f526001602052614b868160405f20614a45565b92908094604051614b968161270d565b5f81525f60208201525f60408201525f828201525f60808201525f60a08201525f60c082015291614dfa575b50602087013594614bd1615425565b50855c95614bdd615425565b506040519082906001607f1b89161515614bf68461278d565b8084526001600160601b03604060208601956001607e1b8d1615158752019a168a525f14614d7f57505051614d10577f3eb6e922886168616596d8bf7407911d08725b2cf3a578ddc363df13fe2f415795938b9388614c9897948b945b15614cf85760208101516001600160401b03164211614cdb57614c76975061579c565b965b8751614c9d575b6040516001600160a01b03909116949091829182614af3565b0390a3565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb35646040516020815280614cd3602082018c612635565b0390a1614c7f565b9291906001600160601b03614cf298511693615589565b96614c78565b5050906001600160601b03614cf29651169189615443565b50505050505092505091506040519063873fd26b60e01b6020830152602482015260248152614d406044826127a8565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb35646040516020815280614d766020820185612635565b0390a190600190565b90915081614dec575b5015614dd957614d97826149e9565b6001600160401b0342911610614d10577f3eb6e922886168616596d8bf7407911d08725b2cf3a578ddc363df13fe2f415795938b9388614c9897948b94614c53565b8663c274d3e360e01b5f5260045260245ffd5b905060c0830151145f614d88565b9050855f525f602052600260405f206001600160601b0360405193614e1e8561270d565b825460018060a01b03811686526001600160401b038160a01c16602087015262ffffff8160e01c16604087015260f81c8186015260018301549082821660808701521c1660a0840152015460c08201525f614bc2565b5f80516020615ef183398151915280546001600160a01b03199081169091555f80516020615e1183398151915280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b6040516323b872dd60e01b81526001600160a01b03918216600482018190523060248301526044820184905292917f000000000000000000000000000000000000000000000000000000000000000016906020905f9060649082855af19081601f3d1160015f5114161516614fec575b5015614fb057602081614fa7614f857f1da2c4060997c108162f55c30ad2268924b069210f8c686ba302e1acc6909ccc946147b6565b855f5260018452611dae60405f20916001600160601b03835460601c16613a71565b604051908152a2565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b3b153d171590505f614f4f565b906001600160401b03809116911603906001600160401b038211613a5057565b9590929796949360018060a01b031697885f52600160205261503e8560405f20614a45565b906153d1576153bd576001600160401b038616988942116153a55761506c611d5e612c283660808c016129ae565b96815f52600160205260405f20996001600160601b038b5416946001600160601b038a1693848710615393575060018060a01b031698895f52600160205260405f20906001600160601b03825460601c16966101408d013580981061538057918d6001600160601b0380615112946151179897960316166001600160601b03198254161790556001600160601b03615103896147b6565b81835460601c16031690612cd2565b614ff9565b926001600160401b03841662ffffff81116153695750615136906147b6565b604051936151438561270d565b88855260208086019c8d5262ffffff90911660408087019182525f60608801818152608089019687526001600160601b0390951660a0808a0191825260c08a019889528e35808452958390529290912097519e51925194519290911b67ffffffffffffffff60a01b166001600160a01b039e909e169d909d1760e09390931b62ffffff60e01b169290921760f89290921b6001600160f81b031916919091178455996001840191516001600160601b03166001600160601b03166001600160601b0319835416178255516001600160601b031661521f91612cd2565b51906002015563ffffffff831692602084105f146152da576401fffffffe9060011b169280840460021490151715613a505785546001600160c01b038116600190941b6001600160401b031660c091821c17901b6001600160c01b031916929092179094557f6f5de72b704dc2c15a0930f92bc5d83253c73358372a4fe4d004ec1a35d7569a936152d5915b6152c7604051958695865260606020870152606086019061387c565b918483036040860152612c9b565b0390a2565b50916152e590613a41565b918260011b9583870460021484151715613a50577f6f5de72b704dc2c15a0930f92bc5d83253c73358372a4fe4d004ec1a35d7569a966152d5946153649260ff916001916153419160071c6001600160f81b0316908301614a33565b929093161b82548260031b1c179082549060031b91821b915f19901b1916179055565b6152ab565b6306dfcc6560e41b5f52601860045260245260445ffd5b8b63112fed8b60e31b5f5260045260245ffd5b63112fed8b60e31b5f5260045260245ffd5b898863cfe6a8fd60e01b5f523560045260245260445ffd5b86631cfdeebb60e01b5f523560045260245ffd5b8763a905765160e01b5f523560045260245ffd5b6042916040519161190160f01b8352600283015260228201522090565b6128359063ffffffff60806001600160401b0360408401511692015116906146b2565b604051906154328261278d565b5f6040838281528260208201520152565b969495919293909660609661553c575f80516020615dd18339815191526154a19596979860018060a01b031693845f52600160205261548660405f209687615c3d565b6040516001600160a01b039190911696879482919082614af3565b0390a36001600160601b03825416906001600160601b038516821061551057506001600160601b038481920316166001600160601b03198254161790555f5260016020526001600160601b036154fe60405f209282845416613a71565b166001600160601b0319825416179055565b949550505050506040519063112fed8b60e31b60208301526024820152602481526128356044826127a8565b955050505050915060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b906001600160601b03809116911603906001600160601b038211613a5057565b929796949093959760609860016060860151161515801561578c575b61575d571561570c575b505060018060a01b0316805f5260016020526001600160601b03608060405f209301511685816001600160601b038216115f146156d757906155f091615569565b906001600160601b03835416906001600160601b03831682106156a9575082546bffffffffffffffffffffffff19169190036001600160601b03161790555b5f90815260208190526040902080546affffffffffffffffffffff60a01b81166001600160a01b0384169081176001600160a01b0319929092161760f890811c600217901b6001600160f81b03191617905560018060a01b03165f5260016020526001600160601b036154fe60405f209282845416613a71565b9697505050505050506040519063112fed8b60e31b60208301526024820152602481526128356044826127a8565b6001600160601b0392506156ee906156f792615569565b82845416613a71565b166001600160601b031982541617905561562f565b6001600160a01b0383165f90815260016020526040902061572d9190615c3d565b835f80516020615dd18339815191526040518061575360018060a01b038a169582614af3565b0390a35f806155af565b5050505050929350505060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b50600260608601511615156155a5565b939190929695949660609760016060870151161515801561591e575b6158f057156158a0575b505082516001600160a01b03948516941684148015919061588c575b506158625760a06131cb93926001600160601b03925f525f6020525f6001604082208160f81b828060f81b03825416178155015582608082015116845f5260016020528361583360405f209282845416613a71565b168419825416179055015116905f526001602052611dae60405f20916001600160601b03835460601c16613a71565b92935050506040519063a905765160e01b60208301526024820152602481526128356044826127a8565b9050602060c084015191013514155f6157de565b6158bc9160018060a01b03165f52600160205260405f20615c3d565b6040516001600160a01b0385169083905f80516020615dd183398151915290806158e68682614af3565b0390a35f806157c2565b50505050929350505060405190631cfdeebb60e01b60208301526024820152602481526128356044826127a8565b50600260608701511615156157b8565b615936615a54565b61593e615b5b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261450060c0826127a8565b60ff5f80516020615eb18339815191525460401c16156159ab57565b631afcd79f60e31b5f5260045ffd5b81519190604183036159ea576159e39250602082015190606060408401519301515f1a90615cda565b9192909190565b50505f9160029190565b60048110156137465780615a06575050565b60018103615a1d5763f645eedf60e01b5f5260045ffd5b60028103615a38575063fce698f760e01b5f5260045260245ffd5b600314615a425750565b6335e2f38360e21b5f5260045260245ffd5b6040515f80516020615df183398151915254905f81615a7284612d1d565b9182825260208201946001811690815f14615b3f5750600114615ae7575b615a9c925003826127a8565b51908115615aa8572090565b50505f80516020615e51833981519152548015615ac25790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b505f80516020615df18339815191525f90815290915f80516020615e718339815191525b818310615b23575050906020615a9c92820101615a90565b6020919350806001915483858801015201910190918392615b0b565b60ff1916865250615a9c92151560051b82016020019050615a90565b6040515f80516020615e3183398151915254905f81615b7984612d1d565b9182825260208201946001811690815f14615c215750600114615bc9575b615ba3925003826127a8565b51908115615baf572090565b50505f80516020615ed1833981519152548015615ac25790565b505f80516020615e318339815191525f90815290915f80516020615f118339815191525b818310615c05575050906020615ba392820101615b97565b6020919350806001915483858801015201910190918392615bed565b60ff1916865250615ba392151560051b82016020019050615b97565b9063ffffffff8116906020821015615c9a576401fffffffe9060011b169080820460021490151715613a505781546001600160c01b038116600290921b6001600160401b031660c091821c17901b6001600160c01b031916179055565b50615ca490613a41565b8060011b9080820460021481151715613a50576131cb9260ff916002916153419160071c6001600160f81b031690600101614a33565b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615d47579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561241f575f516001600160a01b03811615615d3d57905f905f90565b505f906001905f90565b5050505f9160039190565b90615d765750805115615d6757805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580615da7575b615d87575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15615d7f56fe405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace2682b2738d8666734f483aa3e081360183bbd5428f40ad104639f5961025142ba16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1029016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10042ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c005f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75a164736f6c634300081a000a")] + #[sol(rpc, bytecode = "610100346101b957601f615ddc38819003918201601f19168301916001600160401b038311848410176101bd578084926060946040528339810103126101b9578051906001600160a01b03821682036101b9576020810151604090910151916001600160a01b03831683036101b9573060805260a05260c05260e0527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166101aa576002600160401b03196001600160401b03821601610141575b604051615c0a90816101d282396080518181816118e9015261197c015260a05181818161231a0152818161311f015281816133e60152613495015260c051818181610e090152818161115d0152613189015260e051818181611386015281816114cc0152818161186801528181611d0e015281816121ab01526145070152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a15f6100c1565b63f92ee8a960e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c90816308c84e7014612308575080630b7ae1a71461227b57806315d7a240146122605780631ce030241461224257806325d5971f1461210b5780632abff1f2146120005780632e107a9014611f7e5780632e1a7d4d14611f6057806341451f9414611eaf57806341d3ab6914611e9457806345bc4d1014611b265780634f1ef2861461193d57806352d1902d146118d6578063553c0248146118ba5780635b07fdd8146118975780635fbb49211461185257806360dfd4a9146117ba578063612bee0c1461179957806363555624146116d557806370a08231146116925780637136a7f31461167a578063715018a6146115fb5780637870d481146115da57806379ba50971461158d5780638040fdc0146114245780638094e6141461136057806381bf6c241461131757806384b0196e146111ef5780638da5cb5b146111ba578063956b09601461119d5780639f04f420146111805780639fe9428c14611145578063ad2fa6c8146110bd578063ad3cb1cc14611074578063ae7330f114610fd6578063c515c15f14610f51578063c64067a214610f39578063cb09e7c014610ef3578063cb74db1114610eca578063cb82cc8f14610eac578063cdc9712314610db6578063d0e30db014610da2578063d4bd257b14610d05578063df2e670614610c93578063e30c397814610c5e578063eba2ecc814610c21578063f2800f1a14610bca578063f2fde38b14610b44578063f399e22e14610573578063ff1214a51461026d5763ffa1ad741461024f575f80fd5b3461026a578060031936011261026a57602060405160018152f35b80fd5b503461026a57606036600319011261026a576004356001600160401b03811161056f576101608160040191600319903603011261056f576024356001600160401b03811161056b576102c3903690600401612373565b916044356001600160401b038111610567576102e3903690600401612373565b8235916102ef83614278565b9290916103046102ff36886136ce565b61487b565b6103158161031061575b565b6157bc565b95600160c01b161561052657604051630b135d3f60e11b8152602081806103418d8d8c60048501612826565b03816001600160a01b0389165afa90811561051b578b916104ec575b506001600160e01b0319166374eca2c160e11b016104dd575b60405161038460608261268a565b60218152602081017f4c6f636b526571756573742850726f6f66526571756573742072657175657374815260408201602960f81b90526103c2614678565b906103cb6146c2565b8d6103d4614707565b6103dc6147c1565b6103e46145f1565b916103ed61480e565b94604051978897602089019a5180918c5e880160208101918783528051926020849201905e0160200185815281516020819301825e0184815281516020819301825e0183815281516020819301825e0182815281516020819301825e0190815281516020819301825e018d815203601f198101825261046c908261268a565b51902090604051906020820192835260408201526040815261048f60608261268a565b51902061049a61575b565b906104a4916157bc565b9136906104b0926126c6565b6104b9916158b0565b6104c5919592956158ea565b6104ce85614ce6565b966104da989196614e86565b80f35b638baa579f60e01b8a5260048afd5b61050e915060203d602011610514575b610506818361268a565b810190614be5565b5f61035d565b503d6104fc565b6040513d8d823e3d90fd5b61054661053d610537368c8c6126c6565b886158b0565b909291926158ea565b6001600160a01b0385811691161461037657638baa579f60e01b8a5260048afd5b8480fd5b8280fd5b5080fd5b503461026a57604036600319011261026a5761058d612349565b906024356001600160401b03811161056f576105ad903690600401612373565b90925f80516020615b9e833981519152549060ff8260401c1615916001600160401b03811680159081610b3c575b6001149081610b32575b159081610b29575b50610b1a5767ffffffffffffffff1981166001175f80516020615b9e8339815191525582610aee575b5061061f6157fc565b6106276157fc565b6001600160a01b03811615610ada5761063f9061446c565b6106476157fc565b6040918251610656848261268a565b601081526f12509bdd5b991b195cdcd3585c9ad95d60821b6020820152835190610680858361268a565b60018252603160f81b60208301526106966157fc565b61069e6157fc565b8051906001600160401b038211610ac6576106c65f80516020615ade833981519152546128f1565b601f8111610a57575b50602090601f83116001146109db576106ff92918891836108d1575b50508160011b915f199060031b1c19161790565b5f80516020615ade833981519152555b8051906001600160401b0382116109c7576107375f80516020615b1e833981519152546128f1565b601f8111610958575b50602090601f83116001146108dc5761076f92918791836108d15750508160011b915f199060031b1c19161790565b5f80516020615b1e833981519152555b835f80516020615b3e83398151915255835f80516020615bbe833981519152556001600160401b0381116108bd576107c1816107bc6002546128f1565b612929565b83601f821160011461084e57819085966107f0949596926108435750508160011b915f199060031b1c19161790565b6002555b6107fc575080f35b5f80516020615b9e833981519152805460ff60401b1916905551600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a180f35b013590505f806106eb565b60028552601f198216955f80516020615abe83398151915291865b8881106108a55750836001959697981061088c575b505050811b016002556107f4565b01355f19600384901b60f8161c191690555f808061087e565b90926020600181928686013581550194019101610869565b634e487b7160e01b84526041600452602484fd5b015190505f806106eb565b5f80516020615b1e83398151915287528187209190601f198416885b8181106109405750908460019594939210610928575b505050811b015f80516020615b1e8339815191525561077f565b01515f1960f88460031b161c191690555f808061090e565b929360206001819287860151815501950193016108f8565b5f80516020615b1e83398151915287527f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c810191602085106109bd575b601f0160051c01905b8181106109b25750610740565b8781556001016109a5565b909150819061099c565b634e487b7160e01b86526041600452602486fd5b5f80516020615ade83398151915288528188209190601f198416895b818110610a3f5750908460019594939210610a27575b505050811b015f80516020615ade8339815191525561070f565b01515f1960f88460031b161c191690555f8080610a0d565b929360206001819287860151815501950193016109f7565b5f80516020615ade83398151915288527f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f840160051c81019160208510610abc575b601f0160051c01905b818110610ab157506106cf565b888155600101610aa4565b9091508190610a9b565b634e487b7160e01b87526041600452602487fd5b631e4fbdf760e01b84526004849052602484fd5b68ffffffffffffffffff191668010000000000000001175f80516020615b9e833981519152555f610616565b63f92ee8a960e01b8552600485fd5b9050155f6105ed565b303b1591506105e5565b8491506105db565b503461026a57602036600319011261026a57610b5e612349565b610b66613e65565b5f80516020615bde83398151915280546001600160a01b0319166001600160a01b039283169081179091555f80516020615afe833981519152549091167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227008380a380f35b503461026a57602036600319011261026a5760043590610be982613947565b15610c0f576040816020936001600160401b039352808452205460a01c16604051908152f35b60249163d2be005d60e01b8252600452fd5b503461026a576104da610c33366127b3565b91610c3e8135614278565b90610c4b85858386614c05565b610c5484614ce6565b9690953395614e86565b503461026a578060031936011261026a575f80516020615bde833981519152546040516001600160a01b039091168152602090f35b507fc354af001adff0e8c35481c5ce3df3edee370c71572514d281e884c8cb552203610cbe366127b3565b9291909234610cf8575b610cf260405192839260408452610ce26040850183613ba8565b9184830360208601523596612806565b0390a280f35b610d00613aee565b610cc8565b503461026a57610d143661257b565b969095919490936001600160a01b039092169190823b156105675791610d55939185809460405196879586948593636691f64760e01b855260048501612826565b03925af18015610d9757610d82575b610d7e610d72868686612851565b604051918291826124c7565b0390f35b610d8d82809261268a565b61026a5780610d64565b6040513d84823e3d90fd5b508060031936011261026a576104da613aee565b503461026a578060031936011261026a57604051908060025490610dd9826128f1565b8085529160018116908115610e855750600114610e3b575b610d7e84610e018186038261268a565b6040519182917f000000000000000000000000000000000000000000000000000000000000000083526040602084015260408301906124a3565b600281525f80516020615abe833981519152939250905b808210610e6b57509091508101602001610e0182610df1565b919260018160209254838588010152019101909291610e52565b60ff191660208087019190915292151560051b85019092019250610e019150839050610df1565b503461026a57602036600319011261026a576104da600435336144d7565b503461026a57602036600319011261026a576020610ee9600435613947565b6040519015158152f35b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b03610f24612349565b16815260018452205460601c16604051908152f35b503461026a576104da610f4b366127b3565b9161387f565b503461026a57602036600319011261026a57604060e091600435815280602052208054906001600160601b0360026001830154920154916040519360018060a01b03811685526001600160401b038160a01c16602086015262ffffff81871c16604086015260f81c6060850152818116608085015260601c1660a083015260c0820152f35b503461026a57606036600319011261026a5780610ff1612349565b6044356001600160401b03811161107057611010903690600401612373565b6001600160a01b0390921691823b1561106b5761104992849283604051809681958294636691f64760e01b845260243560048501612826565b03925af18015610d975761105a5750f35b816110649161268a565b61026a5780f35b505050fd5b5050fd5b503461026a578060031936011261026a5750610d7e60405161109760408261268a565b60058152640352e302e360dc1b60208201526040519182916020835260208301906124a3565b503461026a576110cc366123d0565b9a93969297909960018060a09b949b9897981b031691823b15610567579161110f939185809460405196879586948593636691f64760e01b855260048501612826565b03925af18015610d9757611130575b610d7e610d728a8a8a8a8a8a8a6135f7565b61113b82809261268a565b61026a578061111e565b503461026a578060031936011261026a5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b503461026a578060031936011261026a57602060405161c3508152f35b503461026a578060031936011261026a5760206040516107d08152f35b503461026a578060031936011261026a575f80516020615afe833981519152546040516001600160a01b039091168152602090f35b503461026a578060031936011261026a575f80516020615b3e833981519152541580611301575b156112c45761126890611227613974565b90611230613a41565b90602061127660405193611244838661268a565b8385525f368137604051968796600f60f81b885260e08589015260e08801906124a3565b9086820360408801526124a3565b904660608601523060808601528260a086015284820360c08601528080855193848152019401925b8281106112ad57505050500390f35b83518552869550938101939281019260010161129e565b60405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b6044820152606490fd5b505f80516020615bbe8339815191525415611216565b503461026a57602036600319011261026a57611354602091604061133c600435614278565b6001600160a01b0390911683526001855291206142c1565b90506040519015158152f35b503461026a5760a036600319011261026a576004358160443560ff811680910361056f577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690813b1561056b57829160e48392604051948593849263d505accf60e01b84523360048501523060248501528960448501526024356064850152608484015260643560a484015260843560c48401525af161140f575b506104da82336144d7565b816114199161268a565b61056f57815f611404565b503461026a57602036600319011261026a57600435611441613e65565b30825260016020526001600160601b03604083205460601c166001600160601b0361146b83613e34565b161161157a576114a161147d82613e34565b30845260016020526001600160601b03604085209181835460601c160316906128a6565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af190811561156f578391611540575b5015611531576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203092a280f35b6312171d8360e31b8252600482fd5b611562915060203d602011611568575b61155a818361268a565b8101906128d9565b5f6114fc565b503d611550565b6040513d85823e3d90fd5b63112fed8b60e31b825230600452602482fd5b503461026a578060031936011261026a575f80516020615bde83398151915254336001600160a01b03909116036115c7576104da3361446c565b63118cdaa760e01b815233600452602490fd5b503461026a57610d7e610d726115ef3661271a565b959490949391936135f7565b503461026a578060031936011261026a57611614613e65565b5f80516020615bde83398151915280546001600160a01b03199081169091555f80516020615afe8339815191528054918216905581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461026a576104da61168c36612526565b91612f19565b503461026a57602036600319011261026a576020906001600160601b03906040906001600160a01b036116c3612349565b16815260018452205416604051908152f35b503461026a57602036600319011261026a576004356116f2613e65565b30825260016020526001600160601b036040832054166001600160601b0361171983613e34565b161161157a5761172881613e34565b30835260016020526001600160601b03806040852092818454160316166001600160601b03198254161790558180808084335af1611764612e48565b5015611531576040519081527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6560203092a280f35b503461026a57610d7e610d726117ae3661271a565b95949094939193612e06565b503461026a57602036600319011261026a5760046060604060209383358152808552206002604051916117ec8361260a565b805460018060a01b03811684526001600160401b038160a01c168785015262ffffff8160e01c16604085015260f81c848401526001600160601b0360018201548181166080860152851c1660a0840152015460c082015201511615156040519015158152f35b503461026a578060031936011261026a576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461026a578060031936011261026a5760206118b261575b565b604051908152f35b503461026a578060031936011261026a57602090604051908152f35b503461026a578060031936011261026a577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316300361192e5760206040515f80516020615b5e8339815191528152f35b63703e46dd60e11b8152600490fd5b50604036600319011261026a57611952612349565b906024356001600160401b03811161056f576119729036906004016126fc565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016308114908115611b04575b50611af5576119b4613e65565b6040516352d1902d60e01b8152926001600160a01b0381169190602085600481865afa80958596611ac1575b506119f957634c9c8ce360e01b84526004839052602484fd5b9091845f80516020615b5e8339815191528103611aaf5750813b15611a9d575f80516020615b5e83398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8480a28151839015611a835780836020611a7f95519101845af4611a79612e48565b916159e7565b5080f35b50505034611a8e5780f35b63b398979f60e01b8152600490fd5b634c9c8ce360e01b8452600452602483fd5b632a87526960e21b8552600452602484fd5b9095506020813d602011611aed575b81611add6020938361268a565b810103126105675751945f6119e0565b3d9150611ad0565b63703e46dd60e11b8252600482fd5b5f80516020615b5e833981519152546001600160a01b0316141590505f6119a7565b503461026a57602036600319011261026a57600435611b67611b4782614278565b6001600160a01b03909116808552600160205260408520909291906142c1565b5015611e8057818352826020526040832060405190611b858261260a565b805460018060a01b03811683526001600160401b038160a01c16602084015262ffffff8160e01c16604084015260f81c60608301526001810154600260808401926001600160601b03831684526001600160601b0360a086019360601c168352015460c08401526004606084015116611e6c576001606084015116611e58576001600160401b03611c1584613eb8565b16421115611e2f5784865260208690526040862080546001600160f81b03811660f891821c60041790911b6001600160f81b0319161781558690600101556001600160601b038151166107d08102908082046107d01490151715611e1b57611c926001600160601b039392612710611c9793049485915116612aae565b613e34565b936002606060018060a01b038651169501511615155f14611db757505060018060a01b03821685526001602052611ce860408620611ce2856001600160601b03835460601c16612dac565b906128a6565b60405163a9059cbb60e01b815261dead60048201526024810182905291602083604481897f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015611dac577f79ca7c80cf57b513ffdf8aa37ec70e40757f5e0d35219241860bb4b4c2fa7616946060946001600160601b0392611d8f575b5060405193845216602083015260018060a01b03166040820152a280f35b611da79060203d6020116115685761155a818361268a565b611d71565b6040513d88823e3d90fd5b9092506001600160601b0330933088526001602052611de360408920611ce28885835460601c16612dac565b511690865260016020526001600160601b03611e06604088209282845416612dac565b166001600160601b0319825416179055611ce8565b634e487b7160e01b87526011600452602487fd5b6044866001600160401b0387611e4487613eb8565b9063079c66ab60e41b845260045216602452fd5b631cfdeebb60e01b86526004859052602486fd5b633231064d60e11b86526004859052602486fd5b63d2be005d60e01b83526004829052602483fd5b503461026a57610d7e610d72611ea936612526565b91612abb565b503461026a57602036600319011261026a5760043590611ece82613947565b15610c0f57604081602093611f4f935280845220600260405191611ef18361260a565b805460018060a01b03811684526001600160401b038160a01c168685015262ffffff8160e01c16604085015260f81c60608401526001600160601b036001820154818116608086015260601c1660a0840152015460c0820152613eb8565b6001600160401b0360405191168152f35b503461026a57602036600319011261026a576104da60043533613d62565b503461026a57611f8d3661257b565b969095919490936001600160a01b039092169190823b156105675791611fce939185809460405196879586948593636691f64760e01b855260048501612826565b03925af18015610d9757611feb575b610d7e610d72868686612abb565b611ff682809261268a565b61026a5780611fdd565b503461026a57602036600319011261026a576004356001600160401b03811161056f57612031903690600401612373565b61203c929192613e65565b6001600160401b0381116120f757612059816107bc6002546128f1565b81601f821160011461208c578190839461208694926108435750508160011b915f199060031b1c19161790565b60025580f35b60028352601f198216935f80516020615abe83398151915291845b8681106120df57508360019596106120c6575b505050811b0160025580f35b01355f19600384901b60f8161c191690555f80806120ba565b909260206001819286860135815501940191016120a7565b634e487b7160e01b82526041600452602482fd5b503461026a57602036600319011261026a5760043533825260016020526001600160601b03604083205460601c166001600160601b0361214a83613e34565b161161222f5761218061215c82613e34565b33845260016020526001600160601b03604085209181835460601c160316906128a6565b60405163a9059cbb60e01b815233600482015260248101829052602081604481866001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af190811561156f578391612210575b5015611531576040519081527ff0ed97f7b968f9d8268bc8d104a11b3586ceeadd0e0af5f73769e2b479f9d0ae60203392a280f35b612229915060203d6020116115685761155a818361268a565b5f6121db565b63112fed8b60e31b825233600452602482fd5b503461026a578060031936011261026a576020604051620186a08152f35b503461026a57610d7e610d7261227536612526565b91612851565b3461230457612289366123d0565b97999598909691959294929091906001600160a01b0316803b15612304576122cb9a5f80946040519d8e9586948593636691f64760e01b855260048501612826565b03925af19687156122f957610d7e98610d72986122e9575b50612e06565b5f6122f39161268a565b5f6122e3565b6040513d5f823e3d90fd5b5f80fd5b34612304575f366003190112612304577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b600435906001600160a01b038216820361230457565b35906001600160a01b038216820361230457565b9181601f84011215612304578235916001600160401b038311612304576020838186019501011161230457565b9181601f84011215612304578235916001600160401b038311612304576020808501948460051b01011161230457565b60e0600319820112612304576004356001600160a01b03811681036123045791602435916044356001600160401b038111612304578161241291600401612373565b929092916064356001600160401b0381116123045781612434916004016123a0565b929092916084356001600160401b0381116123045781612456916004016123a0565b9290929160a4356001600160401b0381116123045781612478916004016123a0565b9290929160c435906001600160401b0382116123045760809082900360031901126123045760040190565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b602081016020825282518091526040820191602060408360051b8301019401925f915b8383106124f957505050505090565b9091929394602080612517600193603f1986820301875289516124a3565b970193019301919392906124ea565b6040600319820112612304576004356001600160401b0381116123045781612550916004016123a0565b92909291602435906001600160401b0382116123045760809082900360031901126123045760040190565b60a0600319820112612304576004356001600160a01b03811681036123045791602435916044356001600160401b03811161230457816125bd91600401612373565b929092916064356001600160401b03811161230457816125df916004016123a0565b92909291608435906001600160401b0382116123045760809082900360031901126123045760040190565b60e081019081106001600160401b0382111761262557604052565b634e487b7160e01b5f52604160045260245ffd5b608081019081106001600160401b0382111761262557604052565b604081019081106001600160401b0382111761262557604052565b606081019081106001600160401b0382111761262557604052565b90601f801991011681019081106001600160401b0382111761262557604052565b6001600160401b03811161262557601f01601f191660200190565b9291926126d2826126ab565b916126e0604051938461268a565b829481845281830111612304578281602093845f960137010152565b9080601f8301121561230457816020612717933591016126c6565b90565b6080600319820112612304576004356001600160401b0381116123045781612744916004016123a0565b929092916024356001600160401b0381116123045781612766916004016123a0565b929092916044356001600160401b0381116123045781612788916004016123a0565b92909291606435906001600160401b0382116123045760809082900360031901126123045760040190565b906040600319830112612304576004356001600160401b0381116123045761016081840360031901126123045760040191602435906001600160401b0382116123045761280291600401612373565b9091565b908060209392818452848401375f828201840152601f01601f1916010190565b604090612717949281528160208201520191612806565b356001600160a01b03811681036123045790565b8260609261286192959495612abb565b92016001600160a01b036128748261283d565b165f5260016020526001600160601b0360405f20541680612893575050565b61289f6128a49261283d565b613d62565b565b80546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055565b90816020910312612304575180151581036123045790565b90600182811c9216801561291f575b602083101461290b57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612900565b601f8111612935575050565b60025f5260205f20906020601f840160051c8301931061296f575b601f0160051c01905b818110612964575050565b5f8155600101612959565b9091508190612950565b6001600160401b0381116126255760051b60200190565b903590601e198136030182121561230457018035906001600160401b0382116123045760200191606082023603831361230457565b91908110156129d5576060020190565b634e487b7160e01b5f52603260045260245ffd5b3561ffff811681036123045790565b8051156129d55760200190565b80518210156129d55760209160051b010190565b91908110156129d55760051b8101359060be1981360301821215612304570190565b60021115612a4557565b634e487b7160e01b5f52602160045260245ffd5b903590601e198136030182121561230457018035906001600160401b0382116123045760200191813603831361230457565b601f19810191908211612a9a57565b634e487b7160e01b5f52601160045260245ffd5b91908203918211612a9a57565b929192612ac9848383612f19565b612ad282612979565b93612ae0604051958661268a565b828552601f19612aef84612979565b015f5b818110612d9b57505084612b0584612979565b612b12604051918261268a565b848152601f19612b2186612979565b013660208301376020830194612b378685612990565b90505f5b818110612d5c5750505f5b818110612b565750505050505050565b612b61818388612a19565b90612b77612b716060880161283d565b83613f5c565b90612b828388612a05565b52612d5357612b918185612a05565b516060830135600281101561230457612ba981612a3b565b60018103612d185750612bbf6080840184612a59565b5092604084013584019180612bdd575b505050600191505b01612b46565b612be78b8a612990565b90915f19810191908211612a9a57612bfe926129c5565b916040612c0d6020850161283d565b930135926001600160601b03841680940361230457612c2f60a0840184612a59565b6001600160a01b039092169491929091853b156123045760205f8760019a612c9d8397612c8b996040519a8b998a98899663a12da43f60e01b885201356004870152606060248701526064860190604060208201359101612806565b84810360031901604486015291612806565b0393f19081612d08575b50612d01577f5c5960582bfc7a494183b4e9a66bfe8ecffc07a83a48d136e732400f7b98bf5090612cd6612e48565b92612cf5604051928392835260406020840152359460408301906124a3565b0390a25b5f8080612bcf565b5050612cf9565b5f612d129161268a565b5f612ca7565b90919250612d2581612a3b565b612d4457612d3557600190612bd7565b63b90a25b160e01b5f5260045ffd5b6304e40c0360e21b5f5260045ffd5b60019150612bd7565b612d7081612d6a8a89612990565b906129c5565b9060018101808211612a9a57612d9461ffff612d8d6001956129e9565b1687612a05565b5201612b3b565b806060602080938a01015201612af2565b906001600160601b03809116911601906001600160601b038211612a9a57565b91908110156129d55760051b8101359061015e1981360301821215612304570190565b908210156129d5576128029160051b810190612a59565b919695949392905f5b818110612e255750505050612717939450612851565b80612e428a610f4b8387612e3c600197898c612dcc565b93612def565b01612e0f565b3d15612e72573d90612e59826126ab565b91612e67604051938461268a565b82523d5f602084013e565b606090565b903590601e198136030182121561230457018035906001600160401b03821161230457602001918160061b3603831361230457565b91908110156129d55760061b0190565b6020815260406020612ed88451838386015260608501906124a3565b93015191015290565b359061ffff8216820361230457565b35906001600160601b038216820361230457565b35906001600160e01b03198216820361230457565b61ffff82116135de57612f2b82612979565b90612f39604051928361268a565b828252601f19612f4884612979565b01366020840137612f5883612979565b612f65604051918261268a565b838152601f19612f7485612979565b013660208301376040850193612f8a8587612e77565b90505f5b8181106135245750505f5b8181106132d25750505050612fad9061436f565b612fc6612fbd6020850185612990565b91909385612e77565b612fd56060879693960161283d565b9160405193612fe385612639565b612fec81612979565b91612ffa604051938461268a565b818352606060208401920281019036821161230457915b81831061328157505050835261302681612979565b94613034604051968761268a565b818652602086019160061b81019036821161230457915b818310613242575050506020820193845260408201928352606082019060018060a01b031681526040519260208401946020865260c08501935193608060408701528451809152602060e087019501905f5b8181106131fd575050505192603f19858203016060860152602080855192838152019401905f5b8181106131cd5750509051608085015250516001600160a01b031660a0830152819003601f19810182526020925f9290916130ff908261268a565b604051918291518091835e8101838152039060025afa156122f9575f51907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906131529080612a59565b9091803b15612304575f92613186926040519586948593849363ab750e7560e01b8552606060048601526064850191612806565b907f00000000000000000000000000000000000000000000000000000000000000006024840152604483015203915afa80156122f9576131c35750565b5f6128a49161268a565b8251805161ffff1687526020908101516001600160e01b03191681880152604090960195909201916001016130c4565b8251805161ffff1688526020818101516001600160a01b0316818a01526040918201516001600160601b0316918901919091526060909701969092019160010161309d565b604083360312612304576020604091825161325c81612654565b61326586612ee1565b8152613272838701612f04565b8382015281520192019161304b565b60608336031261230457602060609160405161329c8161266f565b6132a586612ee1565b81526132b283870161235f565b838201526132c260408701612ef0565b6040820152815201920191613011565b6132dd818386612a19565b906040820135916040516132f081612639565b82815260208101823581526040820160208401358152606083018681526080605660405161331e838261268a565b81815275742c6279746573333220636c61696d4469676573742960501b606060208301927f4173736573736f72436f6d6d69746d656e742875696e7432353620696e64657884527f2c75696e743235362069642c6279746573333220726571756573744469676573604082015201522094519351925191519260405194602086019687526040860152606085015283015260a082015260a081526133c360c08261268a565b5190206133d08389612a05565b526133db8286612a05565b5161348f57613433927f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169161341d9060a0810190612a59565b94906040519561342c87612654565b36916126c6565b84526020840152803b1561230457613462925f916040518080968194631599ead560e01b835260048301612ebc565b039161c350fa9182156122f95760019261347f575b505b01612f99565b5f6134899161268a565b5f613477565b6134cc927f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169161341d9060a0810190612a59565b84526020840152803b15612304576134fb925f916040518080968194631599ead560e01b835260048301612ebc565b03915afa9182156122f957600192613514575b50613479565b5f61351e9161268a565b5f61350e565b602061353a826135348a8c612e77565b90612eac565b013563ffffffff60e01b81168091036123045761357f61357561ffff61356d613568866135348f8f90612e77565b6129e9565b168689612a19565b60a0810190612a59565b6004929192116123045760016135b761ffff6135b0613568878f978f6135349163ffffffff60e01b90351699612e77565b1688612a05565b528181036135c9575050600101612f8e565b632e2ce35360e21b5f5260045260245260445ffd5b506377e4aa5360e11b5f5260045261ffff60245260445ffd5b919695949392905f5b8181106136165750505050612717939450612abb565b8061362d8a610f4b8387612e3c600197898c612dcc565b01613600565b35906001600160401b038216820361230457565b359063ffffffff8216820361230457565b91908260e0910312612304576040516136708161260a565b60c0808294803584526020810135602085015261368f60408201613633565b60408501526136a060608201613647565b60608501526136b160808201613647565b60808501526136c260a08201613647565b60a08501520135910152565b919061016083820312612304576040519060a082018281106001600160401b038211176126255760405281938035835260208101356001600160401b038111612304578101808303906080821261230457604080519261372d8461266f565b126123045760405161373e81612654565b6137478261235f565b815261375560208301612ef0565b6020820152825260408101356001600160401b038111612304578101604081860312612304576040519161378883612654565b813560038110156123045783526020820135926001600160401b038411612304576137ba876137ca95606095016126fc565b6020820152602085015201612f04565b6040820152602084015260408101356001600160401b03811161230457810182601f820112156123045782816020613804933591016126c6565b604084015260608101356001600160401b03811161230457810191604083820312612304576040519261383684612654565b803560028110156123045784526020810135926001600160401b0384116123045760809461386a8461387a968895016126fc565b6020820152606087015201613658565b910152565b91823591600160c01b831615613926576138a59260201c6001600160a01b031684614c05565b905b60406138e4611c926138d46138bb85614ce6565b90506001600160401b0342911610946080369101613658565b6001600160401b03421690614d82565b6001600160601b038251916138f88361266f565b60018352602083018590521691018190526001607f1b9115613920576001607e1b5b1717905d565b5f61391a565b5050506139416139396102ff36846136ce565b61031061575b565b906138a7565b61395361397091614278565b6001600160a01b039091165f9081526001602052604090206142c1565b5090565b604051905f825f80516020615ade8339815191525491613993836128f1565b8083529260018116908115613a2257506001146139b7575b6128a49250038361268a565b505f80516020615ade8339815191525f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b818310613a065750509060206128a4928201016139ab565b60209193508060019154838589010152019101909184926139ee565b602092506128a494915060ff191682840152151560051b8201016139ab565b604051905f825f80516020615b1e8339815191525491613a60836128f1565b8083529260018116908115613a225750600114613a83576128a49250038361268a565b505f80516020615b1e8339815191525f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b818310613ad25750509060206128a4928201016139ab565b6020919350806001915483858901015201910190918492613aba565b613af734613e34565b335f5260016020526001600160601b03613b1860405f209282845416612dac565b166001600160601b03198254161790556040513481527fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c60203392a2565b9035603e1982360301811215612304570190565b906003821015612a455752565b9035601e19823603018112156123045701602081359101916001600160401b03821161230457813603831361230457565b90813581526020820135607e198336030181121561230457610160602083015282016001600160a01b03613bdb8261235f565b166101608301526001600160601b03613bf660208301612ef0565b16610180830152613c0a6040820182613b56565b9060806101a0840152813591600383101561230457613c42613c5591613c38613c8e956101e0880190613b6a565b6020810190613b77565b6040610200870152610220860191612806565b906001600160e01b031990613c6c90606001612f04565b166101c0840152613c806040850185613b77565b908483036040860152612806565b613c9b6060840184613b56565b82820360608401528035600281101561230457610140926040613cd2859484613cc6613ce296612a3b565b84526020810190613b77565b9190928160208201520191612806565b936080810135608085015260a081013560a08501526001600160401b03613d0b60c08301613633565b1660c085015263ffffffff613d2260e08301613647565b1660e085015263ffffffff613d3a6101008301613647565b1661010085015263ffffffff613d536101208301613647565b16610120850152013591015290565b9060018060a01b03821691825f5260016020526001600160601b0360405f2054166001600160601b03613d9484613e34565b1611613e21575f8080848194613da982613e34565b88845260016020526001600160601b03806040862092818454160316166001600160601b03198254161790555af1613ddf612e48565b5015613e125760207f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b6591604051908152a2565b6312171d8360e31b5f5260045ffd5b8263112fed8b60e31b5f5260045260245ffd5b6001600160601b038111613e4e576001600160601b031690565b6306dfcc6560e41b5f52606060045260245260445ffd5b5f80516020615afe833981519152546001600160a01b03163303613e8557565b63118cdaa760e01b5f523360045260245ffd5b906001600160401b03809116911601906001600160401b038211612a9a57565b6127179062ffffff60406001600160401b036020840151169201511690613e98565b906020825280356020830152602081013560408301526040810135606083015260608101359160028310156123045782613f1661271794612a3b565b6080820152613f4a613f3f613f2e6080850185613b77565b60c060a086015260e0850191612806565b9260a0810190613b77565b9160c0601f1982860301910152612806565b90915f9180356060613f6d82614278565b60018060a01b0382165f526001602052613f8a8160405f206142c1565b92908094604051613f9a8161260a565b5f81525f60208201525f60408201525f828201525f60808201525f60a08201525f60c0820152916141fe575b50602087013594613fd5615252565b50855c95613fe1615252565b506040519082906001607f1b89161515613ffa8461266f565b8084526001600160601b03604060208601956001607e1b8d1615158752019a168a525f1461418357505051614114577faf1db8f86d3f32029a484ff54c7ac1d7ef8f038ab050fc065af9e82eb9b850ca95938b938861409c97948b945b156140fc5760208101516001600160401b031642116140df5761407a97506155c9565b965b87516140a1575b6040516001600160a01b03909116949091829182613eda565b0390a3565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb356460405160208152806140d7602082018c6124a3565b0390a1614083565b9291906001600160601b036140f6985116936153b6565b9661407c565b5050906001600160601b036140f69651169189615270565b50505050505092505091506040519063873fd26b60e01b602083015260248201526024815261414460448261268a565b7f210e4fd706e561df48472433bcc50b4589f2c13e784e9992f4c3e6de26eb3564604051602081528061417a60208201856124a3565b0390a190600190565b909150816141f0575b50156141dd5761419b82613eb8565b6001600160401b0342911610614114577faf1db8f86d3f32029a484ff54c7ac1d7ef8f038ab050fc065af9e82eb9b850ca95938b938861409c97948b94614057565b8663c274d3e360e01b5f5260045260245ffd5b905060c0830151145f61418c565b9050855f525f602052600260405f206001600160601b03604051936142228561260a565b825460018060a01b03811686526001600160401b038160a01c16602087015262ffffff8160e01c16604087015260f81c8186015260018301549082821660808701521c1660a0840152015460c08201525f613fc6565b906001600160c11b031982166142a057602082901c6001600160a01b03169163ffffffff1690565b6341abc80160e01b5f5260045ffd5b63020000008210156129d55701905f90565b63ffffffff821691906020831015614313576401fffffffe905460c01c9160011b169180830460021490151715612a9a576001600160401b03906003831b1616901c9060026001831615159216151590565b9161431e9150612a8b565b908160011b9180830460021481151715612a9a5760ff9161434e9160071c6001600160f81b0316906001016142af565b90549060031b1c9116906003821b16901c9060026001831615159216151590565b8051156142a05760018151146144635780515b600181116143985750614394906129f8565b5190565b60018101808211612a9a5760011c905f5b8160011c81106143f757506001808216146143c5575b50614382565b5f198101908111612a9a576143da9083612a05565b515f198201828111612a9a576143f09084612a05565b525f6143bf565b600181901b906001600160ff1b0381168103612a9a576144178286612a05565b5160018301809311612a9a5761442f60019387612a05565b519081811015614454575f5260205260405f205b61444d8287612a05565b52016143a9565b905f5260205260405f20614443565b614394906129f8565b5f80516020615bde83398151915280546001600160a01b03199081169091555f80516020615afe83398151915280549182166001600160a01b0393841690811790915591167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b6040516323b872dd60e01b81526001600160a01b03918216600482018190523060248301526044820184905292917f000000000000000000000000000000000000000000000000000000000000000016906020905f9060649082855af19081601f3d1160015f51141615166145e4575b50156145a85760208161459f61457d7f1da2c4060997c108162f55c30ad2268924b069210f8c686ba302e1acc6909ccc94613e34565b855f5260018452611ce260405f20916001600160601b03835460601c16612dac565b604051908152a2565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b3b153d171590505f614547565b6040519061460060808361268a565b605a82527f6c2c496e70757420696e7075742c4f66666572206f66666572290000000000006060837f50726f6f66526571756573742875696e743235362069642c526571756972656d60208201527f656e747320726571756972656d656e74732c737472696e6720696d616765557260408201520152565b6040519061468760608361268a565b60268252654c696d69742960d01b6040837f43616c6c6261636b286164647265737320616464722c75696e7439362067617360208201520152565b604051906146d160608361268a565b60218252602960f81b6040837f496e7075742875696e743820696e707574547970652c6279746573206461746160208201520152565b6040519061471660c08361268a565b6084825263616b652960e01b60a0837f4f666665722875696e74323536206d696e50726963652c75696e74323536206d60208201527f617850726963652c75696e7436342062696464696e6753746172742c75696e7460408201527f33322072616d705570506572696f642c75696e743332206c6f636b54696d656f60608201527f75742c75696e7433322074696d656f75742c75696e74323536206c6f636b537460808201520152565b604051906147d060608361268a565b602982526874657320646174612960b81b6040837f5072656469636174652875696e743820707265646963617465547970652c627960208201520152565b6040519061481d60808361268a565b60438252626f722960e81b6060837f526571756972656d656e74732843616c6c6261636b2063616c6c6261636b2c5060208201527f7265646963617465207072656469636174652c6279746573342073656c65637460408201520152565b6148836145f1565b61488b614678565b6148936146c2565b9061489c614707565b6148a46147c1565b6148ac61480e565b916040519485946020860197805160208192018a5e860160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815203601f1981018252614922908261268a565b51902090805190602081015161493661480e565b61493e614678565b6149466147c1565b90604051918291602083019480516020819201875e830160208101915f83528051926020849201905e016020015f815281516020819301825e015f815203601f1981018252614995908261268a565b5190209080516149a3614678565b8051906020012090600160a01b6001900381511690602001516001600160601b0316604051916020830193845260408301526060820152606081526149e960808261268a565b5190209060208101516149fa6147c1565b80519060200120908051906003821015612a45576020015160208151910120614a3160405192602084019485526040840190613b6a565b606082015260608152614a4560808261268a565b51902090604063ffffffff60e01b9101511690604051926020840194855260408401526060830152608082015260808152614a8160a08261268a565b5190209060408101516020815191012060806060830151614aa06146c2565b60208151910120906020815191614ab683612a3b565b0151602081519101206040519160208301938452614ad381612a3b565b6040830152606082015260608152614aeb838261268a565b519020920151614af9614707565b604051614b256020828180820195805191829101875e81015f838201520301601f19810183528261268a565b519020908051906020810151906001600160401b0360408201511663ffffffff60608301511663ffffffff6080840151169160c063ffffffff60a08601511694015194604051966020880198895260408801526060870152608086015260a085015260c084015260e08301526101008201526101008152614ba86101208261268a565b51902092604051946020860196875260408601526060850152608084015260a083015260c082015260c08152614bdf60e08261268a565b51902090565b9081602091031261230457516001600160e01b0319811681036123045790565b9291614c176139396102ff36876136ce565b9335600160c01b1615614cb05791602091614c4993604051809581948293630b135d3f60e11b84528960048501612826565b03916001600160a01b0316620186a0fa9081156122f9575f91614c91575b506001600160e01b0319166374eca2c160e11b01614c825790565b638baa579f60e01b5f5260045ffd5b614caa915060203d60201161051457610506818361268a565b5f614c67565b614cc2614cc891614cd19436916126c6565b846158b0565b909391936158ea565b6001600160a01b03908116911603614c825790565b614cf4906080369101613658565b9081516020830151106142a05763ffffffff606083015116608083019063ffffffff825116106142a05763ffffffff90511660a083019063ffffffff825116106142a057614d619063ffffffff6001600160401b036040614d54876157d9565b9601511691511690613e98565b9162ffffff6001600160401b03614d788386614e66565b16116142a0579190565b9060408201906001600160401b0380835116911690811115614e60576001600160401b03614daf846157d9565b168111614e59576001600160401b03825116906001600160401b03614de0606086019363ffffffff85511690613e98565b16811115614df2575050506020015190565b614e1f906001600160401b0363ffffffff614e136020880151885190612aae565b94511694511690612aae565b925192818102918183041490151715612a9a578115614e4557048101809111612a9a5790565b634e487b7160e01b5f52601260045260245ffd5b5050505f90565b50505190565b906001600160401b03809116911603906001600160401b038211612a9a57565b9590929796949360018060a01b031697885f526001602052614eab8560405f206142c1565b9061523e5761522a576001600160401b0386169889421161521257614ed9611c926138d43660808c01613658565b96815f52600160205260405f20996001600160601b038b5416946001600160601b038a1693848710615200575060018060a01b031698895f52600160205260405f20906001600160601b03825460601c16966101408d01358098106151ed57918d6001600160601b0380614f7f94614f849897960316166001600160601b03198254161790556001600160601b03614f7089613e34565b81835460601c160316906128a6565b614e66565b926001600160401b03841662ffffff81116151d65750614fa390613e34565b60405193614fb08561260a565b88855260208086019c8d5262ffffff90911660408087019182525f60608801818152608089019687526001600160601b0390951660a0808a0191825260c08a019889528e35808452958390529290912097519e51925194519290911b67ffffffffffffffff60a01b166001600160a01b039e909e169d909d1760e09390931b62ffffff60e01b169290921760f89290921b6001600160f81b031916919091178455996001840191516001600160601b03166001600160601b03166001600160601b0319835416178255516001600160601b031661508c916128a6565b51906002015563ffffffff831692602084105f14615147576401fffffffe9060011b169280840460021490151715612a9a5785546001600160c01b038116600190941b6001600160401b031660c091821c17901b6001600160c01b031916929092179094557fe5e43c93dc0ec595ed3b122bdc6d39a480e9d17fb6812e0f90cfc4ba33b0969e93615142915b6151346040519586958652606060208701526060860190613ba8565b918483036040860152612806565b0390a2565b509161515290612a8b565b918260011b9583870460021484151715612a9a577fe5e43c93dc0ec595ed3b122bdc6d39a480e9d17fb6812e0f90cfc4ba33b0969e96615142946151d19260ff916001916151ae9160071c6001600160f81b03169083016142af565b929093161b82548260031b1c179082549060031b91821b915f19901b1916179055565b615118565b6306dfcc6560e41b5f52601860045260245260445ffd5b8b63112fed8b60e31b5f5260045260245ffd5b63112fed8b60e31b5f5260045260245ffd5b898863cfe6a8fd60e01b5f523560045260245260445ffd5b86631cfdeebb60e01b5f523560045260245ffd5b8763a905765160e01b5f523560045260245ffd5b6040519061525f8261266f565b5f6040838281528260208201520152565b9694959192939096606096615369575f80516020615b7e8339815191526152ce9596979860018060a01b031693845f5260016020526152b360405f20968761594a565b6040516001600160a01b039190911696879482919082613eda565b0390a36001600160601b03825416906001600160601b038516821061533d57506001600160601b038481920316166001600160601b03198254161790555f5260016020526001600160601b0361532b60405f209282845416612dac565b166001600160601b0319825416179055565b949550505050506040519063112fed8b60e31b602083015260248201526024815261271760448261268a565b955050505050915060405190631cfdeebb60e01b602083015260248201526024815261271760448261268a565b906001600160601b03809116911603906001600160601b038211612a9a57565b92979694909395976060986001606086015116151580156155b9575b61558a5715615539575b505060018060a01b0316805f5260016020526001600160601b03608060405f209301511685816001600160601b038216115f14615504579061541d91615396565b906001600160601b03835416906001600160601b03831682106154d6575082546bffffffffffffffffffffffff19169190036001600160601b03161790555b5f90815260208190526040902080546affffffffffffffffffffff60a01b81166001600160a01b0384169081176001600160a01b0319929092161760f890811c600217901b6001600160f81b03191617905560018060a01b03165f5260016020526001600160601b0361532b60405f209282845416612dac565b9697505050505050506040519063112fed8b60e31b602083015260248201526024815261271760448261268a565b6001600160601b03925061551b9061552492615396565b82845416612dac565b166001600160601b031982541617905561545c565b6001600160a01b0383165f90815260016020526040902061555a919061594a565b835f80516020615b7e8339815191526040518061558060018060a01b038a169582613eda565b0390a35f806153dc565b5050505050929350505060405190631cfdeebb60e01b602083015260248201526024815261271760448261268a565b50600260608601511615156153d2565b939190929695949660609760016060870151161515801561574b575b61571d57156156cd575b505082516001600160a01b0394851694168414801591906156b9575b5061568f5760a06128a493926001600160601b03925f525f6020525f6001604082208160f81b828060f81b03825416178155015582608082015116845f5260016020528361566060405f209282845416612dac565b168419825416179055015116905f526001602052611ce260405f20916001600160601b03835460601c16612dac565b92935050506040519063a905765160e01b602083015260248201526024815261271760448261268a565b9050602060c084015191013514155f61560b565b6156e99160018060a01b03165f52600160205260405f2061594a565b6040516001600160a01b0385169083905f80516020615b7e83398151915290806157138682613eda565b0390a35f806155ef565b50505050929350505060405190631cfdeebb60e01b602083015260248201526024815261271760448261268a565b50600260608701511615156155e5565b615763615827565b61576b61587e565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152614bdf60c08261268a565b6042916040519161190160f01b8352600283015260228201522090565b6127179063ffffffff60806001600160401b036040840151169201511690613e98565b60ff5f80516020615b9e8339815191525460401c161561581857565b631afcd79f60e31b5f5260045ffd5b61582f613974565b805190811561583f576020012090565b50505f80516020615b3e8339815191525480156158595790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b615886613a41565b8051908115615896576020012090565b50505f80516020615bbe8339815191525480156158595790565b81519190604183036158e0576158d99250602082015190606060408401519301515f1a90615a45565b9192909190565b50505f9160029190565b6004811015612a4557806158fc575050565b600181036159135763f645eedf60e01b5f5260045ffd5b6002810361592e575063fce698f760e01b5f5260045260245ffd5b6003146159385750565b6335e2f38360e21b5f5260045260245ffd5b9063ffffffff81169060208210156159a7576401fffffffe9060011b169080820460021490151715612a9a5781546001600160c01b038116600290921b6001600160401b031660c091821c17901b6001600160c01b031916179055565b506159b190612a8b565b8060011b9080820460021481151715612a9a576128a49260ff916002916151ae9160071c6001600160f81b0316906001016142af565b90615a0b57508051156159fc57805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580615a3c575b615a1c575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b15615a14565b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411615ab2579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa156122f9575f516001600160a01b03811615615aa857905f905f90565b505f906001905f90565b5050505f916003919056fe405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acea16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1029016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc55d02e4952038088ed4492ee2cd67de44f0009f19b561e78a7389b37fdfd8e59f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00a164736f6c634300081a000a")] contract BoundlessMarket { constructor(address verifier, bytes32 assessorId, address stakeTokenContract) {} function initialize(address initialOwner, string calldata imageUrl) {} diff --git a/crates/boundless-market/src/contracts/mod.rs b/crates/boundless-market/src/contracts/mod.rs index 5af35fa11..f63647655 100644 --- a/crates/boundless-market/src/contracts/mod.rs +++ b/crates/boundless-market/src/contracts/mod.rs @@ -41,7 +41,10 @@ use token::{ }; use url::Url; -use risc0_zkvm::sha::Digest; +use risc0_zkvm::{ + sha::{Digest, Digestible}, + ReceiptClaim, +}; #[cfg(not(target_os = "zkvm"))] pub use risc0_ethereum_contracts::{encode_seal, selector::Selector, IRiscZeroSetVerifier}; @@ -57,8 +60,8 @@ const TXN_CONFIRM_TIMEOUT: Duration = Duration::from_secs(45); // See the build.rs script in this crate for more details. include!(concat!(env!("OUT_DIR"), "/boundless_market_generated.rs")); pub use boundless_market_contract::{ - AssessorCallback, AssessorCommitment, AssessorJournal, AssessorJournalCallback, - AssessorReceipt, Callback, Fulfillment, FulfillmentContext, IBoundlessMarket, + AssessorCallback, AssessorCommitment, AssessorJournal, AssessorReceipt, Callback, Fulfillment, + FulfillmentContext, FulfillmentData, FulfillmentDataType, IBoundlessMarket, Input as RequestInput, InputType as RequestInputType, LockRequest, Offer, Predicate, PredicateType, ProofRequest, RequestLock, Requirements, Selector as AssessorSelector, }; @@ -346,6 +349,10 @@ pub enum RequestError { /// Request digest mismatch. #[error("request digest mismatch")] DigestMismatch, + + /// Predicate data was not created correctly. + #[error("malformed predicate data")] + MalformedPredicateData, } #[cfg(not(target_os = "zkvm"))] @@ -430,9 +437,23 @@ impl ProofRequest { } Url::parse(&self.imageUrl).map(|_| ())?; - if self.requirements.imageId == B256::default() { - return Err(RequestError::ImageIdIsZero); + match self.requirements.predicate.predicateType { + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + if self.requirements.predicate.data.len() < 32 { + return Err(RequestError::MalformedPredicateData); + } + if self.requirements.image_id() == Some(<[u8; 32]>::from(B256::default()).into()) { + return Err(RequestError::ImageIdIsZero); + } + } + PredicateType::ClaimDigestMatch => { + if self.requirements.predicate.data.len() != 32 { + return Err(RequestError::MalformedPredicateData); + } + } + PredicateType::__Invalid => return Err(RequestError::MalformedPredicateData), } + if self.offer.timeout == 0 { return Err(RequestError::OfferTimeoutIsZero); } @@ -460,6 +481,11 @@ impl ProofRequest { Ok(()) } + + /// TODO(ec2): docs + pub fn image_id(&self) -> Option { + self.requirements.image_id() + } } #[cfg(not(target_os = "zkvm"))] @@ -510,18 +536,8 @@ impl ProofRequest { impl Requirements { /// Creates a new requirements with the given image ID and predicate. - pub fn new(image_id: impl Into, predicate: Predicate) -> Self { - Self { - imageId: <[u8; 32]>::from(image_id.into()).into(), - predicate, - callback: Callback::default(), - selector: UNSPECIFIED_SELECTOR, - } - } - - /// Sets the image ID. - pub fn with_image_id(self, image_id: impl Into) -> Self { - Self { imageId: <[u8; 32]>::from(image_id.into()).into(), ..self } + pub fn new(predicate: Predicate) -> Self { + Self { predicate, callback: Callback::default(), selector: UNSPECIFIED_SELECTOR } } /// Sets the predicate. @@ -551,22 +567,177 @@ impl Requirements { false => Self { selector: FixedBytes::from(Selector::Groth16V2_2 as u32), ..self }, } } + + /// Set the selector for a shrink bitvm2 proof. + /// + /// This will set the selector to the appropriate value based on the current environment. + /// In dev mode, the selector will be set to `FakeReceipt`, otherwise it will be set + /// to `Groth16V2_2`. + #[cfg(not(target_os = "zkvm"))] + pub fn with_shrink_bitvm2_proof(self) -> Self { + match crate::util::is_dev_mode() { + true => Self { selector: FixedBytes::from(Selector::FakeReceipt as u32), ..self }, + false => Self { selector: FixedBytes::from(Selector::ShrinkBitvm2V0_1 as u32), ..self }, + } + } + /// Returns image id from the predicate type. Returns none if claim digest match. Panics if + /// the predicate data is not long enough. + pub fn image_id(&self) -> Option { + self.predicate.image_id() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// The data that is used to construct the claim for a fulfillment. +pub enum FulfillmentClaimData { + /// There are certain types of fulfillments where journals cannot be authenticated and + /// so we can only check the integrity of the receipt. + ClaimDigest(Digest), + /// Proofs fulfilled with both image id and journal and the claim is calculated from them. + ImageIdAndJournal(Digest, Bytes), +} + +impl FulfillmentClaimData { + /// Create the claim data from a claim digest. + pub fn from_claim_digest(claim_digest: impl Into) -> Self { + Self::ClaimDigest(claim_digest.into()) + } + /// Create the claim data from image_id and journal. + pub fn from_image_id_and_journal( + image_id: impl Into, + journal: impl Into, + ) -> Self { + Self::ImageIdAndJournal(image_id.into(), journal.into()) + } + /// Encodes to bytes. + pub fn to_bytes(self) -> Bytes { + match self { + Self::ClaimDigest(digest) => digest.as_bytes().to_vec().into(), + Self::ImageIdAndJournal(image_id, journal) => { + let mut data = image_id.as_bytes().to_vec(); + data.extend_from_slice(journal.as_ref()); + data.into() + } + } + } + /// Get the image id if it exists. + pub fn image_id(&self) -> Option { + match self { + Self::ClaimDigest(_) => None, + Self::ImageIdAndJournal(image_id, _) => Some(*image_id), + } + } + /// Get the journal if it exists + pub fn journal(&self) -> Option<&Bytes> { + match self { + Self::ClaimDigest(_) => None, + Self::ImageIdAndJournal(_, journal) => Some(journal), + } + } + /// Get the claim digest or calculates it if journal and image id exists. + pub fn claim_digest(&self) -> Option { + match self { + Self::ClaimDigest(digest) => Some(*digest), + Self::ImageIdAndJournal(image_id, journal) => { + Some(ReceiptClaim::ok(*image_id, journal.as_ref().to_vec()).digest()) + } + } + } } impl Predicate { /// Returns a predicate to match the journal digest. This ensures that the request's /// fulfillment will contain a journal with the same digest. - pub fn digest_match(digest: impl Into) -> Self { - Self { - predicateType: PredicateType::DigestMatch, - data: digest.into().as_bytes().to_vec().into(), - } + pub fn digest_match(image_id: impl Into, digest: impl Into) -> Self { + let mut image_id = image_id.into().as_bytes().to_vec(); + let digest = digest.into().as_bytes().to_vec(); + image_id.append(&mut digest.clone()); + let data = image_id; + Self { predicateType: PredicateType::DigestMatch, data: data.into() } } /// Returns a predicate to match the journal prefix. This ensures that the request's /// fulfillment will contain a journal with the same prefix. - pub fn prefix_match(prefix: impl Into) -> Self { - Self { predicateType: PredicateType::PrefixMatch, data: prefix.into() } + pub fn prefix_match(image_id: impl Into, prefix: impl Into) -> Self { + let mut image_id = image_id.into().as_bytes().to_vec(); + let prefix = prefix.into().as_ref().to_vec(); + image_id.append(&mut prefix.clone()); + let data = image_id; + Self { predicateType: PredicateType::PrefixMatch, data: data.into() } + } + + /// Returns a predicate to match the claim digest. This ensures that the request's + /// fulfillment will contain a claim with the same digest. + pub fn claim_digest_match(claim_digest: impl Into) -> Self { + Self { + predicateType: PredicateType::ClaimDigestMatch, + data: claim_digest.into().as_bytes().to_vec().into(), + } + } + + /// Returns image id. Returns none if claim digest match. Panics if + /// the predicate data is not long enough. + pub fn image_id(&self) -> Option { + match self.predicateType { + PredicateType::DigestMatch | PredicateType::PrefixMatch => { + Some(Digest::from_bytes(self.data.as_ref()[..32].try_into().unwrap())) + } + _ => None, + } + } + + /// Returns claim digest if the predicate type is ClaimDigestMatch. + pub fn claim_digest(&self) -> Option { + if self.predicateType == PredicateType::ClaimDigestMatch { + Some(Digest::from_bytes(self.data.as_ref().try_into().unwrap())) + } else { + None + } + } + + #[inline] + /// Evaluates the predicate against the fulfillment data. + pub fn eval(&self, fulfillment_data: &FulfillmentClaimData) -> bool { + match self.predicateType { + PredicateType::DigestMatch => { + let (image_id, journal_digest) = self.data.as_ref().split_at(32); + journal_digest + == Sha256::digest( + fulfillment_data + .journal() + .expect("fulfillment data doesnt have journal, but is digest match"), + ) + .as_slice() + && image_id + == fulfillment_data + .image_id() + .expect("fulfillment data doesnt have image id, but is digest match") + .as_bytes() + } + PredicateType::PrefixMatch => { + let (image_id, journal) = self.data.as_ref().split_at(32); + fulfillment_data + .journal() + .expect("fulfillment data doesnt have journal, but is prefix match") + .as_ref() + .starts_with(journal) + && image_id + == fulfillment_data + .image_id() + .expect("fulfillment data doesnt have image id, but is prefix match") + .as_bytes() + } + PredicateType::ClaimDigestMatch => { + self.data.as_ref() + == fulfillment_data + .claim_digest() + .expect( + "fulfillment data doesnt have claim digest, but is claim digest match", + ) + .as_bytes() + } + PredicateType::__Invalid => panic!("invalid PredicateType"), + } } } @@ -706,18 +877,6 @@ use IBoundlessMarket::IBoundlessMarketErrors; #[cfg(not(target_os = "zkvm"))] use IRiscZeroSetVerifier::IRiscZeroSetVerifierErrors; -impl Predicate { - /// Evaluates the predicate against the given journal. - #[inline] - pub fn eval(&self, journal: impl AsRef<[u8]>) -> bool { - match self.predicateType { - PredicateType::DigestMatch => self.data.as_ref() == Sha256::digest(journal).as_slice(), - PredicateType::PrefixMatch => journal.as_ref().starts_with(&self.data), - PredicateType::__Invalid => panic!("invalid PredicateType"), - } - } -} - #[cfg(not(target_os = "zkvm"))] /// The Boundless market module. pub mod boundless_market; @@ -867,10 +1026,10 @@ mod tests { let req = ProofRequest { id: request_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "https://dev.null".to_string(), input: RequestInput::builder().build_inline().unwrap(), offer: Offer { diff --git a/crates/boundless-market/src/request_builder/finalizer.rs b/crates/boundless-market/src/request_builder/finalizer.rs index eb17a73d0..c80f0baf5 100644 --- a/crates/boundless-market/src/request_builder/finalizer.rs +++ b/crates/boundless-market/src/request_builder/finalizer.rs @@ -14,12 +14,15 @@ use super::{Adapt, Layer, RequestParams}; use crate::{ - contracts::RequestInput, - contracts::{Offer, ProofRequest, RequestId, Requirements}, + contracts::{ + FulfillmentClaimData, Offer, PredicateType, ProofRequest, RequestId, RequestInput, + Requirements, + }, util::now_timestamp, }; use anyhow::{bail, Context}; use derive_builder::Builder; +use risc0_zkvm::Digest; use url::Url; #[non_exhaustive] @@ -128,10 +131,18 @@ impl Adapt for RequestParams { .context("failed to build request: offer is incomplete")?; let request_id = self.require_request_id().context("failed to build request")?.clone(); - // As an extra consistency check. verify the journal satisfies the required predicate. - if let Some(ref journal) = self.journal { - if !requirements.predicate.eval(journal) { - bail!("journal in request builder does not match requirements predicate; check request parameters.\npredicate = {:?}\njournal = 0x{}", requirements.predicate, hex::encode(journal)); + // If a callback is requested, we should check that the journal satisfies the required predicate. + if !requirements.callback.is_none() { + if let Some(ref journal) = self.journal { + if let Some(image_id) = self.image_id { + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + image_id, + journal.bytes.clone(), + ); + if !requirements.predicate.eval(&fulfillment_data) { + bail!("journal in request builder does not match requirements predicate; check request parameters.\npredicate = {:?}\njournal = 0x{}", requirements.predicate, hex::encode(journal)); + } + } } } diff --git a/crates/boundless-market/src/request_builder/mod.rs b/crates/boundless-market/src/request_builder/mod.rs index 034b4de01..6eded981d 100644 --- a/crates/boundless-market/src/request_builder/mod.rs +++ b/crates/boundless-market/src/request_builder/mod.rs @@ -29,7 +29,7 @@ use risc0_zkvm::{Digest, Journal}; use url::Url; use crate::{ - contracts::{ProofRequest, RequestId, RequestInput}, + contracts::{Predicate, ProofRequest, RequestId, RequestInput}, input::GuestEnv, storage::{StandardStorageProvider, StorageProvider}, util::NotProvided, @@ -295,6 +295,9 @@ pub struct RequestParams { /// Contents of the [Journal] that results from the execution. pub journal: Option, + /// Claim digest that results from execution. + pub claim_digest: Option, + /// [RequestId] to use for the proof request. pub request_id: Option, @@ -540,6 +543,18 @@ impl RequestParams { Self { requirements: value.into(), ..self } } + /// TODO(ec2): doc + pub fn with_claim_digest(self, value: impl Into) -> Self { + Self { claim_digest: Some(value.into()), ..self } + } + + /// TODO(ec2): doc + pub fn require_claim_digest(&self) -> Result { + self.claim_digest.ok_or(MissingFieldError::with_hint( + "claim_digest", + "can be set using .with_claim_digest(...), and is calculated from the program", + )) + } /// Request a stand-alone Groth16 proof for this request. /// /// This is a convinience method to set the selector on the requirements. Note that calling @@ -552,6 +567,25 @@ impl RequestParams { }; Self { requirements, ..self } } + + /// Request a stand-alone Shrink Bitvm2 proof for this request. + /// + /// This is a convinience method to set the selector on the requirements. Note that calling + /// [RequestParams::with_requirements] after this function will overwrite the change. + pub fn with_shrink_bitvm2_proof(self) -> Self { + let mut requirements = self.requirements; + requirements.selector = match crate::util::is_dev_mode() { + true => Some((Selector::FakeReceipt as u32).into()), + false => Some((Selector::ShrinkBitvm2V0_1 as u32).into()), + }; + // if let Some(ref predicate) = requirements.predicate { + // if predicate.predicateType == crate::contracts::PredicateType::ClaimDigestMatch { + // // If the predicate is a claim digest match, we need to set the image ID. + // requirements.image_id = Some(predicate.data.0.as_ref().try_into().unwrap()); + // } + // } + Self { requirements, ..self } + } } impl Debug for RequestParams { @@ -644,8 +678,8 @@ mod tests { use crate::{ contracts::{ - boundless_market::BoundlessMarketService, Predicate, RequestInput, RequestInputType, - Requirements, + boundless_market::BoundlessMarketService, FulfillmentClaimData, Predicate, + RequestInput, RequestInputType, Requirements, }, input::GuestEnv, storage::{fetch_url, MockStorageProvider, StorageProvider}, @@ -734,8 +768,8 @@ mod tests { let params = request_builder.params().with_program_url(program_url)?.with_stdin(b"hello!"); let request = request_builder.build(params).await?; assert_eq!( - request.requirements.imageId, - risc0_zkvm::compute_image_id(ECHO_ELF)?.as_bytes() + request.requirements.image_id().unwrap(), + risc0_zkvm::compute_image_id(ECHO_ELF)? ); Ok(()) } @@ -836,12 +870,19 @@ mod tests { let bytes = b"journal_data".to_vec(); let journal = Journal::new(bytes.clone()); let req = layer.process((program, &journal, &Default::default())).await?; - + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + req.image_id().unwrap(), + journal.bytes.clone(), + ); // Predicate should match the same journal - assert!(req.predicate.eval(&journal)); + assert!(req.predicate.eval(&fulfillment_data)); // And should not match different data let other = Journal::new(b"other_data".to_vec()); - assert!(!req.predicate.eval(&other)); + let fulfillment_data = FulfillmentClaimData::from_image_id_and_journal( + req.image_id().unwrap(), + other.bytes.clone(), + ); + assert!(!req.predicate.eval(&fulfillment_data)); Ok(()) } @@ -910,8 +951,8 @@ mod tests { let layer = OfferLayer::from(provider.clone()); // Build minimal requirements and request ID let image_id = compute_image_id(ECHO_ELF).unwrap(); - let predicate = Predicate::digest_match(Journal::new(b"hello".to_vec()).digest()); - let requirements = Requirements::new(image_id, predicate); + let predicate = Predicate::digest_match(image_id, Journal::new(b"hello".to_vec()).digest()); + let requirements = Requirements::new(predicate); let request_id = RequestId::new(test_ctx.customer_signer.address(), 0); // Zero cycles diff --git a/crates/boundless-market/src/request_builder/requirements_layer.rs b/crates/boundless-market/src/request_builder/requirements_layer.rs index 7cca50de8..8353ef031 100644 --- a/crates/boundless-market/src/request_builder/requirements_layer.rs +++ b/crates/boundless-market/src/request_builder/requirements_layer.rs @@ -66,12 +66,13 @@ pub struct RequirementParams { impl From for RequirementParams { fn from(value: Requirements) -> Self { + let image_id = value.predicate.image_id().map(<[u8; 32]>::from).map(Into::into); Self { predicate: Some(value.predicate), - image_id: Some(value.imageId), selector: Some(value.selector), callback_address: Some(value.callback.addr), callback_gas_limit: Some(value.callback.gasLimit.to()), + image_id, } } } @@ -85,10 +86,6 @@ impl TryFrom for Requirements { "predicate", "please provide a Predicate with requirements e.g. a digest match on a journal", ))?, - imageId: value.image_id.ok_or(MissingFieldError::with_hint( - "image_id", - "please provide the image ID for the program to be proven", - ))?, selector: value.selector.unwrap_or_default(), callback: Callback { addr: value.callback_address.unwrap_or_default(), @@ -152,8 +149,10 @@ impl Layer<(Digest, &Journal, &RequirementParams)> for RequirementsLayer { &self, (image_id, journal, params): (Digest, &Journal, &RequirementParams), ) -> Result { - let predicate = - params.predicate.clone().unwrap_or_else(|| Predicate::digest_match(journal.digest())); + let predicate = params + .predicate + .clone() + .unwrap_or_else(|| Predicate::digest_match(image_id, journal.digest())); if let Some(params_image_id) = params.image_id { ensure!( image_id == Digest::from(<[u8; 32]>::from(params_image_id)), @@ -169,12 +168,7 @@ impl Layer<(Digest, &Journal, &RequirementParams)> for RequirementsLayer { .unwrap_or_default(); let selector = params.selector.unwrap_or_default(); - Ok(Requirements { - imageId: <[u8; 32]>::from(image_id).into(), - predicate, - callback, - selector, - }) + Ok(Requirements { predicate, callback, selector }) } } diff --git a/crates/boundless-market/src/selector.rs b/crates/boundless-market/src/selector.rs index 2ed0f7130..045fd7f98 100644 --- a/crates/boundless-market/src/selector.rs +++ b/crates/boundless-market/src/selector.rs @@ -38,6 +38,8 @@ pub enum ProofType { Groth16, /// Inclusion proof type. Inclusion, + /// BitVM2 compatible Groth16 proof type. + ShrinkBitvm2, } /// A struct to hold the supported selectors. @@ -51,7 +53,11 @@ impl Default for SupportedSelectors { fn default() -> Self { let mut supported_selectors = Self::new() .with_selector(UNSPECIFIED_SELECTOR, ProofType::Any) - .with_selector(FixedBytes::from(Selector::Groth16V2_2 as u32), ProofType::Groth16); + .with_selector(FixedBytes::from(Selector::Groth16V2_2 as u32), ProofType::Groth16) + .with_selector( + FixedBytes::from(Selector::ShrinkBitvm2V0_1 as u32), + ProofType::ShrinkBitvm2, + ); if is_dev_mode() { supported_selectors = supported_selectors .with_selector(FixedBytes::from(Selector::FakeReceipt as u32), ProofType::Any); @@ -124,6 +130,18 @@ pub fn is_groth16_selector(selector: FixedBytes<4>) -> bool { } } +/// Check if a selector is a bitvm2 groth16 selector. +pub fn is_shrink_bitvm2_selector(selector: FixedBytes<4>) -> bool { + let sel = Selector::from_bytes(selector.into()); + match sel { + Some(selector) => { + selector.get_type() == SelectorType::FakeReceipt + || selector.get_type() == SelectorType::ShrinkBitvm2 + } + None => false, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/boundless-market/src/storage/fetch.rs b/crates/boundless-market/src/storage/fetch.rs index 9217a5577..6d82845c2 100644 --- a/crates/boundless-market/src/storage/fetch.rs +++ b/crates/boundless-market/src/storage/fetch.rs @@ -27,7 +27,7 @@ pub async fn fetch_url(url_str: impl AsRef) -> anyhow::Result> { match url.scheme() { "http" | "https" => fetch_http(&url).await, "file" => { - ensure!(is_dev_mode(), "file fetch is only enabled when RISC0_DEV_MODE is enabled"); + // ensure!(is_dev_mode(), "file fetch is only enabled when RISC0_DEV_MODE is enabled"); fetch_file(&url).await } _ => bail!("unsupported URL scheme: {}", url.scheme()), diff --git a/crates/boundless-market/test-utils/Cargo.toml b/crates/boundless-market/test-utils/Cargo.toml index d0f854c0c..49207edbd 100644 --- a/crates/boundless-market/test-utils/Cargo.toml +++ b/crates/boundless-market/test-utils/Cargo.toml @@ -17,6 +17,7 @@ boundless-market = { workspace = true, features = ["test-utils"] } guest-assessor = { workspace = true } guest-set-builder = { workspace = true } guest-util = { workspace = true } +hex = { workspace = true } risc0-aggregation = { workspace = true } risc0-circuit-recursion = { workspace = true } risc0-ethereum-contracts = { workspace = true, features = ["unstable"] } diff --git a/crates/boundless-market/test-utils/src/lib.rs b/crates/boundless-market/test-utils/src/lib.rs index f0144fdb3..5c6ab2dee 100644 --- a/crates/boundless-market/test-utils/src/lib.rs +++ b/crates/boundless-market/test-utils/src/lib.rs @@ -29,12 +29,14 @@ use boundless_market::{ boundless_market::BoundlessMarketService, bytecode::*, hit_points::{default_allowance, HitPointsService}, - AssessorCommitment, AssessorJournal, Fulfillment, ProofRequest, + AssessorCommitment, AssessorJournal, Fulfillment, FulfillmentData, FulfillmentDataType, + PredicateType, ProofRequest, }, deployments::Deployment, dynamic_gas_filler::DynamicGasFiller, nonce_layer::NonceProvider, }; +use hex::FromHex; use risc0_aggregation::{ merkle_path, merkle_root, GuestState, SetInclusionReceipt, SetInclusionReceiptVerifierParameters, @@ -95,6 +97,18 @@ pub async fn deploy_groth16_verifier( Ok(*instance.address()) } +pub async fn deploy_bitvm2_verifier( + deployer_provider: P, + control_root: B256, + bn254_control_id: B256, +) -> Result

{ + let instance = + RiscZeroBitvm2Groth16Verifier::deploy(deployer_provider, control_root, bn254_control_id) + .await + .context("failed to deploy RiscZeroBitvm2Groth16Verifier")?; + Ok(*instance.address()) +} + pub async fn deploy_mock_verifier(deployer_provider: P) -> Result
{ let instance = RiscZeroMockVerifier::deploy(deployer_provider, FixedBytes([0xFFu8; 4])) .await @@ -222,6 +236,7 @@ pub async fn deploy_contracts( let mut bn254_control_id = BN254_IDENTITY_CONTROL_ID; bn254_control_id.as_mut_bytes().reverse(); let verifier_parameters_digest = Groth16ReceiptVerifierParameters::default().digest(); + println!("g16 verifier_parameters_digest {verifier_parameters_digest}"); ( deploy_groth16_verifier( &deployer_provider, @@ -233,6 +248,7 @@ pub async fn deploy_contracts( ) } }; + let set_verifier = deploy_set_verifier(&deployer_provider, verifier_router, set_builder_id, set_builder_url) .await?; @@ -242,6 +258,31 @@ pub async fn deploy_contracts( deployer_provider.clone(), ); + match is_dev_mode() { + true => println!("Skipping Bitvm2 verifier deployment in dev mode"), + false => { + let control_root = ALLOWED_CONTROL_ROOT; + let mut bn254_control_id = BN254_IDENTITY_CONTROL_ID; + bn254_control_id.as_mut_bytes().reverse(); + let verifier_parameters_digest = Digest::from_hex( + "b72859b60cfe0bb13cbde70859fbc67ef9dbd5410bbe66bdb7be64a3dcf6814e", + ) + .unwrap(); + let bvm2_verifier = deploy_bitvm2_verifier( + &deployer_provider, + <[u8; 32]>::from(control_root).into(), + <[u8; 32]>::from(bn254_control_id).into(), + ) + .await?; + + let bvm2_selector = verifier_parameters_digest.as_bytes()[..4].try_into()?; + + let call = &router_instance + .addVerifier(bvm2_selector, bvm2_verifier) + .from(deployer_signer.address()); + let _ = call.send().await?; + } + } let call = &router_instance .addVerifier(groth16_selector.into(), groth16_verifier) .from(deployer_signer.address()); @@ -435,16 +476,36 @@ pub fn mock_singleton( .abi_encode_seal() .unwrap(); + let predicate_type = request.requirements.predicate.predicateType; + let (claim_digest, fulfillment_data, fulfillment_data_type) = match predicate_type { + PredicateType::ClaimDigestMatch => { + (<[u8; 32]>::from(app_claim_digest).into(), vec![], FulfillmentDataType::None) + } + PredicateType::PrefixMatch | PredicateType::DigestMatch => ( + <[u8; 32]>::from(app_claim_digest).into(), + FulfillmentData { + imageId: <[u8; 32]>::from( + request.requirements.image_id().expect("image ID is required"), + ) + .into(), + journal: app_journal.bytes.into(), + } + .abi_encode(), + FulfillmentDataType::ImageIdAndJournal, + ), + _ => panic!("unsupported predicate type"), + }; let fulfillment = Fulfillment { id: request.id, requestDigest: request_digest, - imageId: to_b256(Digest::from(ECHO_ID)), - journal: app_journal.bytes.into(), + claimDigest: claim_digest, + fulfillmentData: fulfillment_data.into(), + fulfillmentDataType: fulfillment_data_type, seal: set_inclusion_seal.into(), }; let assessor_seal = SetInclusionReceipt::from_path_with_verifier_params( - ReceiptClaim::ok(ASSESSOR_GUEST_ID, MaybePruned::Pruned(Digest::ZERO)), + assesor_receipt_claim, merkle_path(&[app_claim_digest, assessor_claim_digest], 1), verifier_parameters.digest(), ) diff --git a/crates/boundless-market/tests/e2e.rs b/crates/boundless-market/tests/e2e.rs index c72c629e8..4ff9911c2 100644 --- a/crates/boundless-market/tests/e2e.rs +++ b/crates/boundless-market/tests/e2e.rs @@ -18,12 +18,12 @@ use alloy::{ providers::Provider, sol_types::eip712_domain, }; +use alloy_primitives::Bytes; use boundless_market::{ contracts::{ boundless_market::{FulfillmentTx, UnlockedRequest}, hit_points::default_allowance, - AssessorReceipt, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestStatus, - Requirements, + AssessorReceipt, Offer, Predicate, ProofRequest, RequestId, RequestStatus, Requirements, }, input::GuestEnv, }; @@ -41,10 +41,7 @@ fn now_timestamp() -> u64 { async fn new_request(idx: u32, ctx: &TestCtx

) -> ProofRequest { ProofRequest::new( RequestId::new(ctx.customer_signer.address(), idx), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { @@ -213,10 +210,11 @@ async fn test_e2e() { .unwrap(); assert!(ctx.customer_market.is_fulfilled(request_id).await.unwrap()); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve fulfillment data data and seal from the fulfilled request + let (fulfillment_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(fulfillment_data, fulfillment.fulfillmentData); assert_eq!(seal, fulfillment.seal); } @@ -279,10 +277,11 @@ async fn test_e2e_merged_submit_fulfill() { .await .unwrap(); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve fulfillment data and seal from the fulfilled request + let (fulfillment_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillments[0].journal); + assert_eq!(fulfillment_data, fulfillments[0].fulfillmentData); assert_eq!(seal, fulfillments[0].seal); } @@ -333,10 +332,11 @@ async fn test_e2e_price_and_fulfill_batch() { .await .unwrap(); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); + // retrieve callback data and seal from the fulfilled request + let (fulfillment_data, seal) = + ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillments[0].journal); + assert_eq!(fulfillment_data, fulfillments[0].fulfillmentData); assert_eq!(seal, fulfillments[0].seal); } @@ -406,11 +406,11 @@ async fn test_e2e_no_payment() { let balance_after = ctx.prover_market.balance_of(some_other_address).await.unwrap(); assert!(balance_before == balance_after); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = + // retrieve fulfillment data and seal from the fulfilled request + let (fulfillment_data, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(fulfillment_data, fulfillment.fulfillmentData); assert_eq!(seal, fulfillment.seal); } diff --git a/crates/broker-stress/src/main.rs b/crates/broker-stress/src/main.rs index 6eeb41bb7..3de872d9a 100644 --- a/crates/broker-stress/src/main.rs +++ b/crates/broker-stress/src/main.rs @@ -22,15 +22,15 @@ use std::{ use alloy::{ node_bindings::Anvil, - primitives::{utils, U256}, + primitives::{utils, Bytes, U256}, providers::{Provider, WalletProvider}, }; use anyhow::{Context, Result}; use axum::{routing::get, Router}; use boundless_market::{ contracts::{ - hit_points::default_allowance, Offer, Predicate, PredicateType, ProofRequest, RequestId, - RequestInput, RequestInputType, Requirements, + hit_points::default_allowance, Offer, Predicate, ProofRequest, RequestId, RequestInput, + RequestInputType, Requirements, }, input::GuestEnv, }; @@ -88,10 +88,7 @@ async fn request_spawner( ctx.customer_signer.address(), ctx.customer_market.index_from_nonce().await?, ), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), program_url, RequestInput { inputType: RequestInputType::Inline, diff --git a/crates/broker/Cargo.toml b/crates/broker/Cargo.toml index cc9ad8425..8762c1be0 100644 --- a/crates/broker/Cargo.toml +++ b/crates/broker/Cargo.toml @@ -31,6 +31,8 @@ hex = { workspace = true } http-cache-reqwest = "0.15.1" moka = { version = "0.12", features = ["future"] } notify = "6.1" +num-bigint = { version = "0.4", features = ["std"] } +num-traits = "0.2.19" rand = { workspace = true } reqwest = { workspace = true } reqwest-middleware = "0.4.1" @@ -41,6 +43,7 @@ risc0-zkvm = { workspace = true, features = ["std", "client"] } serde = { workspace = true } serde_json = { workspace = true } sha2 = "0.10" +shrink_bitvm2 = { workspace = true } sqlx = { workspace = true, features = ["sqlite", "postgres", "runtime-tokio", "json", "migrate", "macros"] } tempfile = { workspace = true } thiserror = { workspace = true } @@ -52,6 +55,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } url = { workspace = true } uuid = { workspace = true } + [dev-dependencies] alloy = { workspace = true, features = ["node-bindings"] } aws-smithy-http-client = { version = "1.0", features = ["test-util"] } @@ -67,8 +71,18 @@ rand = { workspace = true } risc0-zkvm = { workspace = true, default-features = true } serial_test = "3.2" temp-env = { version = "0.3", features = ["async_closure"] } +test-log = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing-test = { workspace = true } +risc0-groth16 = { version = "2.0.2", features = ["prove"] } +ark-bn254 = "0.5.0" +ark-ff = "0.5.0" +ark-groth16 = { version = "0.5.0" } +ark-serialize = "0.5.0" +ark-ec = "0.5.0" +blake3 = { version = "1.5.0" } + + [features] test-utils = ["dep:boundless-market-test-utils"] diff --git a/crates/broker/proptest-regressions/db/fuzz_db.txt b/crates/broker/proptest-regressions/db/fuzz_db.txt new file mode 100644 index 000000000..ef1e2ca0a --- /dev/null +++ b/crates/broker/proptest-regressions/db/fuzz_db.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 7f3d78f8fb3cc73a9435297ef5971b52fff551fb46e637814229af43cbbe221e # shrinks to operations = [AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), AddOrder(0), GetBatch(796372), GetActiveProofs, GetAggregationProofs, GetAggregationProofs, GetAggregationProofs, GetActiveProofs, GetActiveProofs, GetActiveProofs, GetBatch(2853967736), AddOrder(832495868), BatchOperation(GetCompleteBatch), OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, AddOrder(2480737974), AddOrder(2610084057), GetActiveProofs, GetActiveProofs, GetAggregationProofs, AddOrder(3153343604), GetBatch(1209917930), AddOrder(3473160061), BatchOperation(GetCompleteBatch), BatchOperation(CompleteBatch { g16_proof_id: "꒛A$&=s<ொt&🕴c*.🞿꣖?�𐨜!ቓ🕴�" }), OperateOnExistingOrder(SetOrderFailure), BatchOperation(GetCurrentBatch), OperateOnExistingOrder(SetOrderFailure), GetAggregationProofs, GetAggregationProofs, GetBatch(2528283650), GetAggregationProofs, GetBatch(1877280501), GetProvingOrder, GetActiveProofs, OperateOnExistingOrder(GetOrder), AddOrder(560923163), OperateOnExistingOrder(SetOrderProofId { proof_id: "𑵘i=உ‒'jGXs𐁛.\u{2d7f}ᢰP&𞸤𝕆<🕴/﹨6sò&_" }), AddOrder(687952920), GetBatch(3335856397), GetAggregationProofs, BatchOperation(SetBatchFailure { error: "�`t𝓛𞸤\u{11d91}$/🂫O%KË𐠼PU:𖭟" }), AddOrder(2509982559), AddOrder(4113525281), GetProvingOrder, GetActiveProofs, OperateOnExistingOrder(SetAggregationStatus), OperateOnExistingOrder(SetOrderFailure), AddOrder(3227332288), GetBatch(1997148971), GetAggregationProofs, GetAggregationProofs, GetAggregationProofs, GetActiveProofs, BatchOperation(UpdateBatch { proof_id: "9`/Ѩ", order_count: 70 }), GetActiveProofs, AddOrder(4156301529), AddOrder(752007793), BatchOperation(SetBatchSubmitted), AddOrder(1916394887), OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, GetBatch(2129589048), GetActiveProofs, GetBatch(2215805156), GetActiveProofs, GetAggregationProofs, BatchOperation(SetBatchFailure { error: "🬗M\\ਫ਼N7𐲛e:\u{a0}" }), GetActiveProofs, AddOrder(839835116), GetActiveProofs, BatchOperation(SetBatchFailure { error: "\"$iC\u{1e02a}3t:େ." }), GetBatch(1655014249), GetActiveProofs, OperateOnExistingOrder(SetOrderComplete), GetAggregationProofs, OperateOnExistingOrder(SetAggregationStatus), BatchOperation(CompleteBatch { g16_proof_id: "ꡑ𐙕𐣾`פּఞf𞗲𐳛Ⱥ=»Ⱥ�{p〺𐵢'{" }), AddOrder(791534620), GetAggregationProofs, OperateOnExistingOrder(SetOrderComplete), BatchOperation(CompleteBatch { g16_proof_id: "�𫩱'Ⱥ:" }), BatchOperation(SetBatchSubmitted), OperateOnExistingOrder(GetOrder), AddOrder(691507005), GetProvingOrder, GetBatch(504530438), GetAggregationProofs, GetAggregationProofs, OperateOnExistingOrder(SetAggregationStatus), GetProvingOrder, GetAggregationProofs, GetAggregationProofs, AddOrder(3947372768), GetProvingOrder, AddOrder(2163079076), GetActiveProofs, BatchOperation(GetCompleteBatch), GetAggregationProofs, GetAggregationProofs, OperateOnExistingOrder(SetOrderComplete), AddOrder(164949592), GetBatch(380989067), GetProvingOrder, OperateOnExistingOrder(SetOrderFailure), GetProvingOrder, GetProvingOrder, GetProvingOrder, GetBatch(3736270654), AddOrder(1460963632), OperateOnExistingOrder(GetSubmissionOrder)] diff --git a/crates/broker/src/aggregator.rs b/crates/broker/src/aggregator.rs index c438beed7..0897e1e94 100644 --- a/crates/broker/src/aggregator.rs +++ b/crates/broker/src/aggregator.rs @@ -15,8 +15,12 @@ use alloy::primitives::{utils, Address}; use anyhow::{Context, Result}; use boundless_assessor::{AssessorInput, Fulfillment}; -use boundless_market::{contracts::eip712_domain, input::GuestEnv}; +use boundless_market::{ + contracts::{eip712_domain, FulfillmentClaimData, PredicateType}, + input::GuestEnv, +}; use chrono::Utc; +use hex::FromHex; use risc0_aggregation::GuestState; use risc0_zkvm::{ sha::{Digest, Digestible}, @@ -183,7 +187,6 @@ impl AggregatorService { async fn prove_assessor(&self, order_ids: &[String]) -> Result { let mut fills = vec![]; - let mut assumptions = vec![]; for order_id in order_ids { let order = self @@ -197,8 +200,6 @@ impl AggregatorService { .proof_id .with_context(|| format!("Missing proof_id for order: {order_id}"))?; - assumptions.push(proof_id.clone()); - let journal = self .prover .get_journal(&proof_id) @@ -206,10 +207,20 @@ impl AggregatorService { .with_context(|| format!("Failed to get {proof_id} journal"))? .with_context(|| format!("{proof_id} journal missing"))?; + let fulfillment_data = match order.request.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => FulfillmentClaimData::from_claim_digest( + order.request.requirements.predicate.claim_digest().unwrap(), + ), + _ => FulfillmentClaimData::from_image_id_and_journal( + Digest::from_hex(order.image_id.unwrap()).unwrap(), + journal, + ), + }; + fills.push(Fulfillment { request: order.request.clone(), signature: order.client_sig.clone().to_vec(), - journal, + fulfillment_data, }) } @@ -226,7 +237,7 @@ impl AggregatorService { let proof_res = self .prover - .prove_and_monitor_stark(&self.assessor_guest_id.to_string(), &input_id, assumptions) + .prove_and_monitor_stark(&self.assessor_guest_id.to_string(), &input_id, vec![]) .await .context("Failed to prove assesor stark")?; @@ -684,8 +695,7 @@ mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ ASSESSOR_GUEST_ELF, ASSESSOR_GUEST_ID, ECHO_ELF, ECHO_ID, SET_BUILDER_ELF, SET_BUILDER_ID, @@ -753,10 +763,7 @@ mod tests { // First order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -801,10 +808,7 @@ mod tests { // Second order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 1), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -917,10 +921,7 @@ mod tests { // First order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -980,10 +981,7 @@ mod tests { // Second order let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 1), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1092,10 +1090,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1207,10 +1202,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1330,10 +1322,7 @@ mod tests { let min_price = 200000000000000000u64; let order_request = ProofRequest::new( RequestId::new(customer_signer.address(), 0), - Requirements::new( - image_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -1452,14 +1441,8 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, 999), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), - "http://risczero.com", + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), + "h/risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { minPrice: U256::from(1), @@ -1494,13 +1477,7 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, 1000), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { diff --git a/crates/broker/src/db/fuzz_db.rs b/crates/broker/src/db/fuzz_db.rs index d1d7850dc..a9f5fca55 100644 --- a/crates/broker/src/db/fuzz_db.rs +++ b/crates/broker/src/db/fuzz_db.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloy::primitives::{Address, U256}; +use alloy::primitives::{Address, Bytes, U256}; use chrono::Utc; use elsa::sync::FrozenVec; use proptest::prelude::*; @@ -33,8 +33,7 @@ use crate::{db::AggregationOrder, AggregationState, Order, OrderStatus}; use super::{BrokerDb, SqliteDb}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; // Add new state tracking structure @@ -90,10 +89,7 @@ fn generate_test_order(request_id: u32) -> Order { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, request_id), - Requirements::new( - Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "test", RequestInput { inputType: RequestInputType::Url, data: Default::default() }, Offer { @@ -214,7 +210,7 @@ proptest! { ExistingOrderOperation::GetSubmissionOrder => { let order = db.get_order(id).await.unwrap(); if let Some(order) = order { - if order.proof_id.is_some() && order.lock_price.is_some() { + if order.proof_id.is_some() && order.lock_price.is_some() && order.image_id.is_some() { db.get_submission_order(id).await.unwrap(); } } diff --git a/crates/broker/src/db/mod.rs b/crates/broker/src/db/mod.rs index f8883bab7..f4527778f 100644 --- a/crates/broker/src/db/mod.rs +++ b/crates/broker/src/db/mod.rs @@ -14,7 +14,7 @@ use std::{default::Default, str::FromStr, sync::Arc}; -use alloy::primitives::{ruint::ParseError as RuintParseErr, Bytes, B256, U256}; +use alloy::primitives::{ruint::ParseError as RuintParseErr, Bytes, U256}; use async_trait::async_trait; use chrono::Utc; use sqlx::{ @@ -135,7 +135,7 @@ pub trait BrokerDb { async fn get_submission_order( &self, id: &str, - ) -> Result<(ProofRequest, Bytes, String, B256, U256, FulfillmentType), DbError>; + ) -> Result<(ProofRequest, Bytes, String, String, U256, FulfillmentType), DbError>; async fn get_order_compressed_proof_id(&self, id: &str) -> Result; async fn set_order_failure(&self, id: &str, failure_str: &'static str) -> Result<(), DbError>; async fn set_order_complete(&self, id: &str) -> Result<(), DbError>; @@ -361,14 +361,14 @@ impl BrokerDb for SqliteDb { async fn get_submission_order( &self, id: &str, - ) -> Result<(ProofRequest, Bytes, String, B256, U256, FulfillmentType), DbError> { + ) -> Result<(ProofRequest, Bytes, String, String, U256, FulfillmentType), DbError> { let order = self.get_order(id).await?; if let Some(order) = order { Ok(( order.request.clone(), order.client_sig.clone(), order.proof_id.ok_or(DbError::MissingElm("proof_id"))?, - order.request.requirements.imageId, + order.image_id.ok_or(DbError::MissingElm("image_id"))?, order.lock_price.ok_or(DbError::MissingElm("lock_price"))?, order.fulfillment_type, )) @@ -1062,23 +1062,17 @@ mod tests { use crate::ProofRequest; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, RequestId, RequestInput, RequestInputType, Requirements, + Offer, Predicate, RequestId, RequestInput, RequestInputType, Requirements, }; use risc0_aggregation::GuestState; use risc0_zkvm::sha::Digest; use tracing_test::traced_test; fn create_order_request() -> OrderRequest { - OrderRequest::new( + let mut request = OrderRequest::new( ProofRequest::new( RequestId::new(Address::ZERO, 1), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { @@ -1095,7 +1089,9 @@ mod tests { FulfillmentType::LockAndFulfill, Address::ZERO, 1, - ) + ); + request.image_id = Some(Digest::ZERO.to_string()); + request } fn create_order() -> Order { @@ -1156,12 +1152,12 @@ mod tests { order.lock_price = Some(U256::from(10)); db.add_order(&order).await.unwrap(); - let submit_order: (ProofRequest, Bytes, String, B256, U256, FulfillmentType) = + let submit_order: (ProofRequest, Bytes, String, String, U256, FulfillmentType) = db.get_submission_order(&order.id()).await.unwrap(); assert_eq!(submit_order.0, order.request); assert_eq!(submit_order.1, order.client_sig); assert_eq!(submit_order.2, order.proof_id.unwrap()); - assert_eq!(submit_order.3, order.request.requirements.imageId); + assert_eq!(submit_order.3, order.image_id.unwrap()); assert_eq!(submit_order.4, order.lock_price.unwrap()); assert_eq!(submit_order.5, order.fulfillment_type); } diff --git a/crates/broker/src/lib.rs b/crates/broker/src/lib.rs index 4c48e5878..223e52d20 100644 --- a/crates/broker/src/lib.rs +++ b/crates/broker/src/lib.rs @@ -29,7 +29,7 @@ use anyhow::{Context, Result}; use boundless_market::{ contracts::{boundless_market::BoundlessMarketService, ProofRequest}, order_stream_client::OrderStreamClient, - selector::is_groth16_selector, + selector::{is_groth16_selector, is_shrink_bitvm2_selector}, Deployment, }; use chrono::{serde::ts_seconds, DateTime, Utc}; @@ -307,6 +307,13 @@ impl std::fmt::Display for OrderRequest { } } +#[derive(Debug, PartialEq, Eq)] +enum CompressionType { + None, + Groth16, + ShrinkBitvm2, +} + /// An Order represents a proof request and a specific method of fulfillment. /// /// Requests can be fulfilled in multiple ways, for example by locking then fulfilling them, @@ -388,9 +395,22 @@ impl Order { .clone() } - pub fn is_groth16(&self) -> bool { + fn is_groth16(&self) -> bool { is_groth16_selector(self.request.requirements.selector) } + + fn is_shrink_bitvm2(&self) -> bool { + is_shrink_bitvm2_selector(self.request.requirements.selector) + } + pub fn compression_type(&self) -> CompressionType { + if self.is_groth16() { + CompressionType::Groth16 + } else if self.is_shrink_bitvm2() { + CompressionType::ShrinkBitvm2 + } else { + CompressionType::None + } + } } impl std::fmt::Display for Order { @@ -752,28 +772,31 @@ where } // Construct the prover object interface - let prover: provers::ProverObj = if is_dev_mode() { - tracing::warn!("WARNING: Running the Broker in dev mode does not generate valid receipts. \ - Receipts generated from this process are invalid and should never be used in production."); - Arc::new(provers::DefaultProver::new()) - } else if let (Some(bonsai_api_key), Some(bonsai_api_url)) = - (self.args.bonsai_api_key.as_ref(), self.args.bonsai_api_url.as_ref()) - { - tracing::info!("Configured to run with Bonsai backend"); - Arc::new( - provers::Bonsai::new(config.clone(), bonsai_api_url.as_ref(), bonsai_api_key) - .context("Failed to construct Bonsai client")?, - ) - } else if let Some(bento_api_url) = self.args.bento_api_url.as_ref() { - tracing::info!("Configured to run with Bento backend"); - - Arc::new( - provers::Bonsai::new(config.clone(), bento_api_url.as_ref(), "") - .context("Failed to initialize Bento client")?, - ) - } else { - Arc::new(provers::DefaultProver::new()) - }; + // let prover: provers::ProverObj = if is_dev_mode() { + // tracing::warn!("WARNING: Running the Broker in dev mode does not generate valid receipts. \ + // Receipts generated from this process are invalid and should never be used in production."); + // Arc::new(provers::DefaultProver::new()) + // } else if let (Some(bonsai_api_key), Some(bonsai_api_url)) = + // (self.args.bonsai_api_key.as_ref(), self.args.bonsai_api_url.as_ref()) + // { + // tracing::info!("Configured to run with Bonsai backend"); + // Arc::new( + // provers::Bonsai::new(config.clone(), bonsai_api_url.as_ref(), bonsai_api_key) + // .context("Failed to construct Bonsai client")?, + // ) + // } else if let Some(bento_api_url) = self.args.bento_api_url.as_ref() { + // tracing::info!("Configured to run with Bento backend"); + + // Arc::new( + // provers::Bonsai::new(config.clone(), bento_api_url.as_ref(), "") + // .context("Failed to initialize Bento client")?, + // ) + // } else { + // tracing::warn!("LOCAL PROBVER!!"); + // Arc::new(provers::DefaultProver::new()) + // }; + + let prover: provers::ProverObj = Arc::new(provers::DefaultProver::new()); let (pricing_tx, pricing_rx) = mpsc::channel(PRICING_CHANNEL_CAPACITY); diff --git a/crates/broker/src/market_monitor.rs b/crates/broker/src/market_monitor.rs index c75174293..33a394fdd 100644 --- a/crates/broker/src/market_monitor.rs +++ b/crates/broker/src/market_monitor.rs @@ -622,7 +622,7 @@ mod tests { use alloy::{ network::EthereumWallet, node_bindings::Anvil, - primitives::{Address, U256}, + primitives::{Address, Bytes, U256}, providers::{ext::AnvilApi, ProviderBuilder, WalletProvider}, signers::local::PrivateKeySigner, sol_types::eip712_domain, @@ -631,8 +631,8 @@ mod tests { contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx}, hit_points::default_allowance, - AssessorReceipt, Offer, Predicate, PredicateType, ProofRequest, RequestInput, - RequestInputType, Requirements, + AssessorReceipt, Offer, Predicate, ProofRequest, RequestInput, RequestInputType, + Requirements, }, input::GuestEnv, }; @@ -675,10 +675,10 @@ mod tests { let max_price = 10; let proving_request = ProofRequest { id: boundless_market.request_id_from_nonce().await.unwrap(), - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "test".to_string(), input: RequestInput { inputType: RequestInputType::Url, data: Default::default() }, offer: Offer { @@ -808,21 +808,18 @@ mod tests { .unwrap(); assert!(ctx.customer_market.is_fulfilled(request_id).await.unwrap()); - // retrieve journal and seal from the fulfilled request - let (journal, seal) = + // retrieve fulfillment data and seal from the fulfilled request + let (fulfillment_data, seal) = ctx.customer_market.get_request_fulfillment(request_id).await.unwrap(); - assert_eq!(journal, fulfillment.journal); + assert_eq!(fulfillment_data, fulfillment.fulfillmentData); assert_eq!(seal, fulfillment.seal); } async fn new_request(idx: u32, ctx: &TestCtx

) -> ProofRequest { ProofRequest::new( RequestId::new(ctx.customer_signer.address(), idx), - Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { diff --git a/crates/broker/src/order_monitor.rs b/crates/broker/src/order_monitor.rs index f086a7225..8709f8866 100644 --- a/crates/broker/src/order_monitor.rs +++ b/crates/broker/src/order_monitor.rs @@ -963,6 +963,7 @@ pub(crate) mod tests { use crate::OrderStatus; use crate::{db::SqliteDb, now_timestamp, FulfillmentType}; use alloy::node_bindings::AnvilInstance; + use alloy::primitives::Bytes; use alloy::{ network::EthereumWallet, node_bindings::Anvil, @@ -977,8 +978,7 @@ pub(crate) mod tests { signers::local::PrivateKeySigner, }; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ deploy_boundless_market, deploy_hit_points, ASSESSOR_GUEST_ID, ASSESSOR_GUEST_PATH, @@ -1027,13 +1027,7 @@ pub(crate) mod tests { let request = ProofRequest::new( RequestId::new(self.signer.address(), request_id), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { diff --git a/crates/broker/src/order_picker.rs b/crates/broker/src/order_picker.rs index 1f1b03f38..b98319af4 100644 --- a/crates/broker/src/order_picker.rs +++ b/crates/broker/src/order_picker.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use hex::FromHex; use risc0_zkvm::sha::Digest; use sha2::{Digest as Sha2Digest, Sha256}; use std::collections::BTreeMap; @@ -43,8 +44,11 @@ use alloy::{ }; use anyhow::{Context, Result}; use boundless_market::{ - contracts::{boundless_market::BoundlessMarketService, RequestError, RequestInputType}, - selector::SupportedSelectors, + contracts::{ + boundless_market::BoundlessMarketService, FulfillmentClaimData, PredicateType, + RequestError, RequestInputType, + }, + selector::{is_shrink_bitvm2_selector, SupportedSelectors}, }; use moka::future::Cache; use thiserror::Error; @@ -476,21 +480,21 @@ where ); // Create cache key based on input type - let image_id = Digest::from(order.request.requirements.imageId.0); + let predicate_data = order.request.requirements.predicate.data.to_vec(); let cache_key = match order.request.input.inputType { RequestInputType::Url => { let input_url = std::str::from_utf8(&order.request.input.data) .context("input url is not utf8") .map_err(|e| OrderPickerErr::FetchInputErr(Arc::new(e)))? .to_string(); - PreflightCacheKey { image_id, input: InputCacheKey::Url(input_url) } + PreflightCacheKey { predicate_data, input: InputCacheKey::Url(input_url) } } RequestInputType::Inline => { // For inline inputs, use SHA256 hash of the data let mut hasher = Sha256::new(); Sha2Digest::update(&mut hasher, &order.request.input.data); let input_hash: [u8; 32] = hasher.finalize().into(); - PreflightCacheKey { image_id, input: InputCacheKey::Hash(input_hash) } + PreflightCacheKey { predicate_data, input: InputCacheKey::Hash(input_hash) } } RequestInputType::__Invalid => { return Err(OrderPickerErr::UnexpectedErr(Arc::new(anyhow::anyhow!( @@ -506,7 +510,7 @@ where let config = self.config.clone(); let request = order.request.clone(); let order_id_clone = order_id.clone(); - let cache_key_clone = cache_key.clone(); + let cache_key_clone: PreflightCacheKey = cache_key.clone(); let cache_cloned = self.preflight_cache.clone(); let result = tokio::task::spawn(async move { @@ -656,12 +660,38 @@ where return Ok(Skip); } - // Validate the predicates: - if !order.request.requirements.predicate.eval(journal.clone()) { - tracing::info!("Order {order_id} predicate check failed, skipping"); + // If the selector is a shrink bitvm2 selector, ensure the journal is exactly 32 bytes + if is_shrink_bitvm2_selector(order.request.requirements.selector) && journal.len() != 32 { + tracing::info!( + "Order {order_id} journal is not 32 bytes for shrink bitvm2 selector, skipping", + ); return Ok(Skip); } + // Validate the predicates: + match order.request.requirements.predicate.predicateType { + PredicateType::ClaimDigestMatch => { + tracing::info!("Skip order {order_id} predicate match: ClaimDigestMatch"); + } + _ => { + if !order.request.requirements.predicate.eval( + &FulfillmentClaimData::from_image_id_and_journal( + Digest::from_hex( + order + .image_id + .as_ref() + .expect("image id should be populated because we preflighted"), + ) + .unwrap(), + journal, + ), + ) { + tracing::info!("Order {order_id} predicate check failed, skipping"); + return Ok(Skip); + } + } + } + self.evaluate_order(order, &proof_res, order_gas_cost, lock_expired).await } @@ -1020,7 +1050,7 @@ enum InputCacheKey { /// Key type for the preflight cache #[derive(Hash, Eq, PartialEq, Clone, Debug)] struct PreflightCacheKey { - image_id: Digest, + predicate_data: Vec, input: InputCacheKey, } @@ -1321,7 +1351,7 @@ fn calculate_max_cycles_for_time(prove_khz: u64, time_seconds: u64) -> u64 { #[cfg(test)] pub(crate) mod tests { - use std::time::Duration; + use std::{path::PathBuf, time::Duration}; use super::*; use crate::{ @@ -1333,14 +1363,13 @@ pub(crate) mod tests { use alloy::{ network::EthereumWallet, node_bindings::{Anvil, AnvilInstance}, - primitives::{address, aliases::U96, utils::parse_units, Address, Bytes, FixedBytes, B256}, + primitives::{address, aliases::U96, utils::parse_units, Address, Bytes, FixedBytes}, providers::{ext::AnvilApi, ProviderBuilder}, signers::local::PrivateKeySigner, }; use async_trait::async_trait; use boundless_market::contracts::{ - Callback, Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, - Requirements, + Callback, Offer, Predicate, ProofRequest, RequestId, RequestInput, Requirements, }; use boundless_market::storage::{MockStorageProvider, StorageProvider}; use boundless_market_test_utils::{ @@ -1409,13 +1438,7 @@ pub(crate) mod tests { Box::new(OrderRequest { request: ProofRequest::new( RequestId::new(self.provider.default_signer_address(), params.order_index), - Requirements::new( - image_id, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), image_url, RequestInput::builder() .write_slice(&[0x41, 0x41, 0x41, 0x41]) @@ -1458,13 +1481,7 @@ pub(crate) mod tests { Box::new(OrderRequest { request: ProofRequest::new( RequestId::new(self.provider.default_signer_address(), params.order_index), - Requirements::new( - image_id, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(image_id, Bytes::default())), image_url, RequestInput::builder() .write(&cycles) @@ -1643,7 +1660,7 @@ pub(crate) mod tests { let mut order = ctx.generate_next_order(Default::default()).await; // set a bad predicate order.request.requirements.predicate = - Predicate { predicateType: PredicateType::DigestMatch, data: B256::ZERO.into() }; + Predicate::digest_match(Digest::from(ECHO_ID), Digest::ZERO); let order_id = order.id(); let _request_id = @@ -1842,8 +1859,8 @@ pub(crate) mod tests { let mut ctx = PickerTestCtxBuilder::default().with_config(config).build().await; // NOTE: Values currently adjusted ad hoc to be between the two thresholds. - let min_price = parse_ether("0.0013").unwrap(); - let max_price = parse_ether("0.0013").unwrap(); + let min_price = parse_ether("0.0012").unwrap(); + let max_price = parse_ether("0.0012").unwrap(); // Order should have high enough price with the default selector. let order = ctx @@ -2713,6 +2730,19 @@ pub(crate) mod tests { ) -> Result>, ProverError> { self.default_prover.get_compressed_receipt(proof_id).await } + async fn shrink_bitvm2( + &self, + _proof_id: &str, + _work_dir: Option, + ) -> Result { + todo!("Shrink BitVM is not implemented yet"); + } + async fn get_shrink_bitvm2_receipt( + &self, + _proof_id: &str, + ) -> Result>, ProverError> { + todo!("Shrink BitVM is not implemented yet"); + } } #[tokio::test] diff --git a/crates/broker/src/provers/bonsai.rs b/crates/broker/src/provers/bonsai.rs index ed1f90b1d..253044f3d 100644 --- a/crates/broker/src/provers/bonsai.rs +++ b/crates/broker/src/provers/bonsai.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::future::Future; +use std::{future::Future, path::PathBuf}; use async_trait::async_trait; use bonsai_sdk::{ - non_blocking::{Client as BonsaiClient, SessionId, SnarkId}, + non_blocking::{Client as BonsaiClient, SessionId, ShrinkBitvm2Id, SnarkId}, SdkErr, }; use risc0_zkvm::Receipt; @@ -274,6 +274,46 @@ impl StatusPoller { } } } + + async fn poll_with_retries_shrink_bitvm2_id( + &self, + proof_id: &ShrinkBitvm2Id, + client: &BonsaiClient, + ) -> Result { + loop { + let status = retry::<_, SdkErr, _, _>( + self.retry_counts, + self.poll_sleep_ms, + || async { proof_id.status(client).await }, + "get snark status", + ) + .await; + + if let Err(_err) = status { + return Err(ProverError::StatusFailure); + } + + let status = status.unwrap(); + + match status.status.as_ref() { + "RUNNING" => { + tokio::time::sleep(tokio::time::Duration::from_millis(self.poll_sleep_ms)) + .await; + continue; + } + "SUCCEEDED" => return Ok(proof_id.uuid.clone()), + status_code => { + let err_msg = status.error_msg.unwrap_or_default(); + if err_msg.contains("INTERNAL_ERROR") { + return Err(ProverError::ProverInternalError(err_msg.clone())); + } + return Err(ProverError::ProvingFailed(format!( + "snark proving failed with status {status_code}: {err_msg}" + ))); + } + } + } + } } #[async_trait] @@ -523,6 +563,50 @@ impl Prover for Bonsai { Ok(Some(receipt_buf)) } + + async fn shrink_bitvm2( + &self, + proof_id: &str, + _work_dir: Option, + ) -> Result { + let proof_id = self + .retry( + || async { Ok(self.client.shrink_bitvm2(proof_id.into()).await?) }, + "create snark", + ) + .await?; + + let poller = StatusPoller { + poll_sleep_ms: self.status_poll_ms, + retry_counts: self.status_poll_retry_count, + }; + + poller.poll_with_retries_shrink_bitvm2_id(&proof_id, &self.client).await?; + + Ok(proof_id.uuid) + } + async fn get_shrink_bitvm2_receipt( + &self, + proof_id: &str, + ) -> Result>, ProverError> { + let snark_id = ShrinkBitvm2Id { uuid: proof_id.into() }; + let status = self + .retry( + || async { Ok(snark_id.status(&self.client).await?) }, + "get status of bitvm2 snark", + ) + .await?; + + let Some(output) = status.output else { return Ok(None) }; + let receipt_buf = self + .retry( + || async { Ok(self.client.download(&output).await?) }, + "download bitvm2 snark output", + ) + .await?; + + Ok(Some(receipt_buf)) + } } async fn create_pg_pool() -> Result { diff --git a/crates/broker/src/provers/default.rs b/crates/broker/src/provers/default.rs index 2dead678e..8a60b0d01 100644 --- a/crates/broker/src/provers/default.rs +++ b/crates/broker/src/provers/default.rs @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{borrow::Borrow, collections::HashMap, sync::Arc}; +use std::{borrow::Borrow, collections::HashMap, path::PathBuf, sync::Arc}; use crate::config::ProverConf; use crate::provers::{ExecutorResp, ProofResult, Prover, ProverError}; -use anyhow::{Context, Result as AnyhowResult}; +use anyhow::{anyhow, Context, Result as AnyhowResult}; use async_trait::async_trait; use risc0_zkvm::{ default_executor, default_prover, ExecutorEnv, ProveInfo, ProverOpts, Receipt, SessionInfo, VERSION, }; +use tempfile::tempdir; use tokio::sync::RwLock; use uuid::Uuid; @@ -345,6 +346,7 @@ impl Prover for DefaultProver { } async fn compress(&self, proof_id: &str) -> Result { + tracing::info!("Compressing proof Groth16 {proof_id}"); let receipt = self .get_receipt(proof_id) .await? @@ -395,13 +397,84 @@ impl Prover for DefaultProver { .ok_or_else(|| ProverError::NotFound(format!("proof {proof_id}")))?; Ok(proof_data.compressed_receipt.as_ref().cloned()) } + + async fn shrink_bitvm2( + &self, + proof_id: &str, + work_dir: Option, + ) -> Result { + let temp_dir = tempdir().context("Failed to crate tmpdir")?; + tracing::info!("Compressing proof Shrink bitvm2 {proof_id}"); + let receipt = self + .get_receipt(proof_id) + .await? + .ok_or_else(|| ProverError::NotFound(format!("no receipt for proof {proof_id}")))?; + if receipt.journal.bytes.len() != 32 { + return Err(ProverError::UnexpectedError(anyhow!( + "Shrink BitVM2 requires a journal of 32 bytes, got {}", + receipt.journal.bytes.len() + ))); + } + let proof_id = format!("snark_{}", Uuid::new_v4()); + self.state.proofs.write().await.insert(proof_id.clone(), ProofData::default()); + + tracing::debug!("shrink bitvm2 identity_p254 for proof {proof_id}"); + let succinct_receipt = receipt.inner.succinct().unwrap(); + let p254_receipt = risc0_zkvm::recursion::identity_p254(succinct_receipt) + .context("identity predicate failed")?; + + tracing::info!("Completing identity predicate, {proof_id}"); + + let work_dir = + if let Some(work_dir) = work_dir { work_dir } else { temp_dir.path().to_path_buf() }; + + let compress_result = + shrink_bitvm2::prove_and_verify(&proof_id, &work_dir, p254_receipt, receipt.journal) + .await + .map_err(ProverError::from); + + let compressed_bytes = compress_result + .as_ref() + .map(|receipt| bincode::serialize(receipt).unwrap()) + .unwrap_or_default(); + + let mut proofs = self.state.proofs.write().await; + let proof = proofs.get_mut(&proof_id).unwrap(); + match compress_result { + Ok(_) => { + proof.status = Status::Succeeded; + proof.compressed_receipt = Some(compressed_bytes); + + Ok(proof_id) + } + Err(err) => { + proof.status = Status::Failed; + proof.error_msg = err.to_string(); + + Err(err) + } + } + } + async fn get_shrink_bitvm2_receipt( + &self, + proof_id: &str, + ) -> Result>, ProverError> { + let proofs = self.state.proofs.read().await; + let proof_data = proofs + .get(proof_id) + .ok_or_else(|| ProverError::NotFound(format!("proof {proof_id}")))?; + Ok(proof_data.compressed_receipt.as_ref().cloned()) + } } #[cfg(test)] mod tests { use super::*; + use ark_ff::PrimeField; use boundless_market_test_utils::{ECHO_ELF, ECHO_ID}; - use risc0_zkvm::sha::Digest; + use risc0_zkvm::{sha::Digest, Groth16Seal}; + use shrink_bitvm2::ShrinkBitvm2ReceiptClaim; + use tempfile::tempdir; use tokio::test; #[test] @@ -496,6 +569,53 @@ mod tests { receipt.verify(image_id).unwrap(); } + #[test_log::test(tokio::test)] + async fn test_shrink() { + let work_dir = tempdir().expect("Failed to create temp dir"); + let prover = DefaultProver::new(); + + // Upload test data + let input_data = [255u8; 32].to_vec(); // Example input data + let input_id = prover.upload_input(input_data.clone()).await.unwrap(); + let image_id = Digest::from(ECHO_ID); + prover.upload_image(&image_id.to_string(), ECHO_ELF.to_vec()).await.unwrap(); + + // Run SNARK proving + let ProofResult { id: stark_id, .. } = + prover.prove_and_monitor_stark(&image_id.to_string(), &input_id, vec![]).await.unwrap(); + + let snark_id = + prover.shrink_bitvm2(&stark_id, Some(work_dir.path().to_path_buf())).await.unwrap(); + + // Fetch the compressed receipt + let compressed_receipt = prover.get_compressed_receipt(&snark_id).await.unwrap().unwrap(); + let shrink_receipt: Receipt = bincode::deserialize(&compressed_receipt).unwrap(); + + let stark_receipt = prover.get_receipt(&stark_id).await.unwrap().unwrap(); + + let groth16_receipt = shrink_receipt.inner.groth16().unwrap(); + let groth16_seal = Groth16Seal::from_vec(&groth16_receipt.seal) + .expect("Failed to create Groth16 seal from receipt"); + + let final_output_bytes = + ShrinkBitvm2ReceiptClaim::ok(image_id, stark_receipt.journal.bytes).digest().into(); + let public_input_scalar = ark_bn254::Fr::from_be_bytes_mod_order(&final_output_bytes); + + let public_input_scalar_str = public_input_scalar.to_string(); + let public_input_scalar = + risc0_groth16::PublicInputsJson { values: vec![public_input_scalar_str] } + .to_scalar() + .unwrap(); + println!("R0 Verify Start"); + + let verifying_key = shrink_bitvm2::get_r0_verifying_key(); + + let v = risc0_groth16::Verifier::new(&groth16_seal, &public_input_scalar, &verifying_key) + .unwrap(); + println!("R0 Verify result: {:?}", v.verify().is_ok()); + assert!(v.verify().is_ok(), "R0 verification failed"); + } + #[test] async fn test_error_handling() { let prover = DefaultProver::new(); diff --git a/crates/broker/src/provers/mod.rs b/crates/broker/src/provers/mod.rs index 394eeab9d..b59dccb98 100644 --- a/crates/broker/src/provers/mod.rs +++ b/crates/broker/src/provers/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use async_trait::async_trait; use bonsai_sdk::SdkErr; @@ -140,6 +140,16 @@ pub trait Prover { async fn get_journal(&self, proof_id: &str) -> Result>, ProverError>; async fn compress(&self, proof_id: &str) -> Result; async fn get_compressed_receipt(&self, proof_id: &str) -> Result>, ProverError>; + + async fn shrink_bitvm2( + &self, + proof_id: &str, + work_dir: Option, + ) -> Result; + async fn get_shrink_bitvm2_receipt( + &self, + proof_id: &str, + ) -> Result>, ProverError>; } pub type ProverObj = Arc; diff --git a/crates/broker/src/proving.rs b/crates/broker/src/proving.rs index 362160195..3fd5b134d 100644 --- a/crates/broker/src/proving.rs +++ b/crates/broker/src/proving.rs @@ -24,7 +24,7 @@ use crate::{ provers::ProverObj, task::{RetryRes, RetryTask, SupervisorErr}, utils::cancel_proof_and_fail_order, - Order, OrderStateChange, OrderStatus, + CompressionType, Order, OrderStateChange, OrderStatus, }; use anyhow::{Context, Result}; use thiserror::Error; @@ -92,7 +92,7 @@ impl ProvingService { &self, order_id: &str, stark_proof_id: &str, - is_groth16: bool, + compression_type: CompressionType, snark_proof_id: Option, ) -> Result { let proof_res = self @@ -101,9 +101,25 @@ impl ProvingService { .await .context("Monitoring proof (stark) failed")?; - if is_groth16 && snark_proof_id.is_none() { - let compressed_proof_id = - self.prover.compress(stark_proof_id).await.context("Failed to compress proof")?; + tracing::info!( + "compression_type: {compression_type:?}, snark_proof_id: {snark_proof_id:?}" + ); + let is_compress = compression_type != CompressionType::None; + + if is_compress && snark_proof_id.is_none() { + let compressed_proof_id = match compression_type { + CompressionType::Groth16 => self + .prover + .compress(stark_proof_id) + .await + .context("Failed to compress proof")?, + CompressionType::ShrinkBitvm2 => self + .prover + .shrink_bitvm2(stark_proof_id, None) + .await + .context("Failed to shrink proof")?, + CompressionType::None => unreachable!("Compression type should not be None here"), + }; self.db .set_order_compressed_proof_id(order_id, &compressed_proof_id) .await @@ -114,7 +130,7 @@ impl ProvingService { })?; }; - let status = match is_groth16 { + let status = match is_compress { false => OrderStatus::PendingAgg, true => OrderStatus::SkipAggregation, }; @@ -227,7 +243,7 @@ impl ProvingService { let monitor_task = self.monitor_proof_internal( &order_id, proof_id, - order.is_groth16(), + order.compression_type(), order.compressed_proof_id, ); tokio::pin!(monitor_task); @@ -454,7 +470,7 @@ mod tests { }; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestInput, RequestInputType, Requirements, + Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements, }; use boundless_market_test_utils::{ECHO_ELF, ECHO_ID}; use chrono::Utc; @@ -476,13 +492,11 @@ mod tests { target_timestamp: Some(0), request: ProofRequest { id: request_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Bytes::default(), + )), + imageUrl: "http://risczero.com/image".into(), input: RequestInput { inputType: RequestInputType::Inline, @@ -630,13 +644,11 @@ mod tests { target_timestamp: Some(0), request: ProofRequest { id: order_id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Bytes::default(), + )), + imageUrl: "http://risczero.com/image".into(), input: RequestInput { inputType: RequestInputType::Inline, diff --git a/crates/broker/src/reaper.rs b/crates/broker/src/reaper.rs index 7ba61a83d..af0ce1c37 100644 --- a/crates/broker/src/reaper.rs +++ b/crates/broker/src/reaper.rs @@ -143,8 +143,7 @@ mod tests { }; use alloy::primitives::{Address, Bytes, U256}; use boundless_market::contracts::{ - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, RequestInputType, - Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }; use chrono::Utc; use risc0_zkvm::sha::Digest; @@ -162,13 +161,7 @@ mod tests { target_timestamp: None, request: ProofRequest::new( RequestId::new(Address::ZERO, id as u32), - Requirements::new( - Digest::ZERO, - Predicate { - predicateType: PredicateType::PrefixMatch, - data: Default::default(), - }, - ), + Requirements::new(Predicate::prefix_match(Digest::ZERO, Bytes::default())), "http://risczero.com", RequestInput { inputType: RequestInputType::Inline, data: "".into() }, Offer { diff --git a/crates/broker/src/storage.rs b/crates/broker/src/storage.rs index 9c40fa606..0beff6605 100644 --- a/crates/broker/src/storage.rs +++ b/crates/broker/src/storage.rs @@ -23,6 +23,7 @@ use aws_sdk_s3::{ Client as S3Client, }; use futures::StreamExt; +use hex::FromHex; use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; @@ -80,9 +81,9 @@ pub(crate) async fn create_uri_handler( match uri.scheme() { "file" => { - if !is_dev_mode() { - return Err(StorageErr::UnsupportedScheme("file".to_string())); - } + // if !is_dev_mode() { + // return Err(StorageErr::UnsupportedScheme("file".to_string())); + // } let max_size = if skip_max_size_check { usize::MAX } else { @@ -347,18 +348,20 @@ pub async fn upload_image_uri( request: &crate::ProofRequest, config: &crate::config::ConfigLock, ) -> Result { - let required_image_id = Digest::from(request.requirements.imageId.0); - let image_id_str = required_image_id.to_string(); - if prover.has_image(&image_id_str).await? { - tracing::debug!( - "Skipping program upload for cached image ID: {image_id_str} for request {:x}", - request.id - ); - return Ok(image_id_str); + let image_id_str = request.requirements.image_id().map(|image_id| image_id.to_string()); + // When predicate is ClaimDigestMatch, we do not have the image id, so we must always download and upload the image. + if let Some(ref image_id_str) = image_id_str { + if prover.has_image(image_id_str).await? { + tracing::debug!( + "Skipping program upload for cached image ID: {image_id_str} for request {:x}", + request.id + ); + return Ok(image_id_str.clone()); + } } tracing::debug!( - "Fetching program for request {:x} with image ID {image_id_str} from URI {}", + "Fetching program for request {:x} with image ID {image_id_str:?} from URI {}", request.id, request.imageUrl ); @@ -373,12 +376,17 @@ pub async fn upload_image_uri( let image_id = risc0_zkvm::compute_image_id(&image_data) .context(format!("Failed to compute image ID for request {:x}", request.id))?; - anyhow::ensure!( - image_id == required_image_id, - "image ID does not match requirements; expect {}, got {}", - required_image_id, - image_id - ); + if let Some(ref image_id_str) = image_id_str { + let required_image_id = Digest::from_hex(image_id_str)?; + anyhow::ensure!( + image_id == required_image_id, + "image ID does not match requirements; expect {}, got {}", + required_image_id, + image_id + ); + } + + let image_id_str = image_id.to_string(); tracing::debug!( "Uploading program for request {:x} with image ID {image_id_str} to prover", diff --git a/crates/broker/src/submitter.rs b/crates/broker/src/submitter.rs index 15e8daec7..266ea1caa 100644 --- a/crates/broker/src/submitter.rs +++ b/crates/broker/src/submitter.rs @@ -27,10 +27,12 @@ use anyhow::{anyhow, Context, Result}; use boundless_market::{ contracts::{ boundless_market::{BoundlessMarketService, FulfillmentTx, MarketError, UnlockedRequest}, - encode_seal, AssessorJournal, AssessorReceipt, Fulfillment, + encode_seal, AssessorJournal, AssessorReceipt, Fulfillment, FulfillmentData, + FulfillmentDataType, PredicateType, }, - selector::is_groth16_selector, + selector::{is_groth16_selector, is_shrink_bitvm2_selector}, }; +use hex::FromHex; use risc0_aggregation::{SetInclusionReceipt, SetInclusionReceiptVerifierParameters}; use risc0_ethereum_contracts::set_verifier::SetVerifierService; use risc0_zkvm::{ @@ -274,6 +276,8 @@ where "Failed to get order from DB for submission, order NOT finalized", )?; + let order_img_id = + Digest::from_hex(order_img_id).context("Failed to decode order image ID")?; let mut stake_reward = U256::ZERO; if fulfillment_type == FulfillmentType::FulfillAfterLockExpire { requests_to_price @@ -290,7 +294,13 @@ where .context("Failed to get order journal from prover")? .context("Order proof Journal missing")?; - let seal = if is_groth16_selector(order_request.requirements.selector) { + // NOTE: We assume here that the order execution ended with exit code 0. + let order_claim = + ReceiptClaim::ok(order_img_id, MaybePruned::Pruned(order_journal.digest())); + let order_claim_digest = order_claim.digest(); + let seal = if is_groth16_selector(order_request.requirements.selector) + || is_shrink_bitvm2_selector(order_request.requirements.selector) + { let compressed_proof_id = self.db.get_order_compressed_proof_id(order_id).await.context( "Failed to get order compressed proof ID from DB for submission", @@ -299,15 +309,10 @@ where .await .context("Failed to fetch and encode g16 proof")? } else { - // NOTE: We assume here that the order execution ended with exit code 0. - let order_claim = ReceiptClaim::ok( - order_img_id.0, - MaybePruned::Pruned(order_journal.digest()), - ); let order_claim_index = aggregation_state .claim_digests .iter() - .position(|claim| *claim == order_claim.digest()) + .position(|claim| *claim == order_claim_digest) .ok_or(anyhow!( "Failed to find order claim {order_claim:x?} in aggregated claims" ))?; @@ -317,7 +322,7 @@ where ); tracing::debug!( "Merkle path for order {order_id} : {:x?} : {order_path:x?}", - order_claim.digest() + order_claim_digest ); let set_inclusion_receipt = SetInclusionReceipt::from_path_with_verifier_params( order_claim, @@ -333,11 +338,35 @@ where .eip712_signing_hash(&self.market.eip712_domain().await?.alloy_struct()); let request_id = order_request.id; fulfillment_to_order_id.insert(request_id, order_id); + let predicate_type = order_request.requirements.predicate.predicateType; + + // For now, we default to not providing journals with claim digest match, but you could if it is a R0 ZKVM commit digest. + let (claim_digest, fulfillment_data, fulfillment_data_type) = match predicate_type { + PredicateType::ClaimDigestMatch => ( + order_request.requirements.predicate.data.0.as_ref().try_into().unwrap(), + vec![], + FulfillmentDataType::None, + ), + PredicateType::PrefixMatch | PredicateType::DigestMatch => ( + order_claim_digest, + FulfillmentData { + imageId: <[u8; 32]>::from(order_img_id).into(), + journal: order_journal.into(), + } + .abi_encode(), + FulfillmentDataType::ImageIdAndJournal, + ), + _ => { + return Err(anyhow!("Invalid predicate type: {predicate_type:?}")); + } + }; + fulfillments.push(Fulfillment { id: request_id, requestDigest: request_digest, - imageId: order_img_id, - journal: order_journal.into(), + fulfillmentData: fulfillment_data.into(), + fulfillmentDataType: fulfillment_data_type, + claimDigest: <[u8; 32]>::from(claim_digest).into(), seal: seal.into(), }); anyhow::Ok(()) @@ -637,14 +666,14 @@ mod tests { use alloy::{ network::EthereumWallet, node_bindings::{Anvil, AnvilInstance}, - primitives::U256, + primitives::{Bytes, U256}, providers::ProviderBuilder, signers::local::PrivateKeySigner, }; use boundless_assessor::{AssessorInput, Fulfillment}; use boundless_market::{ contracts::{ - hit_points::default_allowance, Offer, Predicate, PredicateType, ProofRequest, + hit_points::default_allowance, FulfillmentClaimData, Offer, Predicate, ProofRequest, RequestId, RequestInput, RequestInputType, Requirements, }, input::GuestEnv, @@ -740,10 +769,7 @@ mod tests { let order_request = ProofRequest::new( RequestId::new(customer_addr, market_customer.index_from_nonce().await.unwrap()), - Requirements::new( - echo_id, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(echo_id, Bytes::default())), "http://risczero.com/image", RequestInput { inputType: RequestInputType::Inline, data: Default::default() }, Offer { @@ -769,7 +795,10 @@ mod tests { fills: vec![Fulfillment { request: order_request.clone(), signature: client_sig.into(), - journal: echo_receipt.journal.bytes.clone(), + fulfillment_data: FulfillmentClaimData::from_image_id_and_journal( + echo_id, + echo_receipt.journal.bytes.clone(), + ), }], prover_address: prover_addr, }; diff --git a/crates/broker/src/tests/e2e.rs b/crates/broker/src/tests/e2e.rs index 0cca42270..bc0923e80 100644 --- a/crates/broker/src/tests/e2e.rs +++ b/crates/broker/src/tests/e2e.rs @@ -17,14 +17,18 @@ use std::{future::Future, path::PathBuf}; use crate::{config::Config, now_timestamp, Args, Broker}; use alloy::{ node_bindings::Anvil, - primitives::{aliases::U96, utils, utils::parse_ether, Address, FixedBytes, U256}, + primitives::{ + aliases::U96, + utils::{self, parse_ether}, + Address, Bytes, FixedBytes, U256, + }, providers::{Provider, WalletProvider}, signers::local::PrivateKeySigner, }; use boundless_market::{ contracts::{ - hit_points::default_allowance, Callback, Offer, Predicate, PredicateType, ProofRequest, - RequestId, RequestInput, Requirements, + hit_points::default_allowance, Callback, Offer, Predicate, ProofRequest, RequestId, + RequestInput, Requirements, }, selector::{is_groth16_selector, ProofType}, storage::{MockStorageProvider, StorageProvider}, @@ -56,10 +60,9 @@ fn generate_request( callback: Option, offer: Option, ) -> ProofRequest { - let mut requirements = Requirements::new( - Digest::from(ECHO_ID), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ); + let mut requirements = + Requirements::new(Predicate::prefix_match(Digest::from(ECHO_ID), Bytes::default())); + if proof_type == ProofType::Groth16 { requirements = requirements.with_groth16_proof(); } diff --git a/crates/broker/src/utils.rs b/crates/broker/src/utils.rs index 8233ad8dd..62861c751 100644 --- a/crates/broker/src/utils.rs +++ b/crates/broker/src/utils.rs @@ -96,7 +96,7 @@ pub async fn estimate_gas_to_fulfill( .context("unsupported selector")? { ProofType::Any | ProofType::Inclusion => 0, - ProofType::Groth16 => groth16, + ProofType::Groth16 | ProofType::ShrinkBitvm2 => groth16, proof_type => { tracing::warn!("Unknown proof type in gas cost estimation: {proof_type:?}"); 0 diff --git a/crates/guest/assessor/assessor-guest/src/main.rs b/crates/guest/assessor/assessor-guest/src/main.rs index 425902165..281fe52f1 100644 --- a/crates/guest/assessor/assessor-guest/src/main.rs +++ b/crates/guest/assessor/assessor-guest/src/main.rs @@ -7,7 +7,6 @@ #![no_std] extern crate alloc; - use alloc::vec::Vec; use alloy_primitives::{Address, U256}; use alloy_sol_types::{SolStruct, SolValue}; @@ -16,10 +15,7 @@ use boundless_market::contracts::{ AssessorCallback, AssessorCommitment, AssessorJournal, AssessorSelector, RequestId, UNSPECIFIED_SELECTOR, }; -use risc0_zkvm::{ - guest::env, - sha::{Digest, Digestible}, -}; +use risc0_zkvm::{guest::env, sha::Digest}; risc0_zkvm::guest::entry!(main); @@ -45,9 +41,9 @@ fn main() { // For each fill we // - verify the request's signature // - evaluate the request's requirements - // - verify the integrity of its claim // - record the callback if it exists // - record the selector if it is present + // NOTE: We do not verify integrity of the claim. That is done on chain. // We additionally collect the request and claim digests. for (index, fill) in input.fills.iter().enumerate() { // Attempt to decode the request ID. If this fails, there may be flags that are not handled @@ -64,8 +60,7 @@ fn main() { fill.verify_signature(&eip_domain_separator).expect("signature does not verify") }; fill.evaluate_requirements().expect("requirements not met"); - env::verify_integrity(&fill.receipt_claim()).expect("claim integrity check failed"); - let claim_digest = fill.receipt_claim().digest(); + let claim_digest = fill.claim_digest().expect("failed to get claim digest"); let commit = AssessorCommitment { index: U256::from(index), id: fill.request.id, @@ -74,13 +69,20 @@ fn main() { } .eip712_hash_struct(); leaves.push(Digest::from_bytes(*commit)); + + let callback = &fill.request.requirements.callback; + + // Note that this allows for callbacks to be specified even in the case of + // ClaimDigestMatch predicates where the journal cannot be provided. + // This is ok because it will fail on chain checks if a journal is indeed provided. if fill.request.requirements.callback.addr != Address::ZERO { callbacks.push(AssessorCallback { index: index.try_into().expect("callback index overflow"), - addr: fill.request.requirements.callback.addr, - gasLimit: fill.request.requirements.callback.gasLimit, + addr: callback.addr, + gasLimit: callback.gasLimit, }); } + if fill.request.requirements.selector != UNSPECIFIED_SELECTOR { selectors.push(AssessorSelector { index: index.try_into().expect("selector index overflow"), diff --git a/crates/indexer/migrations/3_proof_requests.sql b/crates/indexer/migrations/3_proof_requests.sql index b4d5e50e7..c9b45fe76 100644 --- a/crates/indexer/migrations/3_proof_requests.sql +++ b/crates/indexer/migrations/3_proof_requests.sql @@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS proof_requests ( client_address TEXT NOT NULL, -- Ethereum address -- Requirements fields - image_id TEXT NOT NULL, -- 32-byte image ID (hex encoded) predicate_type TEXT NOT NULL, -- Type of predicate (e.g., 'DigestMatch', 'PrefixMatch') predicate_data TEXT NOT NULL, -- Predicate data (hex encoded) callback_address TEXT, -- Optional callback contract address @@ -34,8 +33,5 @@ CREATE TABLE IF NOT EXISTS proof_requests ( -- Add an index on client_address for faster lookups CREATE INDEX IF NOT EXISTS idx_proof_requests_client_address ON proof_requests(client_address); --- Add an index on image_id for faster lookups -CREATE INDEX IF NOT EXISTS idx_proof_requests_image_id ON proof_requests(image_id); - -- Add an index on bidding_end for time-based queries CREATE INDEX IF NOT EXISTS idx_proof_requests_expires_at ON proof_requests(expires_at); diff --git a/crates/indexer/migrations/4_fulfillments.sql b/crates/indexer/migrations/4_fulfillments.sql index e11b13141..c65eb7712 100644 --- a/crates/indexer/migrations/4_fulfillments.sql +++ b/crates/indexer/migrations/4_fulfillments.sql @@ -7,11 +7,13 @@ CREATE TABLE IF NOT EXISTS assessor_receipts ( ); CREATE TABLE IF NOT EXISTS fulfillments ( - request_digest TEXT NOT NULL, - request_id TEXT NOT NULL, - prover_address TEXT NOT NULL, - image_id TEXT NOT NULL, - journal TEXT, + request_digest TEXT NOT NULL, + request_id TEXT NOT NULL, + prover_address TEXT NOT NULL, + claim_digest TEXT NOT NULL, + fulfillment_data_type TEXT NOT NULL, + fulfillment_data TEXT, + seal TEXT NOT NULL, tx_hash TEXT NOT NULL REFERENCES transactions(tx_hash), block_number BIGINT NOT NULL, @@ -21,4 +23,3 @@ CREATE TABLE IF NOT EXISTS fulfillments ( CREATE INDEX IF NOT EXISTS idx_fulfillments_request_id ON fulfillments(request_id); CREATE INDEX IF NOT EXISTS idx_fulfillments_prover_address ON fulfillments(prover_address); -CREATE INDEX IF NOT EXISTS idx_fulfillments_image_id ON fulfillments(image_id); diff --git a/crates/indexer/src/db.rs b/crates/indexer/src/db.rs index f0aa735a6..087804073 100644 --- a/crates/indexer/src/db.rs +++ b/crates/indexer/src/db.rs @@ -17,7 +17,8 @@ use std::{str::FromStr, sync::Arc}; use alloy::primitives::{Address, B256, U256}; use async_trait::async_trait; use boundless_market::contracts::{ - AssessorReceipt, Fulfillment, PredicateType, ProofRequest, RequestInputType, + AssessorReceipt, Fulfillment, FulfillmentDataType, PredicateType, ProofRequest, + RequestInputType, }; use sqlx::{ any::{install_default_drivers, AnyConnectOptions, AnyPoolOptions}, @@ -296,6 +297,7 @@ impl IndexerDb for AnyDb { let predicate_type = match request.requirements.predicate.predicateType { PredicateType::DigestMatch => "DigestMatch", PredicateType::PrefixMatch => "PrefixMatch", + PredicateType::ClaimDigestMatch => "ClaimDigestMatch", _ => return Err(DbError::BadTransaction("Invalid predicate type".to_string())), }; let input_type = match request.input.inputType { @@ -309,7 +311,6 @@ impl IndexerDb for AnyDb { request_digest, request_id, client_address, - image_id, predicate_type, predicate_data, callback_address, @@ -327,13 +328,12 @@ impl IndexerDb for AnyDb { tx_hash, block_number, block_timestamp - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ON CONFLICT (request_digest) DO NOTHING", ) .bind(format!("{request_digest:x}")) .bind(format!("{:x}", request.id)) .bind(format!("{:x}", request.client_address())) - .bind(format!("{:x}", request.requirements.imageId)) .bind(predicate_type) .bind(format!("{:x}", request.requirements.predicate.data)) .bind(format!("{:x}", request.requirements.callback.addr)) @@ -389,26 +389,33 @@ impl IndexerDb for AnyDb { prover_address: Address, metadata: &TxMetadata, ) -> Result<(), DbError> { + let fulfillment_data_type: &'static str = match fill.fulfillmentDataType { + FulfillmentDataType::ImageIdAndJournal => "ImageIdAndJournal", + FulfillmentDataType::None => "None", + _ => return Err(DbError::BadTransaction("Invalid fulfillment data type".to_string())), + }; self.add_tx(metadata).await?; sqlx::query( "INSERT INTO fulfillments ( request_digest, request_id, prover_address, - image_id, - journal, + claim_digest, + fulfillment_data_type, + fulfillment_data, seal, tx_hash, block_number, block_timestamp - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (request_digest, tx_hash) DO NOTHING", ) .bind(format!("{:x}", fill.requestDigest)) .bind(format!("{:x}", fill.id)) .bind(format!("{prover_address:x}")) - .bind(format!("{:x}", fill.imageId)) - .bind(format!("{:x}", fill.journal)) + .bind(format!("{:x}", fill.claimDigest)) + .bind(fulfillment_data_type) + .bind(format!("{:x}", fill.fulfillmentData)) .bind(format!("{:x}", fill.seal)) .bind(format!("{:x}", metadata.tx_hash)) .bind(metadata.block_number as i64) @@ -736,8 +743,8 @@ mod tests { use crate::test_utils::TestDb; use alloy::primitives::{Address, Bytes, B256, U256}; use boundless_market::contracts::{ - AssessorReceipt, Fulfillment, Offer, Predicate, PredicateType, ProofRequest, RequestId, - RequestInput, Requirements, + AssessorReceipt, Fulfillment, FulfillmentDataType, Offer, Predicate, ProofRequest, + RequestId, RequestInput, Requirements, }; use risc0_zkvm::Digest; @@ -745,10 +752,7 @@ mod tests { fn generate_request(id: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, id), - Requirements::new( - Digest::default(), - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(Digest::default(), Bytes::default())), "https://image_url.dev", RequestInput::builder().write_slice(&[0x41, 0x41, 0x41, 0x41]).build_inline().unwrap(), Offer { @@ -874,8 +878,9 @@ mod tests { let fill = Fulfillment { requestDigest: B256::ZERO, id: U256::from(1), - imageId: B256::ZERO, - journal: Bytes::default(), + claimDigest: B256::ZERO, + fulfillmentData: Bytes::default(), + fulfillmentDataType: FulfillmentDataType::None, seal: Bytes::default(), }; diff --git a/crates/indexer/tests/basic.rs b/crates/indexer/tests/basic.rs index 1d0b5e333..62f00466d 100644 --- a/crates/indexer/tests/basic.rs +++ b/crates/indexer/tests/basic.rs @@ -24,8 +24,8 @@ use alloy::{ use boundless_cli::{DefaultProver, OrderFulfilled}; use boundless_indexer::test_utils::TestDb; use boundless_market::contracts::{ - boundless_market::FulfillmentTx, Offer, Predicate, PredicateType, ProofRequest, RequestId, - RequestInput, Requirements, + boundless_market::FulfillmentTx, Offer, Predicate, ProofRequest, RequestId, RequestInput, + Requirements, }; use boundless_market_test_utils::{ create_test_ctx, ASSESSOR_GUEST_ELF, ECHO_ID, ECHO_PATH, SET_BUILDER_ELF, @@ -42,10 +42,7 @@ async fn create_order( ) -> (ProofRequest, Bytes) { let req = ProofRequest::new( RequestId::new(signer_addr, order_id), - Requirements::new( - ECHO_ID, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().build_inline().unwrap(), Offer { diff --git a/crates/order-stream/src/lib.rs b/crates/order-stream/src/lib.rs index afcedd244..85e6fc960 100644 --- a/crates/order-stream/src/lib.rs +++ b/crates/order-stream/src/lib.rs @@ -592,7 +592,7 @@ mod tests { fn new_request(idx: u32, addr: &Address) -> ProofRequest { ProofRequest::new( RequestId::new(*addr, idx), - Requirements::new(Digest::from_bytes([1; 32]), Predicate::prefix_match([])), + Requirements::new(Predicate::prefix_match(Digest::from_bytes([1; 32]), [])), "http://image_uri.null", GuestEnv::builder().build_inline().unwrap(), Offer { diff --git a/crates/order-stream/src/order_db.rs b/crates/order-stream/src/order_db.rs index b0883a3e9..245343b97 100644 --- a/crates/order-stream/src/order_db.rs +++ b/crates/order-stream/src/order_db.rs @@ -269,10 +269,13 @@ impl OrderDb { #[cfg(test)] mod tests { - use alloy::{primitives::U256, signers::local::LocalSigner, sol_types::SolStruct}; + use alloy::{ + primitives::{Bytes, U256}, + signers::local::LocalSigner, + sol_types::SolStruct, + }; use boundless_market::contracts::{ - eip712_domain, Offer, Predicate, PredicateType, ProofRequest, RequestInput, - RequestInputType, Requirements, + eip712_domain, Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements, }; use futures_util::StreamExt; use risc0_zkvm::sha::Digest; @@ -285,10 +288,10 @@ mod tests { let signer = LocalSigner::random(); let req = ProofRequest { id, - requirements: Requirements::new( + requirements: Requirements::new(Predicate::prefix_match( Digest::ZERO, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Bytes::default(), + )), imageUrl: "test".to_string(), input: RequestInput { inputType: RequestInputType::Url, data: Default::default() }, offer: Offer { diff --git a/crates/shrink_bitvm2/Cargo.toml b/crates/shrink_bitvm2/Cargo.toml new file mode 100644 index 000000000..4da876b8a --- /dev/null +++ b/crates/shrink_bitvm2/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "shrink_bitvm2" +resolver = "2" +version.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +publish = false + +[package.metadata.release] +release = false + +[dependencies] +anyhow = { workspace = true } +hex = { workspace = true } +num-bigint = { version = "0.4", features = ["std"] } +num-traits = "0.2.19" +risc0-groth16 = { version = "2.0.2", features = ["prove"] } +risc0-zkvm = { workspace = true, features = ["std", "client", "prove"] } +serde = { workspace = true } +serde_json = { workspace = true } +sha2 = "0.10" +tempfile = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs", "process"] } + +tracing = { workspace = true } + +ark-bn254 = "0.5.0" +ark-ff = "0.5.0" +ark-groth16 = { version = "0.5.0" } +ark-serialize = "0.5.0" +blake3 = { version = "1.5.0" } diff --git a/crates/shrink_bitvm2/src/lib.rs b/crates/shrink_bitvm2/src/lib.rs new file mode 100644 index 000000000..66bdb28b7 --- /dev/null +++ b/crates/shrink_bitvm2/src/lib.rs @@ -0,0 +1,425 @@ +use std::path::Path; + +use hex::FromHex; +use risc0_zkvm::{ + digest, seal_to_json, sha::Digestible, Digest, Groth16ProofJson, Groth16Receipt, Groth16Seal, + Journal, MaybePruned, Receipt, ReceiptClaim, SuccinctReceipt, SystemState, +}; + +use anyhow::{ensure, Context, Result}; +use num_bigint::BigUint; +use num_traits::Num; + +use serde::Serialize; +use tokio::process::Command; +pub const ALLOWED_CONTROL_ROOT: Digest = + digest!("ce52bf56033842021af3cf6db8a50d1b7535c125a34f1a22c6fdcf002c5a1529"); + +pub const BN254_IDENTITY_CONTROL_ID: Digest = + digest!("c07a65145c3cb48b6101962ea607a4dd93c753bb26975cb47feb00d3666e4404"); + +#[derive(Clone, Debug, Serialize)] +pub struct ShrinkBitvm2ReceiptClaim { + control_root: Digest, + pre: MaybePruned, + post: MaybePruned, + control_id: Digest, + // Note: This journal has to be exactly 32 bytes + journal: Vec, +} + +impl ShrinkBitvm2ReceiptClaim { + pub fn ok( + image_id: impl Into, + journal: impl Into>, + ) -> ShrinkBitvm2ReceiptClaim { + let verifier_params = risc0_zkvm::SuccinctReceiptVerifierParameters::default(); + let control_root = verifier_params.control_root; + Self { + control_root, + pre: MaybePruned::Pruned(image_id.into()), + post: MaybePruned::Value(SystemState { pc: 0, merkle_root: Digest::ZERO }), + control_id: BN254_IDENTITY_CONTROL_ID, + journal: journal.into(), + } + } +} + +impl Digestible for ShrinkBitvm2ReceiptClaim { + fn digest(&self) -> Digest { + use sha2::{Digest as _, Sha256}; + + let mut control_root_bytes: [u8; 32] = self.control_root.as_bytes().try_into().unwrap(); + for byte in &mut control_root_bytes { + *byte = byte.reverse_bits(); + } + let mut hasher = Sha256::new(); + hasher.update(control_root_bytes); + hasher.update(self.pre.digest()); + hasher.update(self.post.digest()); + hasher.update(self.control_id.as_bytes()); + + let output_prefix = hasher.finalize(); + + // final blake3 hash + let mut hasher = blake3::Hasher::new(); + hasher.update(&output_prefix); + hasher.update(&self.journal); + + let mut digest_bytes: [u8; 32] = hasher.finalize().into(); + // trim to 31 bytes + digest_bytes[31] = 0; + // shift because of endianness + digest_bytes.rotate_right(1); + digest_bytes.into() + } +} + +pub async fn prove_and_verify( + proof_id: &str, + work_dir: &Path, + p254_receipt: SuccinctReceipt, + journal: Journal, +) -> Result { + let receipt_claim = p254_receipt.claim.clone().value()?; + + let seal_path = work_dir.join("input.json"); + let proof_path = work_dir.join("proof.json"); + let public_path = work_dir.join("public.json"); + + write_seal(&journal.bytes, p254_receipt, &receipt_claim, &seal_path).await?; + + let proof_json = generate_proof(work_dir, &proof_path).await?; + tracing::info!("{proof_id}: generated shrink groth16 proof"); + + let image_id = receipt_claim.pre.digest(); + let final_receipt = finalize(journal.bytes, receipt_claim, proof_json)?; + let output_bytes = decode_output(&public_path).await?; + tracing::info!("bvm2 decoded output byte length: {}", output_bytes.len()); + + check_output(image_id, &final_receipt, &output_bytes)?; + + verify_proof(&final_receipt, &output_bytes)?; + + Ok(final_receipt) +} + +fn check_output(image_id: Digest, final_receipt: &Receipt, output_bytes: &[u8]) -> Result<()> { + let expected_output_bytes: [u8; 32] = + ShrinkBitvm2ReceiptClaim::ok(image_id, final_receipt.journal.bytes.clone()).digest().into(); + + let expected_output_bytes: [u8; 31] = expected_output_bytes[1..=31].try_into()?; + tracing::info!("check output: computed output: {}", hex::encode(expected_output_bytes)); + + ensure!(expected_output_bytes == output_bytes, "check output: public output mismatch"); + + Ok(()) +} + +async fn decode_output(public_path: &Path) -> Result> { + let output_content_dec = tokio::fs::read_to_string(public_path).await?; + + // Convert output_content_dec from decimal to hex + let parsed_json: serde_json::Value = serde_json::from_str(&output_content_dec)?; + let output_str = parsed_json[0].as_str().context("read public output from json")?; + let output_content_hex = BigUint::from_str_radix(output_str, 10)?.to_str_radix(16); + + // If the length of the hexadecimal string is odd, add a leading zero + let output_content_hex = if output_content_hex.len() % 2 == 0 { + output_content_hex + } else { + format!("0{output_content_hex}") + }; + + Ok(hex::decode(&output_content_hex)?) +} + +fn finalize( + journal_bytes: Vec, + receipt_claim: ReceiptClaim, + proof_json: Groth16ProofJson, +) -> Result { + let seal: Groth16Seal = proof_json.try_into()?; + let verifier_parameters_digest = + Digest::from_hex("b72859b60cfe0bb13cbde70859fbc67ef9dbd5410bbe66bdb7be64a3dcf6814e") + .unwrap(); // TODO(ec2): dont hardcode this (actually not sure if this is ever even used, so could be digest zero) + let groth16_receipt = + Groth16Receipt::new(seal.to_vec(), receipt_claim.into(), verifier_parameters_digest); + let receipt = Receipt::new(risc0_zkvm::InnerReceipt::Groth16(groth16_receipt), journal_bytes); + Ok(receipt) +} + +async fn write_seal( + journal_bytes: &[u8], + p254_receipt: SuccinctReceipt, + receipt_claim: &ReceiptClaim, + seal_path: &Path, +) -> Result<()> { + tracing::info!("Writing seal"); + + let seal_bytes = p254_receipt.get_seal_bytes(); + let mut seal_json = Vec::new(); + seal_to_json(seal_bytes.as_slice(), &mut seal_json)?; + let mut seal_json: serde_json::Value = + serde_json::from_str(std::str::from_utf8(seal_json.as_slice())?)?; + + let mut journal_bits = Vec::new(); + for byte in journal_bytes { + for i in 0..8 { + journal_bits.push((byte >> (7 - i)) & 1); + } + } + + let pre_state_digest_bits: Vec<_> = receipt_claim + .pre + .digest() + .as_bytes() + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| ((byte >> i) & 1).to_string())) + .collect(); + + let post_state_digest_bits: Vec<_> = receipt_claim + .post + .digest() + .as_bytes() + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| ((byte >> i) & 1).to_string())) + .collect(); + + let mut id_bn254_fr_bits: Vec = p254_receipt + .control_id + .as_bytes() + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| ((byte >> i) & 1).to_string())) + .collect(); + id_bn254_fr_bits.remove(248); + id_bn254_fr_bits.remove(248); + + let mut succinct_control_root_bytes: [u8; 32] = + risc0_zkvm::SuccinctReceiptVerifierParameters::default() + .control_root + .as_bytes() + .try_into()?; + + succinct_control_root_bytes.reverse(); + let succinct_control_root_hex = hex::encode(succinct_control_root_bytes); + + let a1_str = succinct_control_root_hex[0..32].to_string(); + let a0_str = succinct_control_root_hex[32..64].to_string(); + let a0_dec = to_decimal(&a0_str).context("a0_str returned None")?; + let a1_dec = to_decimal(&a1_str).context("a1_str returned None")?; + + let control_root = vec![a0_dec, a1_dec]; + + seal_json["journal_digest_bits"] = journal_bits.into(); + seal_json["pre_state_digest_bits"] = pre_state_digest_bits.into(); + seal_json["post_state_digest_bits"] = post_state_digest_bits.into(); + seal_json["id_bn254_fr_bits"] = id_bn254_fr_bits.into(); + seal_json["control_root"] = control_root.into(); + + tokio::fs::write(seal_path, serde_json::to_string_pretty(&seal_json)?).await?; + + Ok(()) +} + +// #!/bin/bash +// +// set -eoux +// +// ulimit -s unlimited +// ./verify_for_guest /mnt/input.json output.wtns +// rapidsnark verify_for_guest_final.zkey output.wtns /mnt/proof.json /mnt/public.json + +pub async fn generate_proof(work_dir: &Path, proof_path: &Path) -> Result { + tracing::info!("docker run ozancw/risc0-to-bitvm2-groth16-prover"); + + let volume = format!("{}:/mnt", work_dir.display()); + let status = Command::new("docker") + .args(["run", "--rm", "-v", &volume, "ozancw/risc0-to-bitvm2-groth16-prover"]) + .status() + .await?; + + anyhow::ensure!( + status.success(), + "ozancw/risc0-to-bitvm2-groth16-prover failed: {:?}", + status.code() + ); + + let proof_content = tokio::fs::read_to_string(proof_path).await?; + let proof_json: Groth16ProofJson = serde_json::from_str(&proof_content)?; + + Ok(proof_json) +} + +fn to_decimal(s: &str) -> Option { + let int = BigUint::from_str_radix(s, 16).ok(); + int.map(|n| n.to_str_radix(10)) +} + +pub fn verify_proof(final_receipt: &Receipt, output_bytes: &[u8]) -> Result<()> { + tracing::info!("verify_proof output bytes: {}", hex::encode(output_bytes)); + use ark_ff::PrimeField; + + let ark_proof = from_seal(&final_receipt.inner.groth16()?.seal); + let public_input_scalar = ark_bn254::Fr::from_be_bytes_mod_order(output_bytes); + let ark_vk = get_ark_verifying_key(); + let ark_pvk = ark_groth16::prepare_verifying_key(&ark_vk); + let res = ark_groth16::Groth16::::verify_proof( + &ark_pvk, + &ark_proof, + &[public_input_scalar], + ) + .unwrap(); + tracing::info!("Shrink bitvm2 Proof verification result: {:?}", res); + + ensure!(res, "proof verification failed"); + + Ok(()) +} + +pub fn get_ark_verifying_key() -> ark_groth16::VerifyingKey { + use ark_bn254::{Fq, Fq2, G1Affine, G2Affine}; + use std::str::FromStr; + + let alpha_g1 = G1Affine::new( + Fq::from_str( + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + ) + .unwrap(), + Fq::from_str( + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + ) + .unwrap(), + ); + + let beta_g2 = G2Affine::new( + Fq2::new( + Fq::from_str( + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + ) + .unwrap(), + Fq::from_str( + "4252822878758300859123897981450591353533073413197771768651442665752259397132", + ) + .unwrap(), + ), + Fq2::new( + Fq::from_str( + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + ) + .unwrap(), + Fq::from_str( + "21847035105528745403288232691147584728191162732299865338377159692350059136679", + ) + .unwrap(), + ), + ); + + let gamma_g2 = G2Affine::new( + Fq2::new( + Fq::from_str( + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + ) + .unwrap(), + Fq::from_str( + "11559732032986387107991004021392285783925812861821192530917403151452391805634", + ) + .unwrap(), + ), + Fq2::new( + Fq::from_str( + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + ) + .unwrap(), + Fq::from_str( + "4082367875863433681332203403145435568316851327593401208105741076214120093531", + ) + .unwrap(), + ), + ); + + let delta_g2 = G2Affine::new( + Fq2::new( + Fq::from_str( + "19928663713463533589216209779412278386769407450988172849262535478593422929698", + ) + .unwrap(), + Fq::from_str( + "19916519943909223643323234301580053157586699704876134064841182937085943926141", + ) + .unwrap(), + ), + Fq2::new( + Fq::from_str( + "4584600978911428195337731119171761277167808711062125916470525050324985708782", + ) + .unwrap(), + Fq::from_str( + "903010326261527050999816348900764705196723158942686053018929539519969664840", + ) + .unwrap(), + ), + ); + + let gamma_abc_g1 = vec![ + G1Affine::new( + Fq::from_str( + "6698887085900109660417671413804888867145870700073340970189635830129386206569", + ) + .unwrap(), + Fq::from_str( + "10431087902009508261375793061696708147989126018612269070732549055898651692604", + ) + .unwrap(), + ), + G1Affine::new( + Fq::from_str( + "20225609417084538563062516991929114218412992453664808591983416996515711931386", + ) + .unwrap(), + Fq::from_str( + "3236310410959095762960658876334609343091075204896196791007975095263664214628", + ) + .unwrap(), + ), + ]; + + ark_groth16::VerifyingKey { alpha_g1, beta_g2, gamma_g2, delta_g2, gamma_abc_g1 } +} +pub fn get_r0_verifying_key() -> risc0_groth16::VerifyingKey { + let json_content = std::fs::read_to_string("/home/etu/risc0-to-bitvm2/vkey_guest.json") + .expect("Failed to read verification key JSON file"); + let vk_json: risc0_groth16::VerifyingKeyJson = + serde_json::from_str(&json_content).expect("Failed to parse verification key JSON"); + + vk_json.verifying_key().unwrap() +} +pub fn from_seal(seal_bytes: &[u8]) -> ark_groth16::Proof { + use ark_bn254::{Fq, Fq2, G1Affine, G2Affine}; + use ark_ff::{Field, PrimeField}; + + let a = G1Affine::new( + Fq::from_be_bytes_mod_order(&seal_bytes[0..32]), + Fq::from_be_bytes_mod_order(&seal_bytes[32..64]), + ); + + let b = G2Affine::new( + Fq2::from_base_prime_field_elems([ + Fq::from_be_bytes_mod_order(&seal_bytes[96..128]), + Fq::from_be_bytes_mod_order(&seal_bytes[64..96]), + ]) + .unwrap(), + Fq2::from_base_prime_field_elems([ + Fq::from_be_bytes_mod_order(&seal_bytes[160..192]), + Fq::from_be_bytes_mod_order(&seal_bytes[128..160]), + ]) + .unwrap(), + ); + + let c = G1Affine::new( + Fq::from_be_bytes_mod_order(&seal_bytes[192..224]), + Fq::from_be_bytes_mod_order(&seal_bytes[224..256]), + ); + + ark_groth16::Proof { a, b, c } +} diff --git a/crates/slasher/tests/basic.rs b/crates/slasher/tests/basic.rs index 6f6af6b23..9d9c2812e 100644 --- a/crates/slasher/tests/basic.rs +++ b/crates/slasher/tests/basic.rs @@ -24,7 +24,7 @@ use alloy::{ use boundless_cli::OrderFulfilled; use boundless_market::contracts::{ boundless_market::{FulfillmentTx, UnlockedRequest}, - Offer, Predicate, PredicateType, ProofRequest, RequestId, RequestInput, Requirements, + Offer, Predicate, ProofRequest, RequestId, RequestInput, Requirements, }; use boundless_market_test_utils::create_test_ctx; use boundless_market_test_utils::{ASSESSOR_GUEST_ELF, ECHO_ID, ECHO_PATH, SET_BUILDER_ELF}; @@ -40,10 +40,7 @@ async fn create_order( ) -> (ProofRequest, Bytes) { let req = ProofRequest::new( RequestId::new(signer_addr, order_id), - Requirements::new( - ECHO_ID, - Predicate { predicateType: PredicateType::PrefixMatch, data: Default::default() }, - ), + Requirements::new(Predicate::prefix_match(ECHO_ID, Bytes::default())), format!("file://{ECHO_PATH}"), RequestInput::builder().build_inline().unwrap(), Offer { diff --git a/documentation/site/pages/developers/tutorials/proof-composition.mdx b/documentation/site/pages/developers/tutorials/proof-composition.mdx index db4f7b052..38147794a 100644 --- a/documentation/site/pages/developers/tutorials/proof-composition.mdx +++ b/documentation/site/pages/developers/tutorials/proof-composition.mdx @@ -46,7 +46,7 @@ First, we request a raw Groth16 proof from the Echo guest program: # let image_id = [0u8; 32]; # let journal = Vec::::new(); # let groth16 = true; -let mut requirements = Requirements::new(image_id, Predicate::digest_match(journal.digest())); +let mut requirements = Requirements::new(Predicate::digest_match(image_id, journal.digest())); if groth16 { requirements = requirements.with_groth16_proof(); } diff --git a/examples/composition/Cargo.lock b/examples/composition/Cargo.lock index dba0d5a95..95f7958d5 100644 --- a/examples/composition/Cargo.lock +++ b/examples/composition/Cargo.lock @@ -599,7 +599,7 @@ dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", - "heck 0.5.0", + "heck", "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", @@ -618,7 +618,7 @@ dependencies = [ "alloy-json-abi", "const-hex", "dunce", - "heck 0.5.0", + "heck", "macro-string", "proc-macro2", "quote", @@ -840,6 +840,7 @@ dependencies = [ "digest 0.10.7", "fnv", "merlin", + "rayon", "sha2", ] @@ -872,6 +873,7 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "rayon", "zeroize", ] @@ -930,6 +932,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "zeroize", ] @@ -1014,6 +1017,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", + "rayon", ] [[package]] @@ -1029,6 +1033,7 @@ dependencies = [ "educe", "fnv", "hashbrown 0.15.4", + "rayon", ] [[package]] @@ -1092,6 +1097,7 @@ dependencies = [ "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", + "rayon", ] [[package]] @@ -1145,6 +1151,7 @@ checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -1153,6 +1160,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -2042,6 +2055,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -2084,15 +2110,14 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bce8d6acc5286a16e94c29e9c885d1869358885e08a6feeb6bc54e36fe20055" +version = "1.4.1" +source = "git+https://github.com/risc0/risc0?branch=flaub%2Fshrink-bitvm2#6ed3f0321638ae7eb9c097b8aa8becba3f4b1350" dependencies = [ "duplicate", "maybe-async", "reqwest", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", ] @@ -2187,6 +2212,7 @@ dependencies = [ "guest-assessor", "guest-set-builder", "guest-util", + "hex", "risc0-aggregation", "risc0-circuit-recursion", "risc0-ethereum-contracts", @@ -2217,6 +2243,8 @@ dependencies = [ "http-cache-reqwest", "moka", "notify", + "num-bigint 0.4.6", + "num-traits", "rand 0.9.1", "reqwest", "reqwest-middleware", @@ -2227,6 +2255,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shrink_bitvm2", "sqlx", "tempfile", "thiserror 2.0.12", @@ -2456,7 +2485,7 @@ version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.104", @@ -2540,6 +2569,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -3061,12 +3096,13 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "duplicate" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" dependencies = [ - "heck 0.4.1", - "proc-macro-error", + "heck", + "proc-macro2", + "proc-macro2-diagnostics", ] [[package]] @@ -3930,12 +3966,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -5808,30 +5838,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -5863,6 +5869,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.7.0" @@ -7399,6 +7418,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shrink_bitvm2" +version = "0.14.0" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "blake3", + "hex", + "num-bigint 0.4.6", + "num-traits", + "risc0-groth16", + "risc0-zkvm", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -7595,7 +7637,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -7807,7 +7849,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -7820,7 +7862,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -9269,6 +9311,12 @@ dependencies = [ "hashlink 0.9.1", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/examples/composition/apps/src/main.rs b/examples/composition/apps/src/main.rs index b230011a4..79103c505 100644 --- a/examples/composition/apps/src/main.rs +++ b/examples/composition/apps/src/main.rs @@ -21,11 +21,12 @@ use crate::ICounter::ICounterInstance; use alloy::{ primitives::{Address, B256}, signers::local::PrivateKeySigner, - sol_types::SolCall, + sol_types::{SolCall, SolValue}, }; use anyhow::{bail, Context, Result}; use boundless_market::{ - input::GuestEnv, request_builder::OfferParams, Client, Deployment, StorageProviderConfig, + contracts::boundless_market_contract::FulfillmentData, input::GuestEnv, + request_builder::OfferParams, Client, Deployment, StorageProviderConfig, }; use clap::Parser; use guest_util::{ECHO_ELF, ECHO_ID, IDENTITY_ELF, IDENTITY_ID}; @@ -93,11 +94,12 @@ async fn run(args: Args) -> Result<()> { .await .context("failed to build boundless client")?; + let input = format!("{:?}", SystemTime::now()); // Request an un-aggregated proof from the Boundless market using the ECHO guest. let echo_request = client .new_request() .with_program(ECHO_ELF) - .with_stdin(format!("{:?}", SystemTime::now()).as_bytes()) + .with_stdin(input.as_bytes()) .with_groth16_proof(); // Submit the request to the Boundless market @@ -106,18 +108,21 @@ async fn run(args: Args) -> Result<()> { // Wait for the request to be fulfilled (check periodically) tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (echo_journal, echo_seal) = client + let (callback_data, echo_seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // periodic check every 5 seconds expires_at, ) .await?; + let FulfillmentData { imageId: cb_image_id, journal: echo_journal } = + FulfillmentData::abi_decode(&callback_data)?; tracing::info!("Request {:x} fulfilled", request_id); + assert_eq!(Digest::from(<[u8; 32]>::from(cb_image_id)), Digest::from(ECHO_ID)); // Decode the resulting RISC0-ZKVM receipt. let Ok(ContractReceipt::Base(echo_receipt)) = - risc0_ethereum_contracts::receipt::decode_seal(echo_seal, ECHO_ID, echo_journal.clone()) + risc0_ethereum_contracts::receipt::decode_seal(echo_seal, ECHO_ID, echo_journal) else { bail!("did not receive requested unaggregated receipt") }; @@ -138,7 +143,7 @@ async fn run(args: Args) -> Result<()> { // Wait for the request to be fulfilled (check periodically) tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (identity_journal, identity_seal) = client + let (identity_callback_data, identity_seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // periodic check every 5 seconds @@ -146,6 +151,8 @@ async fn run(args: Args) -> Result<()> { ) .await?; tracing::info!("Request {:x} fulfilled", request_id); + let FulfillmentData { journal: identity_journal, .. } = + FulfillmentData::abi_decode(&identity_callback_data)?; debug_assert_eq!(&identity_journal, echo_claim_digest.as_bytes()); // Interact with the Counter contract by calling the increment function. diff --git a/examples/counter-with-callback/Cargo.lock b/examples/counter-with-callback/Cargo.lock index 890e81396..f7e96e7fa 100644 --- a/examples/counter-with-callback/Cargo.lock +++ b/examples/counter-with-callback/Cargo.lock @@ -72,9 +72,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dff4dd98e17de00203f851800bbc8b76eb29a4d4e3e44074614338b7a3308d" +checksum = "8ad4eb51e7845257b70c51b38ef8d842d5e5e93196701fcbd757577971a043c6" dependencies = [ "alloy-consensus", "alloy-contract", @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda689f7287f15bd3582daba6be8d1545bad3740fd1fb778f629a1fe866bb43b" +checksum = "ca3b746060277f3d7f9c36903bb39b593a741cb7afcb0044164c28f0e9b673f0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5659581e41e8fe350ecc3593cb5c9dcffddfd550896390f2b78a07af67b0fa" +checksum = "bf98679329fa708fa809ea596db6d974da892b068ad45e48ac1956f582edf946" dependencies = [ "alloy-consensus", "alloy-eips", @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944085cf3ac8f32d96299aa26c03db7c8ca6cdaafdbc467910b889f0328e6b70" +checksum = "a10e47f5305ea08c37b1772086c1573e9a0a257227143996841172d37d3831bb" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f35887da30b5fc50267109a3c61cd63e6ca1f45967983641053a40ee83468c1" +checksum = "f562a81278a3ed83290e68361f2d1c75d018ae3b8589a314faf9303883e18ec9" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -253,9 +253,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d4009efea6f403b3a80531f9c6f70fc242399498ff71196a1688cc1c901f44" +checksum = "dc41384e9ab8c9b2fb387c52774d9d432656a28edcda1c2d4083e96051524518" dependencies = [ "alloy-eips", "alloy-primitives", @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883dee3b4020fcb5667ee627b4f401e899dad82bf37b246620339dd980720ed9" +checksum = "12c454fcfcd5d26ed3b8cae5933cbee9da5f0b05df19b46d4bd4446d1f082565" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -307,9 +307,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6e5b8ac1654a05c224390008e43634a2bdc74e181e02cf8ed591d8b3d4ad08" +checksum = "42d6d39eabe5c7b3d8f23ac47b0b683b99faa4359797114636c66e0743103d05" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -333,9 +333,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7980333dd9391719756ac28bc2afa9baa705fc70ffd11dc86ab078dd64477" +checksum = "3704fa8b7ba9ba3f378d99b3d628c8bc8c2fc431b709947930f154e22a8368b6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984c20af8aee7d123bb4bf40cf758b362b38cb9ff7160d986b6face604a1e6a9" +checksum = "6441582b4fc44d4e3ce583848cf1a4e1c202eb7eb33193fe459869e965ae1e24" dependencies = [ "alloy-genesis", "alloy-hardforks", @@ -394,9 +394,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478a42fe167057b7b919cd8b0c2844f0247f667473340dad100eaf969de5754e" +checksum = "08800e8cbe70c19e2eb7cf3d7ff4b28bdd9b3933f8e1c8136c7d910617ba03bf" dependencies = [ "alloy-chains", "alloy-consensus", @@ -420,6 +420,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", + "http 1.3.1", "lru 0.13.0", "parking_lot 0.12.4", "pin-project", @@ -457,9 +458,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0c6d723fbdf4a87454e2e3a275e161be27edcfbf46e2e3255dd66c138634b6" +checksum = "162301b5a57d4d8f000bf30f4dcb82f9f468f3e5e846eeb8598dd39e7886932c" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -480,9 +481,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41492dac39365b86a954de86c47ec23dcc7452cdb2fde591caadc194b3e34c6" +checksum = "6cd8ca94ae7e2b32cc3895d9981f3772aab0b4756aa60e9ed0bcfee50f0e1328" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -492,9 +493,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10493fa300a2757d8134f584800fef545c15905c95122bed1f6dde0b0d9dae27" +checksum = "9f3ff6a778ebda3deaed9af17930d678611afe1effa895c4260b61009c314f82" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -504,9 +505,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7eb22670a972ad6c222a6c6dac3eef905579acffe9d63ab42be24c7d158535" +checksum = "076b47e834b367d8618c52dd0a0d6a711ddf66154636df394805300af4923b8a" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -515,9 +516,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b777b98526bbe5b7892ca22a7fd5f18ed624ff664a79f40d0f9f2bf94ba79a84" +checksum = "2c2f847e635ec0be819d06e2ada4bcc4e4204026a83c4bfd78ae8d550e027ae7" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -536,9 +537,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8d2c52adebf3e6494976c8542fbdf12f10123b26e11ad56f77274c16a2a039" +checksum = "ae699248d02ade9db493bbdae61822277dc14ae0f82a5a4153203b60e34422a6" dependencies = [ "alloy-primitives", "serde", @@ -547,9 +548,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0494d1e0f802716480aabbe25549c7f6bc2a25ff33b08fd332bbb4b7d06894" +checksum = "3cf7d793c813515e2b627b19a15693960b3ed06670f9f66759396d06ebe5747b" dependencies = [ "alloy-primitives", "async-trait", @@ -562,9 +563,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c2435eb8979a020763ced3fb478932071c56e5f75ea86db41f320915d325ba" +checksum = "51a424bc5a11df0d898ce0fd15906b88ebe2a6e4f17a514b51bc93946bb756bd" dependencies = [ "alloy-consensus", "alloy-network", @@ -599,7 +600,7 @@ dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", - "heck 0.5.0", + "heck", "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", @@ -618,7 +619,7 @@ dependencies = [ "alloy-json-abi", "const-hex", "dunce", - "heck 0.5.0", + "heck", "macro-string", "proc-macro2", "quote", @@ -651,13 +652,12 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0107675e10c7f248bf7273c1e7fdb02409a717269cc744012e6f3c39959bfb" +checksum = "4f317d20f047b3de4d9728c556e2e9a92c9a507702d2016424cd8be13a74ca5e" dependencies = [ "alloy-json-rpc", "alloy-primitives", - "auto_impl", "base64 0.22.1", "derive_more 2.0.1", "futures", @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e3736701b5433afd06eecff08f0688a71a10e0e1352e0bbf0bed72f0dd4e35" +checksum = "ff084ac7b1f318c87b579d221f11b748341d68b9ddaa4ffca5e62ed2b8cfefb4" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.24" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6acb36318dfa50817154064fea7932adf2eec3f51c86680e2b37d7e8906c66bb" +checksum = "1154c8187a5ff985c95a8b2daa2fedcf778b17d7668e5e50e556c4ff9c881154" dependencies = [ "alloy-primitives", "darling", @@ -840,6 +840,7 @@ dependencies = [ "digest 0.10.7", "fnv", "merlin", + "rayon", "sha2", ] @@ -872,6 +873,7 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "rayon", "zeroize", ] @@ -930,6 +932,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "zeroize", ] @@ -1014,6 +1017,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", + "rayon", ] [[package]] @@ -1029,6 +1033,7 @@ dependencies = [ "educe", "fnv", "hashbrown 0.15.4", + "rayon", ] [[package]] @@ -1092,6 +1097,7 @@ dependencies = [ "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", + "rayon", ] [[package]] @@ -1145,6 +1151,7 @@ checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -1153,6 +1160,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -2042,6 +2055,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -2084,15 +2110,14 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bce8d6acc5286a16e94c29e9c885d1869358885e08a6feeb6bc54e36fe20055" +version = "1.4.1" +source = "git+https://github.com/risc0/risc0?branch=flaub%2Fshrink-bitvm2#6ed3f0321638ae7eb9c097b8aa8becba3f4b1350" dependencies = [ "duplicate", "maybe-async", "reqwest", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", ] @@ -2187,6 +2212,7 @@ dependencies = [ "guest-assessor", "guest-set-builder", "guest-util", + "hex", "risc0-aggregation", "risc0-circuit-recursion", "risc0-ethereum-contracts", @@ -2217,6 +2243,8 @@ dependencies = [ "http-cache-reqwest", "moka", "notify", + "num-bigint 0.4.6", + "num-traits", "rand 0.9.1", "reqwest", "reqwest-middleware", @@ -2227,6 +2255,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shrink_bitvm2", "sqlx", "tempfile", "thiserror 2.0.12", @@ -2456,7 +2485,7 @@ version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.104", @@ -2540,6 +2569,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -3061,12 +3096,13 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "duplicate" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" dependencies = [ - "heck 0.4.1", - "proc-macro-error", + "heck", + "proc-macro2", + "proc-macro2-diagnostics", ] [[package]] @@ -3336,6 +3372,7 @@ dependencies = [ "clap", "guest-util", "risc0-zkvm", + "shrink_bitvm2", "test-log", "tokio", "tracing", @@ -3928,12 +3965,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -5806,30 +5837,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -5861,6 +5868,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.7.0" @@ -6587,8 +6607,6 @@ dependencies = [ [[package]] name = "risc0-ethereum-contracts" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3a6978f67c13fcf3e9123d28882368a3a3d686f881a35bb0caaebc585f93d6" dependencies = [ "alloy", "alloy-primitives", @@ -7397,6 +7415,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shrink_bitvm2" +version = "0.14.0" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "blake3", + "hex", + "num-bigint 0.4.6", + "num-traits", + "risc0-groth16", + "risc0-zkvm", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -7593,7 +7634,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -7805,7 +7846,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -7818,7 +7859,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -9267,6 +9308,12 @@ dependencies = [ "hashlink 0.9.1", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/examples/counter-with-callback/Cargo.toml b/examples/counter-with-callback/Cargo.toml index 8fd4070ca..7c0b21e79 100644 --- a/examples/counter-with-callback/Cargo.toml +++ b/examples/counter-with-callback/Cargo.toml @@ -29,3 +29,6 @@ url = "2.5" # Always optimize; otherwise, local proving takes much longer to run [profile.dev] opt-level = 3 + +[patch.crates-io] +risc0-ethereum-contracts = { path = "/home/etu/risc0-ethereum/contracts" } diff --git a/examples/counter-with-callback/apps/Cargo.toml b/examples/counter-with-callback/apps/Cargo.toml index 94a68ad7c..d947132df 100644 --- a/examples/counter-with-callback/apps/Cargo.toml +++ b/examples/counter-with-callback/apps/Cargo.toml @@ -11,6 +11,7 @@ boundless-market = { workspace = true } clap = { workspace = true } guest-util = { workspace = true } risc0-zkvm = { workspace = true } +shrink_bitvm2 = { path = "/home/etu/boundless/crates/shrink_bitvm2" } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/examples/counter-with-callback/apps/src/main.rs b/examples/counter-with-callback/apps/src/main.rs index 6e1b7c51c..c3d1ee8ea 100644 --- a/examples/counter-with-callback/apps/src/main.rs +++ b/examples/counter-with-callback/apps/src/main.rs @@ -21,10 +21,13 @@ use crate::counter::{ICounter, ICounter::ICounterInstance}; use alloy::{primitives::Address, signers::local::PrivateKeySigner, sol_types::SolCall}; use anyhow::{Context, Result}; use boundless_market::{ - request_builder::RequirementParams, Client, Deployment, StorageProviderConfig, + contracts::Predicate, + request_builder::{OfferParams, RequirementParams}, + Client, Deployment, StorageProviderConfig, }; use clap::Parser; -use guest_util::ECHO_ELF; +use guest_util::{ECHO_ELF, ECHO_ID}; +use risc0_zkvm::{sha::Digestible, Digest, ReceiptClaim}; use tracing_subscriber::{filter::LevelFilter, prelude::*, EnvFilter}; use url::Url; @@ -88,21 +91,43 @@ async fn run(args: Args) -> Result<()> { .await .context("failed to build boundless client")?; - // We use a timestamp as input to the ECHO guest code as the Counter contract + let counter = ICounterInstance::new(args.counter_address, client.provider().clone()); + + // Use the current count as input to the ECHO guest code as the Counter contract // accepts only unique proofs. Using the same input twice would result in the same proof. - let echo_message = format!("{:?}", SystemTime::now()); + let echo_message = counter + .count() + .call() + .await + .with_context(|| format!("failed to call {}", ICounter::countCall::SIGNATURE))?; + + // Shrink Bitvm2 proofs require the input to be 32 bytes + let echo_message = echo_message.as_le_bytes(); + + // let r0_claim_digest = ReceiptClaim::ok(ECHO_ID, echo_message.to_vec()).digest(); + let blake3_claim_digest = + shrink_bitvm2::blake3_claim_digest(&Digest::from(ECHO_ID), echo_message.as_ref()); // Create a request with a callback to the counter contract let request = client .new_request() .with_program(ECHO_ELF) - .with_stdin(echo_message.as_bytes()) + .with_stdin(echo_message) // Add the callback to the counter contract by configuring the requirements .with_requirements( RequirementParams::builder() .callback_address(args.counter_address) - .callback_gas_limit(100_000), - ); + .callback_gas_limit(100_000) + .predicate(Predicate::claim_digest_match(blake3_claim_digest)), + ) + .with_offer( + OfferParams::builder() + .min_price(alloy::primitives::utils::parse_ether("0.001")?) + .max_price(alloy::primitives::utils::parse_ether("0.002")?) + .timeout(1000) + .lock_timeout(1000), + ) + .with_shrink_bitvm2_proof(); // Submit the request to the blockchain let (request_id, expires_at) = client.submit_onchain(request).await?; @@ -112,7 +137,7 @@ async fn run(args: Args) -> Result<()> { let (_journal, _seal) = client .wait_for_request_fulfillment( request_id, - Duration::from_secs(5), // check every 5 seconds + Duration::from_secs(10), // check every 5 seconds expires_at, ) .await?; @@ -120,7 +145,6 @@ async fn run(args: Args) -> Result<()> { // We interact with the Counter contract by calling the getCount function to check that the callback // was executed correctly. - let counter = ICounterInstance::new(args.counter_address, client.provider().clone()); let count = counter .count() .call() diff --git a/examples/counter/Cargo.lock b/examples/counter/Cargo.lock index 0de317f18..5114be64a 100644 --- a/examples/counter/Cargo.lock +++ b/examples/counter/Cargo.lock @@ -599,7 +599,7 @@ dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", - "heck 0.5.0", + "heck", "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", @@ -618,7 +618,7 @@ dependencies = [ "alloy-json-abi", "const-hex", "dunce", - "heck 0.5.0", + "heck", "macro-string", "proc-macro2", "quote", @@ -840,6 +840,7 @@ dependencies = [ "digest 0.10.7", "fnv", "merlin", + "rayon", "sha2", ] @@ -872,6 +873,7 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "rayon", "zeroize", ] @@ -930,6 +932,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "zeroize", ] @@ -1014,6 +1017,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", + "rayon", ] [[package]] @@ -1029,6 +1033,7 @@ dependencies = [ "educe", "fnv", "hashbrown 0.15.4", + "rayon", ] [[package]] @@ -1092,6 +1097,7 @@ dependencies = [ "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", + "rayon", ] [[package]] @@ -1145,6 +1151,7 @@ checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -1153,6 +1160,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -2042,6 +2055,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -2084,15 +2110,14 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bce8d6acc5286a16e94c29e9c885d1869358885e08a6feeb6bc54e36fe20055" +version = "1.4.1" +source = "git+https://github.com/risc0/risc0?branch=flaub%2Fshrink-bitvm2#6ed3f0321638ae7eb9c097b8aa8becba3f4b1350" dependencies = [ "duplicate", "maybe-async", "reqwest", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", ] @@ -2187,6 +2212,7 @@ dependencies = [ "guest-assessor", "guest-set-builder", "guest-util", + "hex", "risc0-aggregation", "risc0-circuit-recursion", "risc0-ethereum-contracts", @@ -2217,6 +2243,8 @@ dependencies = [ "http-cache-reqwest", "moka", "notify", + "num-bigint 0.4.6", + "num-traits", "rand 0.9.1", "reqwest", "reqwest-middleware", @@ -2227,6 +2255,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shrink_bitvm2", "sqlx", "tempfile", "thiserror 2.0.12", @@ -2456,7 +2485,7 @@ version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.104", @@ -2540,6 +2569,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -3061,12 +3096,13 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "duplicate" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" dependencies = [ - "heck 0.4.1", - "proc-macro-error", + "heck", + "proc-macro2", + "proc-macro2-diagnostics", ] [[package]] @@ -3336,6 +3372,7 @@ dependencies = [ "clap", "guest-util", "risc0-zkvm", + "shrink_bitvm2", "test-log", "tokio", "tracing", @@ -3928,12 +3965,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -5806,30 +5837,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -5861,6 +5868,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.7.0" @@ -6587,8 +6607,6 @@ dependencies = [ [[package]] name = "risc0-ethereum-contracts" version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3a6978f67c13fcf3e9123d28882368a3a3d686f881a35bb0caaebc585f93d6" dependencies = [ "alloy", "alloy-primitives", @@ -7397,6 +7415,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shrink_bitvm2" +version = "0.14.0" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "blake3", + "hex", + "num-bigint 0.4.6", + "num-traits", + "risc0-groth16", + "risc0-zkvm", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -7593,7 +7634,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -7805,7 +7846,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -7818,7 +7859,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -9267,6 +9308,12 @@ dependencies = [ "hashlink 0.9.1", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 8fd4070ca..e3d507b78 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -13,7 +13,6 @@ guest-util = { path = "../../crates/guest/util" } boundless-market = { path = "../../crates/boundless-market" } boundless-market-test-utils = { path = "../../crates/boundless-market/test-utils" } broker = { path = "../../crates/broker" } - # risc0 monorepo dependencies. risc0-zkvm = { version = "2.3", default-features = false } @@ -29,3 +28,6 @@ url = "2.5" # Always optimize; otherwise, local proving takes much longer to run [profile.dev] opt-level = 3 + +[patch.crates-io] +risc0-ethereum-contracts = { path = "/home/etu/risc0-ethereum/contracts" } \ No newline at end of file diff --git a/examples/counter/apps/Cargo.toml b/examples/counter/apps/Cargo.toml index 68bd55e55..6a74ecf44 100644 --- a/examples/counter/apps/Cargo.toml +++ b/examples/counter/apps/Cargo.toml @@ -11,6 +11,7 @@ boundless-market = { workspace = true } clap = { workspace = true } guest-util = { workspace = true } risc0-zkvm = { workspace = true } +shrink_bitvm2 = { path = "/home/etu/boundless/crates/shrink_bitvm2" } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/examples/counter/apps/src/main.rs b/examples/counter/apps/src/main.rs index 1564ff15a..95828729b 100644 --- a/examples/counter/apps/src/main.rs +++ b/examples/counter/apps/src/main.rs @@ -18,16 +18,16 @@ use std::{ }; use crate::counter::{ICounter, ICounter::ICounterInstance}; -use alloy::{ - primitives::{Address, B256}, - signers::local::PrivateKeySigner, - sol_types::SolCall, -}; +use alloy::{primitives::Address, signers::local::PrivateKeySigner, sol_types::SolCall}; use anyhow::{Context, Result}; -use boundless_market::{Client, Deployment, StorageProviderConfig}; +use boundless_market::{ + contracts::{Predicate, PredicateType}, + request_builder::{OfferParams, RequirementParams}, + Client, Deployment, StorageProviderConfig, +}; use clap::Parser; use guest_util::{ECHO_ELF, ECHO_ID}; -use risc0_zkvm::sha::{Digest, Digestible}; +use risc0_zkvm::{sha::Digestible, Digest, ReceiptClaim}; use tracing_subscriber::{filter::LevelFilter, prelude::*, EnvFilter}; use url::Url; @@ -94,38 +94,83 @@ async fn run(args: Args) -> Result<()> { .await .context("failed to build boundless client")?; - // Use the default ECHO program with timestamp input - // We use a timestamp as input to the ECHO guest code as the Counter contract + let counter = ICounterInstance::new(args.counter_address, client.provider().clone()); + + // Use the current count as input to the ECHO guest code as the Counter contract // accepts only unique proofs. Using the same input twice would result in the same proof. - let echo_message = format!("{:?}", SystemTime::now()); + + let start_count = counter + .getCount(client.caller()) + .call() + .await + .with_context(|| format!("failed to call {}", ICounter::getCountCall::SIGNATURE))?; + + // Shrink Bitvm2 proofs require the input to be 32 bytes + let echo_message = start_count.as_le_bytes(); + + // let r0_claim_digest = ReceiptClaim::ok(ECHO_ID, echo_message.as_bytes().to_vec()).digest(); + let blake3_claim_digest = + shrink_bitvm2::blake3_claim_digest(&Digest::from(ECHO_ID), echo_message.as_ref()); // Build the request based on whether program URL is provided let request = if let Some(program_url) = args.program_url { // Use the provided URL - client.new_request().with_program_url(program_url)?.with_stdin(echo_message.as_bytes()) + client + .new_request() + .with_program_url(program_url)? + .with_stdin(echo_message) + .with_requirements( + RequirementParams::builder() + .predicate(Predicate::claim_digest_match(blake3_claim_digest)) + .build() + .unwrap(), + ) + .with_offer( + OfferParams::builder() + .min_price(alloy::primitives::utils::parse_ether("0.001")?) + .max_price(alloy::primitives::utils::parse_ether("0.002")?) + .timeout(1000) + .lock_timeout(1000), + ) + .with_shrink_bitvm2_proof() } else { - client.new_request().with_program(ECHO_ELF).with_stdin(echo_message.as_bytes()) + client + .new_request() + .with_program(ECHO_ELF) + .with_stdin(echo_message) + .with_requirements( + RequirementParams::builder() + .predicate(Predicate::claim_digest_match(blake3_claim_digest)) + .build() + .unwrap(), + ) + .with_offer( + OfferParams::builder() + .min_price(alloy::primitives::utils::parse_ether("0.001")?) + .max_price(alloy::primitives::utils::parse_ether("0.002")?) + .timeout(1000) + .lock_timeout(1000), + ) + .with_shrink_bitvm2_proof() }; let (request_id, expires_at) = client.submit_onchain(request).await?; // Wait for the request to be fulfilled. The market will return the journal and seal. tracing::info!("Waiting for request {:x} to be fulfilled", request_id); - let (journal, seal) = client + let (callback_data, seal) = client .wait_for_request_fulfillment( request_id, Duration::from_secs(5), // check every 5 seconds expires_at, ) .await?; + tracing::info!("Callback data: {:?}", callback_data); tracing::info!("Request {:x} fulfilled", request_id); // We interact with the Counter contract by calling the increment function with the journal and // seal returned by the market. - let counter = ICounterInstance::new(args.counter_address, client.provider().clone()); - let journal_digest = B256::try_from(journal.digest().as_bytes())?; - let image_id = B256::try_from(Digest::from(ECHO_ID).as_bytes())?; - let call_increment = counter.increment(seal, image_id, journal_digest).from(client.caller()); + let call_increment = counter.increment(seal, blake3_claim_digest.into()).from(client.caller()); // By calling the increment function, we verify the seal against the published roots // of the SetVerifier contract. diff --git a/examples/counter/contracts/src/Counter.sol b/examples/counter/contracts/src/Counter.sol index 77d633aa2..e26d7be91 100644 --- a/examples/counter/contracts/src/Counter.sol +++ b/examples/counter/contracts/src/Counter.sol @@ -15,7 +15,7 @@ pragma solidity ^0.8.13; import {ICounter} from "./ICounter.sol"; -import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {IRiscZeroVerifier, Receipt} from "risc0/IRiscZeroVerifier.sol"; error AlreadyVerified(bytes32 received); @@ -35,13 +35,13 @@ contract Counter is ICounter { VERIFIER = verifier; } - function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) public { - if (verified[journalDigest]) { - revert AlreadyVerified({received: journalDigest}); + function increment(bytes calldata seal, bytes32 claimDigest) public { + if (verified[claimDigest]) { + revert AlreadyVerified({received: claimDigest}); } - VERIFIER.verify(seal, imageId, journalDigest); - verified[journalDigest] = true; + VERIFIER.verifyIntegrity(Receipt({seal: seal, claimDigest: claimDigest})); + verified[claimDigest] = true; count[msg.sender] += 1; emit Increment(msg.sender, count[msg.sender]); } diff --git a/examples/counter/contracts/src/ICounter.sol b/examples/counter/contracts/src/ICounter.sol index 084a741a4..42ea24970 100644 --- a/examples/counter/contracts/src/ICounter.sol +++ b/examples/counter/contracts/src/ICounter.sol @@ -24,6 +24,6 @@ pragma solidity ^0.8.13; interface ICounter { event Increment(address indexed who, uint256 count); - function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external; + function increment(bytes calldata seal, bytes32 claimDigest) external; function getCount(address who) external view returns (uint256); } diff --git a/examples/counter/contracts/test/Counter.t.sol b/examples/counter/contracts/test/Counter.t.sol index c1fecd128..56da24293 100644 --- a/examples/counter/contracts/test/Counter.t.sol +++ b/examples/counter/contracts/test/Counter.t.sol @@ -39,7 +39,7 @@ contract CounterTest is RiscZeroCheats, Test { function test_Increment() public { RiscZeroReceipt memory receipt = verifier.mockProve(IMAGE_ID, sha256(MOCK_JOURNAL)); - counter.increment(receipt.seal, IMAGE_ID, sha256(MOCK_JOURNAL)); + counter.increment(receipt.seal, receipt.claimDigest); assertEq(counter.count(address(this)), 1); } } diff --git a/examples/smart-contract-requestor/Cargo.lock b/examples/smart-contract-requestor/Cargo.lock index 99a08b3e2..1022d0fe6 100644 --- a/examples/smart-contract-requestor/Cargo.lock +++ b/examples/smart-contract-requestor/Cargo.lock @@ -840,6 +840,7 @@ dependencies = [ "digest 0.10.7", "fnv", "merlin", + "rayon", "sha2", ] @@ -872,6 +873,7 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "rayon", "zeroize", ] @@ -930,6 +932,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "zeroize", ] @@ -1014,6 +1017,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", + "rayon", ] [[package]] @@ -1029,6 +1033,7 @@ dependencies = [ "educe", "fnv", "hashbrown 0.15.4", + "rayon", ] [[package]] @@ -1092,6 +1097,7 @@ dependencies = [ "arrayvec", "digest 0.10.7", "num-bigint 0.4.6", + "rayon", ] [[package]] @@ -1145,6 +1151,7 @@ checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -1153,6 +1160,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -2042,6 +2055,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -2088,11 +2114,23 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bce8d6acc5286a16e94c29e9c885d1869358885e08a6feeb6bc54e36fe20055" dependencies = [ - "duplicate", + "duplicate 1.0.0", "maybe-async", "reqwest", "serde", "thiserror 1.0.69", +] + +[[package]] +name = "bonsai-sdk" +version = "1.4.1" +source = "git+https://github.com/risc0/risc0?branch=flaub%2Fshrink-bitvm2#6ed3f0321638ae7eb9c097b8aa8becba3f4b1350" +dependencies = [ + "duplicate 2.0.0", + "maybe-async", + "reqwest", + "serde", + "thiserror 2.0.12", "tokio", ] @@ -2187,6 +2225,7 @@ dependencies = [ "guest-assessor", "guest-set-builder", "guest-util", + "hex", "risc0-aggregation", "risc0-circuit-recursion", "risc0-ethereum-contracts", @@ -2205,7 +2244,7 @@ dependencies = [ "aws-config", "aws-sdk-s3", "bincode", - "bonsai-sdk", + "bonsai-sdk 1.4.1", "boundless-assessor", "boundless-market", "boundless-market-test-utils", @@ -2217,6 +2256,8 @@ dependencies = [ "http-cache-reqwest", "moka", "notify", + "num-bigint 0.4.6", + "num-traits", "rand 0.9.1", "reqwest", "reqwest-middleware", @@ -2227,6 +2268,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "shrink_bitvm2", "sqlx", "tempfile", "thiserror 2.0.12", @@ -2540,6 +2582,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -3069,6 +3117,17 @@ dependencies = [ "proc-macro-error", ] +[[package]] +name = "duplicate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "proc-macro2-diagnostics", +] + [[package]] name = "dyn-clone" version = "1.0.19" @@ -5861,6 +5920,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "version_check", + "yansi", +] + [[package]] name = "proptest" version = "1.7.0" @@ -6691,7 +6763,7 @@ dependencies = [ "addr2line 0.22.0", "anyhow", "bincode", - "bonsai-sdk", + "bonsai-sdk 1.4.0", "borsh", "bytemuck", "bytes", @@ -7398,6 +7470,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shrink_bitvm2" +version = "0.14.0" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "blake3", + "hex", + "num-bigint 0.4.6", + "num-traits", + "risc0-groth16", + "risc0-zkvm", + "serde", + "serde_json", + "sha2", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -9268,6 +9363,12 @@ dependencies = [ "hashlink 0.9.1", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" diff --git a/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol b/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol index 6b4b382b0..62d07fc47 100644 --- a/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol +++ b/examples/smart-contract-requestor/contracts/src/SmartContractRequestor.sol @@ -57,21 +57,25 @@ contract SmartContractRequestor is IERC1271 { return 0xffffffff; } - // Validate that the request provided is as expected. - // For this example, we check the image id is as expected, and that the predicate restricts - // the output to match the day specified in the id. - if (request.requirements.imageId != ECHO_ID) { - return 0xffffffff; - } - // Validate the predicate type and data are correct. This ensures that the request was executed with // the correct input and resulted in the correct output. In this case it ensures that the input // to the request was the correct day since epoch that corresponds to the request id. if (request.requirements.predicate.predicateType != PredicateType.DigestMatch) { return 0xffffffff; } + + // We know the predicate is a DigestMatch, so the data is bytes32 and bytes32 + (bytes32 imageId, bytes32 journalDigest) = abi.decode(request.requirements.predicate.data, (bytes32, bytes32)); + + // Validate that the request provided is as expected. + // For this example, we check the image id is as expected, and that the predicate restricts + // the output to match the day specified in the id. + if (imageId != ECHO_ID) { + return 0xffffffff; + } + bytes32 expectedPredicate = sha256(abi.encodePacked(daysSinceEpoch)); - if (bytes32(request.requirements.predicate.data) != expectedPredicate) { + if (journalDigest != expectedPredicate) { return 0xffffffff; } diff --git a/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol b/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol index e65e4f6d9..ecf26eaf2 100644 --- a/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol +++ b/examples/smart-contract-requestor/contracts/test/SmartContractRequestor.t.sol @@ -16,7 +16,7 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {SmartContractRequestor} from "../src/SmartContractRequestor.sol"; import {ProofRequest} from "boundless-market/types/ProofRequest.sol"; -import {PredicateType} from "boundless-market/types/Predicate.sol"; +import {PredicateLibrary} from "boundless-market/types/Predicate.sol"; import {ImageID} from "boundless-market/libraries/UtilImageID.sol"; import {RequestId, RequestIdLibrary} from "boundless-market/types/RequestId.sol"; import {IBoundlessMarket} from "boundless-market/IBoundlessMarket.sol"; @@ -81,7 +81,9 @@ contract SmartContractRequestorTest is Test { function test_IsValidSignatureInvalidImageId() public view { // Create a proof request with invalid image ID ProofRequest memory request = _createValidProofRequest(START_DAY + 1); - request.requirements.imageId = bytes32(0); + (bytes32 imageId, bytes32 journalDigest) = abi.decode(request.requirements.predicate.data, (bytes32, bytes32)); + imageId = bytes32(0); + request.requirements.predicate = PredicateLibrary.createDigestMatchPredicate(imageId, journalDigest); bytes memory signature = abi.encode(request); bytes32 requestHash = _hashTypedData(request.eip712Digest()); @@ -92,9 +94,8 @@ contract SmartContractRequestorTest is Test { function _createValidProofRequest(uint32 daysSinceEpoch) internal pure returns (ProofRequest memory) { ProofRequest memory request; - request.requirements.imageId = ImageID.ECHO_ID; - request.requirements.predicate.predicateType = PredicateType.DigestMatch; - request.requirements.predicate.data = abi.encodePacked(sha256(abi.encodePacked(daysSinceEpoch))); + request.requirements.predicate = + PredicateLibrary.createDigestMatchPredicate(ImageID.ECHO_ID, sha256(abi.encodePacked(daysSinceEpoch))); request.id = RequestIdLibrary.from(address(0), daysSinceEpoch, true); return request; } diff --git a/remappings.txt b/remappings.txt index 58db52928..33d5c4152 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,5 +3,5 @@ openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ forge-std/=lib/forge-std/src/ -risc0/=lib/risc0-ethereum/contracts/src/ +risc0/=/home/etu/risc0-ethereum/contracts/src/ solmate/=lib/solmate/src \ No newline at end of file diff --git a/request.yaml b/request.yaml index 8206b8bb8..a6e6fbac0 100644 --- a/request.yaml +++ b/request.yaml @@ -5,10 +5,10 @@ id: 0 # if set to 0, gets overwritten by a random id # Specifies the requirements for the delivered proof, including the program that must be run, # and the constraints on the journal's value, which define the statement to be proven. requirements: - imageId: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3" predicate: predicateType: PrefixMatch - data: "53797374" + # concat(imageId, prefix) = concat(53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a3, 53797374) + data: "53cb4210cf2f5bf059e3a4f7bcbb8e21ddc5c11a690fd79e87947f9fec5522a353797374" callback: addr: "0x0000000000000000000000000000000000000000" gasLimit: 0