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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion crates/boundless-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,12 @@ impl OrderFulfiller {
)?)
};

Self::initialize(prover, client, assessor_selector, ASSESSOR_DEFAULT_IMAGE_URL).await
// ASSESSOR_IMAGE_URL overrides the default assessor guest source (any downloader
// scheme, e.g. file://). The proven guest's image id must match the id pinned by the
// router assessor adapter that `assessor_selector` dispatches to.
let assessor_image_url = std::env::var("ASSESSOR_IMAGE_URL")
.unwrap_or_else(|_| ASSESSOR_DEFAULT_IMAGE_URL.to_string());
Self::initialize(prover, client, assessor_selector, &assessor_image_url).await
}

/// Initialize an OrderFulfiller from a provided Prover instance.
Expand Down
1 change: 1 addition & 0 deletions crates/boundless-market/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ serde_json = { workspace = true }
alloy = { workspace = true, features = ["json-rpc"] }
boundless-test-utils = { workspace = true }
guest-util = { workspace = true }
postcard = { workspace = true, features = ["alloc"] }
temp-env = { version = "0.3.6", features = ["async_closure"] }
tokio = { workspace = true, features = ["test-util"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
Expand Down
41 changes: 39 additions & 2 deletions crates/boundless-market/src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,41 @@
// limitations under the License.

use alloy_primitives::B256;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[cfg(not(target_os = "zkvm"))]
use std::fmt;

/// A 32-byte hash digest representing an image ID or journal digest.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Digest([u8; 32]);

/// Serde encodes as eight little-endian u32 words, wire-identical to
/// `risc0_zkvm::sha::Digest`. Deployed guests (e.g. the on-chain assessor)
/// decode their postcard inputs against that layout, so the encoding is a
/// compatibility contract, not an implementation detail: a byte-array
/// encoding here would silently break every host-to-deployed-guest boundary
/// that embeds a digest.
impl Serialize for Digest {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let words: [u32; 8] = core::array::from_fn(|i| {
u32::from_le_bytes(self.0[i * 4..i * 4 + 4].try_into().expect("4-byte chunk"))
});
words.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for Digest {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let words = <[u32; 8]>::deserialize(deserializer)?;
let mut bytes = [0u8; 32];
for (i, word) in words.iter().enumerate() {
bytes[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes());
}
Ok(Self(bytes))
}
}

impl Digest {
/// The zero digest.
pub const ZERO: Self = Self([0u8; 32]);
Expand Down Expand Up @@ -101,6 +127,17 @@ mod tests {
assert_eq!(<[u8; 32]>::from(d), bytes);
}

#[test]
fn postcard_wire_format_matches_risc0_digest() {
// risc0's Digest is [u32; 8]; postcard varint-encodes each LE word.
// 0x42424242 -> varint c2 84 89 92 04, repeated for all 8 words.
let d = Digest::from_bytes([0x42; 32]);
let encoded = postcard::to_allocvec(&d).unwrap();
assert_eq!(encoded, [0xc2, 0x84, 0x89, 0x92, 0x04].repeat(8));
let decoded: Digest = postcard::from_bytes(&encoded).unwrap();
assert_eq!(decoded, d);
}

#[cfg(not(target_os = "zkvm"))]
#[test]
fn roundtrip_hex() {
Expand Down
Loading