Skip to content

BM-1466: Ec2/claim digest #1004

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
435e8c4
initial deployment
capossele May 21, 2025
4c36ef5
update commit
capossele May 21, 2025
d798851
use verifier rather than estop
capossele May 21, 2025
a1d3222
fix bytecode
capossele May 22, 2025
575d8b5
clean up
capossele May 22, 2025
1ebbc60
tmp
capossele May 22, 2025
924c53f
Merge remote-tracking branch 'origin/main' into angelo/base-deployment
capossele May 23, 2025
2e2156a
Merge remote-tracking branch 'origin/main' into angelo/base-deployment
capossele May 26, 2025
63d873a
fix cli
capossele May 26, 2025
da0cc55
fix comment
capossele May 26, 2025
661daa6
drop burn
capossele May 26, 2025
7cd90bb
add base deployment
capossele May 27, 2025
a672a0a
drop hp
capossele May 28, 2025
918934a
Merge remote-tracking branch 'origin/main' into angelo/base-deployment
capossele May 29, 2025
4a8b78e
add new staging deployments
capossele May 29, 2025
48822c6
tmp
capossele May 29, 2025
ebbccd1
Merge branch 'main' into angelo/claim-digest
capossele Jun 3, 2025
0985ba3
artifacts
capossele Jun 3, 2025
9b25d47
fmt
capossele Jun 3, 2025
e4dbfd4
use predicateType
capossele Jun 3, 2025
00f1078
clean up
capossele Jun 3, 2025
3c43edf
add no journal test
capossele Jun 3, 2025
25c7b42
Merge branch 'main' into angelo/claim-digest
ec2 Aug 4, 2025
7b905ef
Merge branch 'main' into angelo/claim-digest
ec2 Aug 11, 2025
27d0dc0
remove receipt verification from assessor-guest
ec2 Aug 11, 2025
41aa8b6
add CallbackData
ec2 Aug 11, 2025
9566b73
fix rust side
ec2 Aug 12, 2025
50167b3
forge fmt
ec2 Aug 12, 2025
fc6bd7f
fix rust tests
ec2 Aug 12, 2025
e2013f8
counter example with claim digest match predicate
ec2 Aug 12, 2025
eaa391f
remove image id from requirements and put it in the predicate data
ec2 Aug 12, 2025
0032fa2
still need to figure out how to get the image id from request in broker
ec2 Aug 12, 2025
c90f973
tests build, but not all pass
ec2 Aug 13, 2025
4ae5304
fix requirements type solidity signature
ec2 Aug 13, 2025
ea907a8
fix request builder
ec2 Aug 13, 2025
1937e1b
cargo tests pass
ec2 Aug 13, 2025
69abfb7
satisfy some compiler warnings
ec2 Aug 13, 2025
5df52a5
fix sol test
ec2 Aug 13, 2025
7864baa
Merge branch 'main' into ec2/claim-digest
ec2 Aug 13, 2025
ac3c914
fix more compiler warnings
ec2 Aug 13, 2025
3e0d070
debugging still
ec2 Aug 13, 2025
8d8a0c8
fix composition example
ec2 Aug 13, 2025
d3c9ace
fix smart-contract-requestor example
ec2 Aug 13, 2025
2ba252f
clippy
ec2 Aug 13, 2025
8833ded
fix deployment
ec2 Aug 13, 2025
e161dff
fix some naming
ec2 Aug 13, 2025
de0d3e2
composition
ec2 Aug 13, 2025
3b54725
remove some comments
ec2 Aug 13, 2025
7f2172e
fix boundless-cli test
ec2 Aug 13, 2025
04f6f99
do not push assumption receipts into the assessor in aggregator
ec2 Aug 13, 2025
0600765
fix comments
ec2 Aug 14, 2025
88b981b
Merge branch 'main' into ec2/claim-digest
ec2 Aug 14, 2025
d294e34
rename FulfillmentData to FulfillmentClaimData
ec2 Aug 14, 2025
dd44c54
Add callbacktype enum
ec2 Aug 14, 2025
d70bc72
fix test domain
ec2 Aug 14, 2025
de7225e
fix clippy
ec2 Aug 14, 2025
3778944
check claim digest if journal is available and not a claim digest match
ec2 Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand Down
16 changes: 8 additions & 8 deletions contracts/deployment-test/Deploymnet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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)
});
Expand All @@ -257,4 +257,4 @@ contract Client {
(uint8 v, bytes32 r, bytes32 s) = VM.sign(wallet, structDigest);
return abi.encodePacked(r, s, v);
}
}
}
69 changes: 35 additions & 34 deletions contracts/snapshots/BoundlessMarketBasicTest.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
{
"ERC20 approve: required for depositStake": "45966",
"bytecode size implementation": "24381",
"bytecode size implementation": "23636",
"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",
"deposit: first ever deposit": "50832",
"deposit: second deposit": "33732",
"depositStake: 1 HP (tops up market account)": "59312",
"depositStake: full (drains testProver account)": "49712",
"depositStakeWithPermit: 1 HP (tops up market account)": "72236",
"depositStakeWithPermit: full (drains testProver account)": "72227",
"fulfill (no journal): a batch of 8": "384582",
"fulfill: a batch of 8": "412815",
"fulfill: a locked request": "92176",
"fulfill: a locked request (locked via prover signature)": "92176",
"fulfill: a locked request with 10kB journal": "424790",
"fulfill: another prover fulfills without payment": "87139",
"fulfill: fulfilled by the locked prover for payment (request already fulfilled by another prover)": "81038",
"fulfillAndWithdraw: a batch of 8": "425191",
"fulfillAndWithdraw: a locked request": "104552",
"lockinRequest: base case": "146984",
"lockinRequest: with prover signature": "156626",
"priceAndFulfill: a single request": "109862",
"priceAndFulfill: a single request (smart contract signature)": "120052",
"priceAndFulfill: a single request (with selector)": "112163",
"priceAndFulfill: a single request that was not locked": "109862",
"priceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "109862",
"priceAndFulfill: fulfill already fulfilled was locked request": "102177",
"slash: base case": "100923",
"slash: fulfilled request after lock deadline": "80488",
"submitRequest: with maxPrice ether": "52621",
"submitRequest: without ether": "45778",
"submitRootAndFulfill: a batch of 2 requests": "171017",
"submitRootAndFulfill: a locked request": "126585",
"submitRootAndFulfill: a locked request (locked via prover signature)": "126585",
"submitRootAndFulfillAndWithdraw: a locked request": "138301",
"submitRootAndPriceAndFulfill: a single request": "143070",
"submitRootAndPriceAndFulfill: a single request that was not locked": "143070",
"submitRootAndPriceAndFulfill: a single request that was not locked fulfilled by prover not in allow-list": "143070",
"withdraw: 1 ether": "40314",
"withdraw: full balance": "40326",
"withdrawStake: 1 HP balance": "68743",
Expand Down
40 changes: 20 additions & 20 deletions contracts/snapshots/BoundlessMarketBench.json
Original file line number Diff line number Diff line change
@@ -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": "133867",
"fulfill (with callback): batch of 002": "221508",
"fulfill (with callback): batch of 004": "397945",
"fulfill (with callback): batch of 008": "750387",
"fulfill (with callback): batch of 016": "1292502",
"fulfill (with callback): batch of 032": "2409336",
"fulfill (with selector): batch of 001": "94414",
"fulfill (with selector): batch of 002": "142699",
"fulfill (with selector): batch of 004": "241820",
"fulfill (with selector): batch of 008": "430750",
"fulfill (with selector): batch of 016": "810306",
"fulfill (with selector): batch of 032": "1594177",
"fulfill: batch of 001": "92164",
"fulfill: batch of 002": "138207",
"fulfill: batch of 004": "232791",
"fulfill: batch of 008": "412665",
"fulfill: batch of 016": "773951",
"fulfill: batch of 032": "1520776",
"fulfill: batch of 064": "3070189",
"fulfill: batch of 128": "6308304"
}
34 changes: 28 additions & 6 deletions contracts/src/BoundlessMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ 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 {CallbackData} from "./types/CallbackData.sol";
import {Fulfillment} from "./types/Fulfillment.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";
Expand Down Expand Up @@ -239,6 +241,7 @@ contract BoundlessMarket is
}
bytes32[] memory leaves = new bytes32[](fills.length);
bool[] memory hasSelector = new bool[](fills.length);
PredicateType[] memory predicateTypes = new PredicateType[](fills.length);

// Check the selector constraints.
// NOTE: The assessor guest adds non-zero selector values to the list.
Expand All @@ -255,16 +258,16 @@ contract BoundlessMarket is
// Verify the application receipts.
for (uint256 i = 0; i < fills.length; i++) {
Fulfillment calldata fill = fills[i];
predicateTypes[i] = fill.predicateType;

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));
}
}

Expand All @@ -278,6 +281,7 @@ contract BoundlessMarket is
root: batchRoot,
callbacks: assessorReceipt.callbacks,
selectors: assessorReceipt.selectors,
predicateTypes: predicateTypes,
prover: assessorReceipt.prover
})
)
Expand Down Expand Up @@ -333,13 +337,31 @@ contract BoundlessMarket is
}

uint256 callbackIndexPlusOne = fillToCallbackIndexPlusOne[i];
if (callbackIndexPlusOne > 0) {
// We do not support callbacks for claim digest matches.
if ((fill.predicateType != PredicateType.ClaimDigestMatch) && (callbackIndexPlusOne > 0)) {
(bytes32 imageId, bytes calldata journal) = _decodeCallbackData(fill.callbackData);

AssessorCallback calldata callback = assessorReceipt.callbacks[callbackIndexPlusOne - 1];
_executeCallback(fill.id, callback.addr, callback.gasLimit, fill.imageId, fill.journal, fill.seal);
_executeCallback(fill.id, callback.addr, callback.gasLimit, imageId, journal, fill.seal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading this correctly, it seems that imageId and journal here are not authenticated.
It is true that we are using the correct values in the assessor guest to compute the claimDigest, however on the contract side an adversary could replace their values. If my line of thinking makes sense we could either commit to it as part of the assessor journal, or alternatively, we could recompute the claimDigest from those values in case the predicate type is != than PredicateType.ClaimDigestMatch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also now that I think about it, are we only guaranteeing the callbackData in case of a callback is required? Cause in that case the only way to get back a journal now is by requesting a callback and I'm not sure is what we want

}
}
}

function _decodeCallbackData(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
}
}

/// @inheritdoc IBoundlessMarket
function priceAndFulfillAndWithdraw(
ProofRequest[] calldata requests,
Expand Down
3 changes: 3 additions & 0 deletions contracts/src/types/AssessorJournal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +18,8 @@ struct AssessorJournal {
/// @notice The (optional) selectors for the requests committed by the assessor.
/// @dev This is used to verify the fulfillment of the request against its selector's seal.
Selector[] selectors;
/// @notice The list of `PredicateType` for each request.
PredicateType[] predicateTypes;
Comment on lines +21 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this.

Also, if we do this should be in AssessorCommitment instead. The reason the callbacks and selectors are outside this Merkle tree of AssessorCommitment objects is that most of them will be empty and so it takes up less space to do it this way.

/// @notice Root of the Merkle tree committing to the set of proven claims.
/// @dev In the case of a batch of size one, this may simply be the eip712Digest of the `AssessorCommitment`.
bytes32 root;
Expand Down
19 changes: 19 additions & 0 deletions contracts/src/types/CallbackData.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 CallbackDataLibrary for CallbackData global;

/// @title Callback Struct and Library
/// @notice Represents a callback configuration for proof delivery
struct CallbackData {
/// @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 CallbackDataLibrary {}
19 changes: 9 additions & 10 deletions contracts/src/types/Fulfillment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pragma solidity ^0.8.20;

import {RequestId} from "./RequestId.sol";
import {PredicateType} from "./Predicate.sol";

using FulfillmentLibrary for Fulfillment global;

Expand All @@ -15,16 +16,14 @@ 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, I think removing the journal from the Fulfillment and only providing it in the callback is the right call. It will have the effect though that, by default, journals will not have onchain data availability guarantees (in the ProofDelivered) event. Most applications we have seen so far will have computed the journal locally (as they must to use the DigestMatch predicate) so this should not need needed, however, it may add some friction to the devex if they were planning on fetching the receipt from the chain and have the journal included.

/// @notice The `PredicateType` of the request that is being fulfilled.
/// @dev When the `PredicateType` is `ClaimDigestMatch`, the imageIdOrClaimDigest field is the claim digest,
/// and otherwise it is the image ID of the guest that was executed.
PredicateType predicateType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my other comments, I think we can remove this.

/// @notice Claim Digest
bytes32 claimDigest;
/// @notice The callback data, if requested.
bytes callbackData;
Comment on lines +24 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my other comments, this should come with the AssessorReceipt

/// @notice Cryptographic proof for the validity of the execution results.
/// @dev This will be sent to the `IRiscZeroVerifier` associated with this contract.
bytes seal;
Expand Down
Loading
Loading