mod common;
use common::{bytes_to_hex, hex_to_bytes, json_value_to_ipld};
use content_addressable::content_id::{BLAKE3_HASH_CODE, DAG_CBOR_CODEC};
use content_addressable::{canonical, ContentId};
use serde::Deserialize;
#[derive(Deserialize)]
struct Vector {
name: String,
value: serde_json::Value,
canonical_dagcbor_hex: String,
content_id_str: String,
content_id_bytes_hex: String,
digest_hex: String,
}
fn load_vectors() -> Vec<Vector> {
let raw = include_str!("vectors.json");
serde_json::from_str(raw).expect("tests/vectors.json must be valid JSON")
}
#[test]
fn vectors_file_is_nonempty_and_covers_required_cases() {
let vectors = load_vectors();
assert!(
vectors.len() >= 8,
"the issue requires >= 8 vectors; found {}",
vectors.len()
);
let names: Vec<&str> = vectors.iter().map(|v| v.name.as_str()).collect();
for required in [
"empty_map",
"empty_list",
"nested_map",
"map_order_independent",
"ints_strings_bytes_mix",
"sample_a",
] {
assert!(
names.contains(&required),
"vectors.json must contain the {required:?} case; have {names:?}"
);
}
}
#[test]
fn every_vector_matches_the_rust_core() {
for v in load_vectors() {
let ipld = json_value_to_ipld(&v.value);
let bytes = canonical::to_canonical_dagcbor(&ipld)
.unwrap_or_else(|e| panic!("[{}] encode failed: {e}", v.name));
assert_eq!(
bytes_to_hex(&bytes),
v.canonical_dagcbor_hex,
"[{}] canonical dag-cbor hex drifted",
v.name
);
let id = ContentId::from_canonical_bytes(&bytes);
assert_eq!(
id.to_string(),
v.content_id_str,
"[{}] ContentId string drifted",
v.name
);
assert_eq!(
bytes_to_hex(&id.to_bytes()),
v.content_id_bytes_hex,
"[{}] ContentId bytes drifted",
v.name
);
assert_eq!(
id.digest_hex(),
v.digest_hex,
"[{}] digest_hex drifted",
v.name
);
assert_eq!(
v.digest_hex.len(),
64,
"[{}] digest_hex must be 64 chars",
v.name
);
assert!(
!v.digest_hex.starts_with("0x"),
"[{}] digest_hex must carry no 0x prefix",
v.name
);
assert!(
v.digest_hex
.bytes()
.all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)),
"[{}] digest_hex must be lowercase hex only",
v.name
);
assert_eq!(
v.content_id_bytes_hex,
format!("01711e20{}", v.digest_hex),
"[{}] digest_hex must be the CID-bytes tail after the 01711e20 prefix",
v.name
);
assert_eq!(
bytes_to_hex(&id.digest_bytes()),
v.digest_hex,
"[{}] digest_bytes() hex must equal the pinned digest_hex",
v.name
);
let expected_cid_bytes = hex_to_bytes(&v.content_id_bytes_hex);
let parsed = ContentId::from_bytes(&expected_cid_bytes)
.unwrap_or_else(|e| panic!("[{}] pinned CID bytes do not parse: {e}", v.name));
assert_eq!(
parsed, id,
"[{}] pinned CID bytes name a different id",
v.name
);
}
}
#[test]
fn pinned_cid_bytes_are_independently_reconstructible() {
assert_eq!(DAG_CBOR_CODEC, 0x71, "this gate hard-codes dag-cbor 0x71");
assert_eq!(BLAKE3_HASH_CODE, 0x1e, "this gate hard-codes BLAKE3 0x1e");
const CID_HEADER: [u8; 4] = [0x01, 0x71, 0x1e, 0x20];
let vectors = load_vectors();
assert!(!vectors.is_empty());
for v in &vectors {
let dagcbor = hex_to_bytes(&v.canonical_dagcbor_hex);
let digest = blake3::hash(&dagcbor);
assert_eq!(
digest.as_bytes().len(),
32,
"BLAKE3 digest must be 32 bytes"
);
let mut hand_built = Vec::with_capacity(CID_HEADER.len() + 32);
hand_built.extend_from_slice(&CID_HEADER);
hand_built.extend_from_slice(digest.as_bytes());
let id = ContentId::from_canonical_bytes(&dagcbor);
assert_eq!(
id.to_bytes(),
hand_built,
"[{}] hand-reconstructed CID bytes disagree with ContentId::to_bytes() \
— this would be a real wire-shape bug, not a vector typo",
v.name
);
assert_eq!(
bytes_to_hex(&hand_built),
v.content_id_bytes_hex,
"[{}] pinned CID bytes disagree with the independent reconstruction",
v.name
);
}
}
#[test]
fn every_vector_survives_the_checked_decoder() {
for v in load_vectors() {
let bytes = hex_to_bytes(&v.canonical_dagcbor_hex);
let decoded: ipld_core::ipld::Ipld = canonical::from_canonical_dagcbor_checked(&bytes)
.unwrap_or_else(|e| {
panic!(
"[{}] the checked decoder rejected a GOLDEN vector: {e}",
v.name
)
});
assert_eq!(
decoded,
json_value_to_ipld(&v.value),
"[{}] the checked decoder returned a different value",
v.name
);
let reencoded = canonical::to_canonical_dagcbor(&decoded)
.unwrap_or_else(|e| panic!("[{}] re-encode failed: {e}", v.name));
assert_eq!(
bytes_to_hex(&reencoded),
v.canonical_dagcbor_hex,
"[{}] a decoded golden vector re-encoded to different bytes",
v.name
);
assert_eq!(
ContentId::from_canonical_bytes(&reencoded).to_string(),
v.content_id_str,
"[{}] the round trip moved the identity",
v.name
);
}
}