Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 26 additions & 10 deletions contracts/src/types/Predicate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,45 @@ library PredicateLibrary {
return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)});
}

/// @notice Evaluates the predicate against the given journal and journal digest.
/// @notice Evaluates the predicate against the image ID and journal.
/// @dev If the predicate is of type ClaimDigestMatch and image ID and journal are not available,
/// use the evaluation function with the claim digest instead.
/// @param predicate The predicate to evaluate.
/// @param imageId Image ID to use for evaluation.
/// @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, bytes32 imageId, bytes memory journal, bytes32 journalDigest)
internal
pure
returns (bool)
{
function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal) internal pure returns (bool) {
if (predicate.predicateType == PredicateType.DigestMatch) {
require(predicate.data.length == 64, "Invalid DigestMatch data length");
bytes memory dataJournal = Bytes.slice(predicate.data, 32);
return bytes32(dataJournal) == journalDigest;
return bytes32(dataJournal) == sha256(abi.encode(journal)) && bytes32(predicate.data) == imageId;
} else if (predicate.predicateType == PredicateType.PrefixMatch) {
require(predicate.data.length >= 32, "Invalid PrefixMatch data length");
bytes memory dataJournal = Bytes.slice(predicate.data, 32);
return startsWith(journal, dataJournal);
return startsWith(journal, dataJournal) && bytes32(predicate.data) == imageId;
} else if (predicate.predicateType == PredicateType.ClaimDigestMatch) {
return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest();
require(predicate.data.length == 32, "Invalid ClaimDigestMatch data length");
return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, sha256(abi.encode(journal))).digest();
} else {
revert("Unreachable code");
}
}

/// @notice Evaluates the predicate against the claim digest.
/// @dev This function should be used when the predicate is of type ClaimDigestMatch
/// and the image ID and journal are not available.
/// @param predicate The predicate to evaluate.
/// @param claimDigest Claim digest to use for evaluation.
/// @return True if the predicate is satisfied, false otherwise.
function eval(Predicate memory predicate, bytes32 claimDigest) internal pure returns (bool) {
if (predicate.predicateType == PredicateType.ClaimDigestMatch) {
require(predicate.data.length == 32, "Invalid ClaimDigestMatch data length");
return bytes32(predicate.data) == claimDigest;
} else {
revert("Predicate not of type ClaimDigestMatch");
}
}

/// @notice Checks if the journal starts with the given prefix.
/// @param journal The journal to check.
/// @param prefix The prefix to check for.
Expand Down
25 changes: 12 additions & 13 deletions contracts/test/types/Predicate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,28 @@ contract PredicateTest is Test {
using ReceiptClaimLib for ReceiptClaim;

function testEvalDigestMatch() public pure {
bytes32 hash = keccak256("test");
bytes32 hash = sha256(abi.encode("test"));
Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash);
assertEq(
uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch"
);

bytes memory journal = "test";
bytes32 journalDigest = keccak256(journal);

bool result = predicate.eval(IMAGE_ID, journal, journalDigest);
bool result = predicate.eval(IMAGE_ID, journal);
assertTrue(result, "Predicate evaluation should be true for matching digest");
}

function testEvalDigestMatchFail() public pure {
bytes32 hash = keccak256("test");
bytes32 hash = sha256(abi.encode("test"));
Predicate memory predicate = PredicateLibrary.createDigestMatchPredicate(IMAGE_ID, hash);
assertEq(
uint8(predicate.predicateType), uint8(PredicateType.DigestMatch), "Predicate type should be DigestMatch"
);

bytes memory journal = "different test";
bytes32 journalDigest = keccak256(journal);

bool result = predicate.eval(IMAGE_ID, journal, journalDigest);
bool result = predicate.eval(IMAGE_ID, journal);
assertFalse(result, "Predicate evaluation should be false for non-matching digest");
}

Expand All @@ -47,7 +45,7 @@ contract PredicateTest is Test {
Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix);
bytes memory journal = "prefix and more";

bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal));
bool result = predicate.eval(IMAGE_ID, journal);
assertTrue(result, "Predicate evaluation should be true for matching prefix");
}

Expand All @@ -56,13 +54,13 @@ contract PredicateTest is Test {
Predicate memory predicate = PredicateLibrary.createPrefixMatchPredicate(IMAGE_ID, prefix);
bytes memory journal = "different prefix";

bool result = predicate.eval(IMAGE_ID, journal, keccak256(journal));
bool result = predicate.eval(IMAGE_ID, 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 journalDigest = sha256(abi.encode(journal));
bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest();
Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest);
assertEq(
Expand All @@ -71,13 +69,13 @@ contract PredicateTest is Test {
"Predicate type should be ClaimDigestMatch"
);

bool result = predicate.eval(IMAGE_ID, journal, journalDigest);
bool result = predicate.eval(claimDigest);
assertTrue(result, "Predicate evaluation should be true for matching digest");
}

function testEvalClaimDigestMatchFail() public pure {
bytes memory journal = "test";
bytes32 journalDigest = keccak256(journal);
bytes32 journalDigest = sha256(abi.encode(journal));
bytes32 claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest();
Predicate memory predicate = PredicateLibrary.createClaimDigestMatchPredicate(claimDigest);
assertEq(
Expand All @@ -87,9 +85,10 @@ contract PredicateTest is Test {
);

journal = "different test";
journalDigest = keccak256(journal);
journalDigest = sha256(abi.encode(journal));
claimDigest = ReceiptClaimLib.ok(IMAGE_ID, journalDigest).digest();

bool result = predicate.eval(IMAGE_ID, journal, journalDigest);
bool result = predicate.eval(claimDigest);
assertFalse(result, "Predicate evaluation should be false for non-matching digest");
}
}
4 changes: 2 additions & 2 deletions crates/assessor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ mod tests {
ProofRequest::new(
RequestId::new(signer, id),
Requirements::new(Predicate::prefix_match(Digest::from_bytes(image_id.0), prefix)),
"test",
"http://test.null",
RequestInput { inputType: RequestInputType::Url, data: Default::default() },
Offer {
minPrice: U256::from(1),
Expand All @@ -221,7 +221,7 @@ mod tests {
ProofRequest::new(
RequestId::new(signer, id),
Requirements::new(Predicate::claim_digest_match(claim_digest)),
"test",
"http://test.null",
RequestInput { inputType: RequestInputType::Url, data: Default::default() },
Offer {
minPrice: U256::from(1),
Expand Down
8 changes: 6 additions & 2 deletions crates/boundless-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ fn is_dev_mode() -> bool {
mod tests {
use super::*;
use alloy::{
primitives::{FixedBytes, Signature},
primitives::{FixedBytes, Signature, U256},
signers::local::PrivateKeySigner,
};
use boundless_market::contracts::{
Expand All @@ -412,7 +412,11 @@ mod tests {
}),
format!("file://{ECHO_PATH}"),
RequestInput::builder().write_slice(&[1, 2, 3, 4]).build_inline().unwrap(),
Offer::default(),
Offer::default()
.with_timeout(60)
.with_lock_timeout(30)
.with_max_price(U256::from(1000))
.with_ramp_up_start(10),
);

let signature = request.sign_request(signer, Address::ZERO, 1).await.unwrap();
Expand Down
36 changes: 26 additions & 10 deletions crates/boundless-market/src/contracts/artifacts/Predicate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,45 @@ library PredicateLibrary {
return Predicate({predicateType: PredicateType.ClaimDigestMatch, data: abi.encodePacked(claimDigest)});
}

/// @notice Evaluates the predicate against the given journal and journal digest.
/// @notice Evaluates the predicate against the image ID and journal.
/// @dev If the predicate is of type ClaimDigestMatch and image ID and journal are not available,
/// use the evaluation function with the claim digest instead.
/// @param predicate The predicate to evaluate.
/// @param imageId Image ID to use for evaluation.
/// @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, bytes32 imageId, bytes memory journal, bytes32 journalDigest)
internal
pure
returns (bool)
{
function eval(Predicate memory predicate, bytes32 imageId, bytes memory journal) internal pure returns (bool) {
if (predicate.predicateType == PredicateType.DigestMatch) {
require(predicate.data.length == 64, "Invalid DigestMatch data length");
bytes memory dataJournal = Bytes.slice(predicate.data, 32);
return bytes32(dataJournal) == journalDigest;
return bytes32(dataJournal) == sha256(abi.encode(journal)) && bytes32(predicate.data) == imageId;
} else if (predicate.predicateType == PredicateType.PrefixMatch) {
require(predicate.data.length >= 32, "Invalid PrefixMatch data length");
bytes memory dataJournal = Bytes.slice(predicate.data, 32);
return startsWith(journal, dataJournal);
return startsWith(journal, dataJournal) && bytes32(predicate.data) == imageId;
} else if (predicate.predicateType == PredicateType.ClaimDigestMatch) {
return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, journalDigest).digest();
require(predicate.data.length == 32, "Invalid ClaimDigestMatch data length");
return bytes32(predicate.data) == ReceiptClaimLib.ok(imageId, sha256(abi.encode(journal))).digest();
} else {
revert("Unreachable code");
}
}

/// @notice Evaluates the predicate against the claim digest.
/// @dev This function should be used when the predicate is of type ClaimDigestMatch
/// and the image ID and journal are not available.
/// @param predicate The predicate to evaluate.
/// @param claimDigest Claim digest to use for evaluation.
/// @return True if the predicate is satisfied, false otherwise.
function eval(Predicate memory predicate, bytes32 claimDigest) internal pure returns (bool) {
if (predicate.predicateType == PredicateType.ClaimDigestMatch) {
require(predicate.data.length == 32, "Invalid ClaimDigestMatch data length");
return bytes32(predicate.data) == claimDigest;
} else {
revert("Predicate not of type ClaimDigestMatch");
}
}

/// @notice Checks if the journal starts with the given prefix.
/// @param journal The journal to check.
/// @param prefix The prefix to check for.
Expand Down
6 changes: 2 additions & 4 deletions crates/boundless-market/src/contracts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,7 @@ impl ProofRequest {
contract_addr: Address,
chain_id: u64,
) -> Result<Signature, RequestError> {
let domain = eip712_domain(contract_addr, chain_id);
let hash = self.eip712_signing_hash(&domain.alloy_struct());
let hash = self.signing_hash(contract_addr, chain_id)?;
Ok(signer.sign_hash(&hash).await?)
}

Expand Down Expand Up @@ -525,8 +524,7 @@ impl ProofRequest {
if sig.normalize_s().is_some() {
return Err(RequestError::SignatureNonCanonicalError);
}
let domain = eip712_domain(contract_addr, chain_id);
let hash = self.eip712_signing_hash(&domain.alloy_struct());
let hash = self.signing_hash(contract_addr, chain_id)?;
let addr = sig.recover_address_from_prehash(&hash)?;
if addr == self.client_address() {
Ok(())
Expand Down
9 changes: 3 additions & 6 deletions crates/boundless-market/src/order_stream_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use alloy::{
signers::{Error as SignerErr, Signer},
};
use alloy_primitives::B256;
use alloy_sol_types::SolStruct;
use anyhow::{Context, Result};
use async_stream::stream;
use chrono::{DateTime, Utc};
Expand All @@ -35,7 +34,7 @@ use tokio_tungstenite::{
};
use utoipa::ToSchema;

use crate::contracts::{eip712_domain, ProofRequest, RequestError};
use crate::contracts::{ProofRequest, RequestError};

/// Order stream submission API path.
pub const ORDER_SUBMISSION_PATH: &str = "/api/v1/submit_order";
Expand Down Expand Up @@ -135,8 +134,7 @@ impl Order {
/// Validate the Order
pub fn validate(&self, market_address: Address, chain_id: u64) -> Result<(), OrderError> {
self.request.validate()?;
let domain = eip712_domain(market_address, chain_id);
let hash = self.request.eip712_signing_hash(&domain.alloy_struct());
let hash = self.request.signing_hash(market_address, chain_id)?;
if hash != self.request_digest {
return Err(OrderError::RequestError(RequestError::DigestMismatch));
}
Expand Down Expand Up @@ -224,8 +222,7 @@ impl OrderStreamClient {
let url = self.base_url.join(ORDER_SUBMISSION_PATH)?;
let signature =
request.sign_request(signer, self.boundless_market_address, self.chain_id).await?;
let domain = eip712_domain(self.boundless_market_address, self.chain_id);
let request_digest = request.eip712_signing_hash(&domain.alloy_struct());
let request_digest = request.signing_hash(self.boundless_market_address, self.chain_id)?;
let order = Order { request: request.clone(), request_digest, signature };
order.validate(self.boundless_market_address, self.chain_id)?;
let order_json = serde_json::to_value(&order)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/broker/src/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ mod tests {
minPrice: U256::from(min_price),
maxPrice: U256::from(250000000000000000u64),
rampUpStart: now_timestamp(),
timeout: 50,
timeout: 100,
lockTimeout: 100,
rampUpPeriod: 1,
lockCollateral: U256::from(10),
Expand Down Expand Up @@ -1331,7 +1331,7 @@ mod tests {
minPrice: U256::from(min_price),
maxPrice: U256::from(250000000000000000u64),
rampUpStart: now_timestamp(),
timeout: 50,
timeout: 100,
lockTimeout: 100,
rampUpPeriod: 1,
lockCollateral: U256::from(10),
Expand Down
1 change: 1 addition & 0 deletions crates/guest/assessor/assessor-guest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fn main() {
// by this guest. This check is not strictly needed, but reduces the chance of accidentally
// failing to enforce a constraint.
RequestId::try_from(fill.request.id).unwrap();
fill.request.validate().expect("request is not valid");

// ECDSA signatures are always checked here.
// Smart contract signatures (via EIP-1271) are checked on-chain either when a request is locked,
Expand Down
6 changes: 2 additions & 4 deletions crates/order-stream/src/order_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,9 @@ mod tests {
use alloy::{
primitives::{Bytes, U256},
signers::local::LocalSigner,
sol_types::SolStruct,
};
use boundless_market::contracts::{
eip712_domain, Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements,
Offer, Predicate, ProofRequest, RequestInput, RequestInputType, Requirements,
};
use futures_util::StreamExt;
use risc0_zkvm::sha::Digest;
Expand Down Expand Up @@ -305,8 +304,7 @@ mod tests {
},
};
let signature = req.sign_request(&signer, Address::ZERO, 31337).await.unwrap();
let domain = eip712_domain(Address::ZERO, 31337);
let request_digest = req.eip712_signing_hash(&domain.alloy_struct());
let request_digest = req.signing_hash(Address::ZERO, 31337).unwrap();

Order::new(req, request_digest, signature)
}
Expand Down
Loading