diff --git a/contracts/src/types/Predicate.sol b/contracts/src/types/Predicate.sol index da5be1446..439d6c48c 100644 --- a/contracts/src/types/Predicate.sol +++ b/contracts/src/types/Predicate.sol @@ -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. diff --git a/contracts/test/types/Predicate.t.sol b/contracts/test/types/Predicate.t.sol index 716be8f7c..0899253b1 100644 --- a/contracts/test/types/Predicate.t.sol +++ b/contracts/test/types/Predicate.t.sol @@ -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"); } @@ -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"); } @@ -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( @@ -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( @@ -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"); } } diff --git a/crates/assessor/src/lib.rs b/crates/assessor/src/lib.rs index 0ec2ac5fd..267ba5909 100644 --- a/crates/assessor/src/lib.rs +++ b/crates/assessor/src/lib.rs @@ -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), @@ -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), diff --git a/crates/boundless-cli/src/lib.rs b/crates/boundless-cli/src/lib.rs index 0b3cbca04..1469afe1f 100644 --- a/crates/boundless-cli/src/lib.rs +++ b/crates/boundless-cli/src/lib.rs @@ -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::{ @@ -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(); diff --git a/crates/boundless-market/src/contracts/artifacts/Predicate.sol b/crates/boundless-market/src/contracts/artifacts/Predicate.sol index da5be1446..439d6c48c 100644 --- a/crates/boundless-market/src/contracts/artifacts/Predicate.sol +++ b/crates/boundless-market/src/contracts/artifacts/Predicate.sol @@ -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. diff --git a/crates/boundless-market/src/contracts/mod.rs b/crates/boundless-market/src/contracts/mod.rs index e9f6f6eca..19c7c415a 100644 --- a/crates/boundless-market/src/contracts/mod.rs +++ b/crates/boundless-market/src/contracts/mod.rs @@ -496,8 +496,7 @@ impl ProofRequest { contract_addr: Address, chain_id: u64, ) -> Result { - 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?) } @@ -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(()) diff --git a/crates/boundless-market/src/order_stream_client.rs b/crates/boundless-market/src/order_stream_client.rs index ac8986455..f9321f23c 100644 --- a/crates/boundless-market/src/order_stream_client.rs +++ b/crates/boundless-market/src/order_stream_client.rs @@ -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}; @@ -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"; @@ -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)); } @@ -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)?; diff --git a/crates/broker/src/aggregator.rs b/crates/broker/src/aggregator.rs index 029cdd62d..da15fe926 100644 --- a/crates/broker/src/aggregator.rs +++ b/crates/broker/src/aggregator.rs @@ -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), @@ -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), diff --git a/crates/guest/assessor/assessor-guest/src/main.rs b/crates/guest/assessor/assessor-guest/src/main.rs index f03dd83a0..96b9686d8 100644 --- a/crates/guest/assessor/assessor-guest/src/main.rs +++ b/crates/guest/assessor/assessor-guest/src/main.rs @@ -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, diff --git a/crates/order-stream/src/order_db.rs b/crates/order-stream/src/order_db.rs index 762141fef..40fe1b0ff 100644 --- a/crates/order-stream/src/order_db.rs +++ b/crates/order-stream/src/order_db.rs @@ -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; @@ -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) }