use serde_json::Value;
use crate::c14n;
pub fn fingerprint_form(hex: &str) -> String {
format!("sha256:{hex}")
}
pub fn is_fingerprint_form(value: &str) -> bool {
let Some(hex) = value.strip_prefix("sha256:") else {
return false;
};
hex.len() == 64
&& hex
.bytes()
.all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
}
pub fn source_fingerprint(bytes: &[u8]) -> String {
fingerprint_form(&c14n::sha256_hex_bytes(bytes))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FingerprintManifest {
pub config_sha256: String,
pub payload_sha256: String,
pub profile_id: String,
pub profile_sha256: String,
pub schema_version: String,
pub source_fingerprint: String,
}
impl FingerprintManifest {
pub fn to_value(&self) -> Value {
let mut map = serde_json::Map::new();
map.insert(
"config_sha256".into(),
Value::String(self.config_sha256.clone()),
);
map.insert(
"payload_sha256".into(),
Value::String(self.payload_sha256.clone()),
);
map.insert("profile_id".into(), Value::String(self.profile_id.clone()));
map.insert(
"profile_sha256".into(),
Value::String(self.profile_sha256.clone()),
);
map.insert(
"schema_version".into(),
Value::String(self.schema_version.clone()),
);
map.insert(
"source_fingerprint".into(),
Value::String(self.source_fingerprint.clone()),
);
Value::Object(map)
}
pub fn document_fingerprint(&self) -> Result<String, c14n::C14nError> {
Ok(fingerprint_form(&c14n::sha256_hex(&self.to_value())?))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_contract_vector_v4() {
let m = FingerprintManifest {
config_sha256: "68cc61753d299917cc7773f069c18aca31c8ac68f43736a94cb57eee05144084"
.into(),
payload_sha256: "dad47d0ac4ab90f60691eb884c4c7e58d38ef7b87ef3df4bf602cd6087c9c757"
.into(),
profile_id: "ethos-deterministic-v1".into(),
profile_sha256: "d6145b9210845db39ad592ea549788432b52a649778c9947f5b2d91173e38070"
.into(),
schema_version: "1.0.0".into(),
source_fingerprint:
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef".into(),
};
assert_eq!(
m.document_fingerprint().unwrap(),
"sha256:b5d30710d0c25cc38d8dec924ecaf57ae4f81276dd5dc14d75cb3b5b6bde62d3"
);
}
#[test]
fn source_fingerprint_of_known_bytes() {
assert_eq!(
source_fingerprint(b""),
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn fingerprint_form_validation_is_strict() {
assert!(is_fingerprint_form(
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
));
assert!(!is_fingerprint_form(
"sha256:E3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
));
assert!(!is_fingerprint_form(
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85"
));
assert!(!is_fingerprint_form(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
));
assert!(!is_fingerprint_form(
"sha256:g3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
));
}
}