Skip to content

Commit

Permalink
feat(interpreter-cid,interpreter-data)!: Support for multiple hash ty…
Browse files Browse the repository at this point in the history
…pes in CID verification (#722)

It will allow to change CID hash functions without breaking compatibility or use multiple CID hash functions.
  • Loading branch information
monoid authored Nov 22, 2023
1 parent dc8afde commit 524c302
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 71 deletions.
67 changes: 43 additions & 24 deletions air/tests/test_module/features/signatures/corruption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/// values forged in the CID stores.
use air::ExecutionCidState;
use air::PreparationError;
use air_interpreter_cid::CidVerificationError;
use air_interpreter_signatures::PeerCidTracker;
use air_interpreter_signatures::PublicKey;
use air_interpreter_signatures::SignatureStore;
Expand Down Expand Up @@ -92,10 +93,13 @@ fn test_attack_replace_value() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "serde_json::value::Value",
cid_repr: "bagaaihrayhxgqijfajraxivb7hxwshhbsdqk4j5zyqypb54zggmn5v7mmwxq".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "serde_json::value::Value",
cid_repr: "bagaaihrayhxgqijfajraxivb7hxwshhbsdqk4j5zyqypb54zggmn5v7mmwxq".into(),
}
.into()
)
);
}

Expand Down Expand Up @@ -165,10 +169,13 @@ fn test_attack_replace_tetraplet() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "marine_call_parameters::SecurityTetraplet",
cid_repr: "bagaaihraqlxlbr5q54odmlqwrzpw4smuxzzqbrfas6c7ajhb73samtrjkkva".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "marine_call_parameters::SecurityTetraplet",
cid_repr: "bagaaihraqlxlbr5q54odmlqwrzpw4smuxzzqbrfas6c7ajhb73samtrjkkva".into(),
}
.into()
)
);
}

Expand Down Expand Up @@ -245,10 +252,13 @@ fn test_attack_replace_call_result() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::ServiceResultCidAggregate",
cid_repr: "bagaaihrai3ii6rephch2kv2efkbolmhfjvpj2w3fyr2tj6lavd4yiloy2ybq".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "air_interpreter_data::executed_state::ServiceResultCidAggregate",
cid_repr: "bagaaihrai3ii6rephch2kv2efkbolmhfjvpj2w3fyr2tj6lavd4yiloy2ybq".into(),
}
.into()
)
);
}

Expand Down Expand Up @@ -332,10 +342,13 @@ fn test_attack_replace_canon_value() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonCidAggregate",
cid_repr: "bagaaihram3i44lmbxmukumwohtp2dkocgdqjwzixddzxjmzlvhea7aid5l7q".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "air_interpreter_data::executed_state::CanonCidAggregate",
cid_repr: "bagaaihram3i44lmbxmukumwohtp2dkocgdqjwzixddzxjmzlvhea7aid5l7q".into(),
}
.into()
)
);
}

Expand Down Expand Up @@ -428,10 +441,13 @@ fn test_attack_replace_canon_result_values() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaihrar7xfyl5usjhn5s6xisvwkh55zyq5lvjnwr6j5j3yjutf55aowqea".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaihrar7xfyl5usjhn5s6xisvwkh55zyq5lvjnwr6j5j3yjutf55aowqea".into(),
}
.into()
)
);
}

Expand Down Expand Up @@ -528,9 +544,12 @@ fn test_attack_replace_canon_result_tetraplet() {

assert_error_eq!(
&res,
PreparationError::CidStoreVerificationError(CidStoreVerificationError::MismatchError {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaihrar7xfyl5usjhn5s6xisvwkh55zyq5lvjnwr6j5j3yjutf55aowqea".into(),
})
PreparationError::CidStoreVerificationError(
CidVerificationError::ValueMismatch {
type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate",
cid_repr: "bagaaihrar7xfyl5usjhn5s6xisvwkh55zyq5lvjnwr6j5j3yjutf55aowqea".into(),
}
.into()
)
);
}
10 changes: 7 additions & 3 deletions crates/air-lib/interpreter-cid/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ keywords = ["fluence", "air", "programming-language", "cid", "ipld"]
categories = ["wasm"]

[dependencies]
blake3 = "1.5.0"
cid = { version = "0.10.1", default-features = false, features = ["std"] }
multihash = { version = "0.18.1", default-features = false, features = ["multihash-impl", "std", "blake3"] }
multihash = { version = "0.18.1", default-features = false, features = ["multihash-impl", "std", "sha2", "blake3"] }
serde = { version = "1.0.190", features = ["derive", "rc"] }
serde_json = "1.0.108"
serde_json = "1.0.95"
thiserror = "1.0.49"

# beware: `digest` version should match one of the used in particular hash crates
digest = "0.10.7"
sha2 = "0.10.7"
blake3 = { version = "1.5.0", features = ["traits-preview"] }
89 changes: 62 additions & 27 deletions crates/air-lib/interpreter-cid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
unreachable_patterns
)]

mod verify;

pub use crate::verify::{verify_value, CidVerificationError};

use serde::Deserialize;
use serde::Serialize;
use thiserror::Error as ThisError;

use std::fmt;
use std::io::BufWriter;
Expand All @@ -37,9 +42,12 @@ use std::rc::Rc;
/// Should-be-opaque type for the inner representation of CID.
/// It has to be serializable and Borsh-serializable, as well as implement `Debug`, `Eq`, `Ord`, `Hash` and similar
/// basic traits. It is also can be unsized.
// You should be able to replace it with [u8], and most of the code will just work.
// you should be able to replace it with [u8], and most of the code will just work
pub type CidRef = str;

// there is no Rust multicodec crate with appropriate constants
const JSON_CODEC: u64 = 0x0200;

#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct CID<T: ?Sized>(Rc<CidRef>, #[serde(skip)] PhantomData<*const T>);
Expand Down Expand Up @@ -93,34 +101,24 @@ impl<Val> std::hash::Hash for CID<Val> {
}
}

pub struct CidCalculationError(serde_json::Error);

impl fmt::Debug for CidCalculationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl<T: ?Sized> std::convert::TryFrom<&'_ CID<T>> for cid::Cid {
type Error = cid::Error;

impl fmt::Display for CidCalculationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
fn try_from(value: &CID<T>) -> Result<Self, Self::Error> {
use std::str::FromStr;

impl From<serde_json::Error> for CidCalculationError {
fn from(source: serde_json::Error) -> Self {
Self(source)
cid::Cid::from_str(&value.0)
}
}

impl std::error::Error for CidCalculationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
#[derive(Debug, ThisError)]
pub enum CidCalculationError {
#[error(transparent)]
InvalidJson(#[from] serde_json::Error),
}

/// Calculate a CID of JSON-serialized value.
// TODO we might refactor this to `SerializationFormat` trait
// TODO we might refactor this to `SerializationCodec` trait
// that both transform data to binary/text form (be it JSON, CBOR or something else)
// and produces CID too
pub fn value_to_json_cid<Val: Serialize + ?Sized>(
Expand All @@ -129,16 +127,53 @@ pub fn value_to_json_cid<Val: Serialize + ?Sized>(
use cid::Cid;
use multihash::{Code, MultihashDigest};

let mut hasher = blake3::Hasher::new();
serde_json::to_writer(BufWriter::with_capacity(8 * 1024, &mut hasher), value)?;
let hash = hasher.finalize();
let hash = value_json_hash::<blake3::Hasher, Val>(value)?;

let digest = Code::Blake3_256
.wrap(hash.as_bytes())
.wrap(&hash)
.expect("can't happend: incorrect hash length");
// seems to be better than RAW_CODEC = 0x55
const JSON_CODEC: u64 = 0x0200;

let cid = Cid::new_v1(JSON_CODEC, digest);
Ok(CID::new(cid.to_string()))
}

pub(crate) fn value_json_hash<D: digest::Digest + std::io::Write, Val: Serialize + ?Sized>(
value: &Val,
) -> Result<Vec<u8>, serde_json::Error> {
const HASH_BUFFER_SIZE: usize = 8 * 1024;

let mut hasher = D::new();
serde_json::to_writer(
BufWriter::with_capacity(HASH_BUFFER_SIZE, &mut hasher),
value,
)?;
let hash = hasher.finalize();

Ok(hash.to_vec())
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn test_cid_default() {
assert_eq!(
value_to_json_cid(&json!("test")).unwrap(),
CID::new("bagaaihrarcyykpv4oj7zwdbepczyfthxya4og7s2rwvrzolm5kg2eu5dz3xa")
);
assert_eq!(
value_to_json_cid(&json!([1, 2, 3])).unwrap(),
CID::new("bagaaihram6sitn77tquub77n2jzjgttrlwkverv44pv3gns6qghm6hx6d36a"),
);
assert_eq!(
value_to_json_cid(&json!(1)).unwrap(),
CID::new("bagaaihra2y55tkbgv6i4d7vdoglfuzhbd3ra6e7ennpvfrmzaejwmbntusdq"),
);
assert_eq!(
value_to_json_cid(&json!({"key": 42})).unwrap(),
CID::new("bagaaihracpzxhsrpviexa7k6glwdhyh3a4kvy6j7qlcqokzqbs3q424cmxyq"),
);
}
}
Loading

0 comments on commit 524c302

Please sign in to comment.