import hashlib
import json
import pathlib
import pytest
import content_addressable as ca
from content_addressable import (
ContentId,
RawContentId,
content_id,
from_canonical_dagcbor,
from_canonical_dagcbor_checked,
to_canonical_dagcbor,
)
def _blake3_digest(data: bytes):
try:
return hashlib.blake3(data).digest() except (AttributeError, ValueError):
pass
try:
import blake3 as _blake3
return _blake3.blake3(data).digest()
except ImportError:
return None
def test_content_id_is_deterministic():
value = {"name": "alpha", "attrs": {"zeta": 26, "alpha": 1}}
id_a = content_id(value)
id_b = content_id(value)
assert str(id_a) == str(id_b)
assert id_a == id_b
assert hash(id_a) == hash(id_b)
def test_content_id_differs_for_different_values():
a = content_id({"name": "hello"})
b = content_id({"name": "world"})
assert a != b
assert str(a) != str(b)
def test_content_id_usable_as_dict_key():
a1 = content_id({"k": 1})
a2 = content_id({"k": 1})
b = content_id({"k": 2})
s = {a1, a2, b}
assert len(s) == 2
def test_content_id_bytes_roundtrip():
original = content_id({"x": [1, 2, 3]})
raw = original.to_bytes()
assert isinstance(raw, bytes)
restored = ContentId.from_bytes(raw)
assert restored == original
assert str(restored) == str(original)
def test_content_id_string_roundtrip():
original = content_id({"x": [1, 2, 3]})
text = str(original)
restored = ContentId.parse(text)
assert restored == original
assert restored.to_bytes() == original.to_bytes()
def test_content_id_repr():
cid = content_id({"a": 1})
r = repr(cid)
assert r.startswith("ContentId('")
assert str(cid) in r
def test_content_id_v1_base32_string_shape():
cid = content_id({"a": 1})
s = str(cid)
assert s.startswith("b")
def test_digest_bytes_is_raw_32_byte_blake3_hash():
cid = content_id({"a": 1})
d = cid.digest_bytes()
assert isinstance(d, bytes)
assert len(d) == 32
assert cid.to_bytes() == bytes([0x01, 0x71, 0x1E, 0x20]) + d
assert cid.to_bytes()[-32:] == d
def test_digest_hex_is_64_char_lower_hex_no_prefix():
cid = content_id({"a": 1})
h = cid.digest_hex()
assert isinstance(h, str)
assert len(h) == 64
assert not h.startswith("0x")
assert h == h.lower()
assert all(c in "0123456789abcdef" for c in h)
assert h == cid.digest_bytes().hex()
cid_hex = cid.to_bytes().hex()
assert cid_hex.endswith(h)
assert len(cid_hex) > len(h)
assert cid_hex == "01711e20" + h
def test_digest_hex_empty_map_is_frozen_value():
cid = content_id({})
assert (
cid.digest_hex()
== "1f94cbf313b3ce23257a7251ea0fc95a24556ea611e4f8f475e549971baedb02"
)
def test_digest_forms_agree_with_no_rehash_bridge():
cid = content_id({"x": [1, 2, 3]})
rebuilt = ContentId.from_blake3_content_digest(cid.digest_bytes())
assert rebuilt == cid
assert rebuilt.digest_hex() == cid.digest_hex()
def test_canonical_dagcbor_is_insertion_order_independent():
one = {}
one["alpha"] = 1
one["zeta"] = 26
two = {}
two["zeta"] = 26
two["alpha"] = 1
assert to_canonical_dagcbor(one) == to_canonical_dagcbor(two)
assert content_id(one) == content_id(two)
def test_canonical_dagcbor_nested_order_independent():
a = {"outer": {"b": 2, "a": 1}, "k": [1, 2, 3]}
b = {"k": [1, 2, 3], "outer": {"a": 1, "b": 2}}
assert to_canonical_dagcbor(a) == to_canonical_dagcbor(b)
def test_content_id_equals_hash_of_its_own_canonical_bytes():
value = {"name": "alpha", "attrs": {"zeta": 26, "alpha": 1}, "n": 7}
canon = to_canonical_dagcbor(value)
by_hand = ContentId.from_canonical_bytes(canon)
via_helper = content_id(value)
assert by_hand == via_helper
assert str(by_hand) == str(via_helper)
def test_content_id_matches_independent_blake3_cidv1():
value = {"hello": "world", "n": [1, 2, 3]}
canon = to_canonical_dagcbor(value)
digest = _blake3_digest(canon)
if digest is None:
pytest.skip("no BLAKE3 implementation available (need py3.13+ or `blake3`)")
assert len(digest) == 32
multihash = bytes([0x1E, 0x20]) + digest
cid_bytes = bytes([0x01, 0x71]) + multihash
cid = content_id(value)
assert cid.to_bytes() == cid_bytes
assert ContentId.from_bytes(cid_bytes) == cid
def test_from_blake3_content_digest_wraps_without_rehashing():
d = bytes(range(1, 33)) assert len(d) == 32
wrapped = ContentId.from_blake3_content_digest(d)
assert wrapped.to_bytes()[-32:] == d
assert wrapped.to_bytes() == bytes([0x01, 0x71, 0x1E, 0x20]) + d
rehashed = ContentId.from_canonical_bytes(d)
assert wrapped != rehashed
assert wrapped != content_id(d)
assert ContentId.from_bytes(wrapped.to_bytes()) == wrapped
def test_from_blake3_content_digest_converges_with_from_canonical_bytes():
content = b"\x00\x01\x02 some canonical dag-cbor bytes"
digest = _blake3_digest(content)
if digest is None:
pytest.skip("no BLAKE3 implementation available (need py3.13+ or `blake3`)")
assert len(digest) == 32
via_digest = ContentId.from_blake3_content_digest(digest)
via_hash = ContentId.from_canonical_bytes(content)
assert via_digest == via_hash
assert str(via_digest) == str(via_hash)
def test_from_blake3_content_digest_rejects_wrong_length():
for bad in (b"", b"too short", bytes(31), bytes(33), bytes(64)):
with pytest.raises(ValueError):
ContentId.from_blake3_content_digest(bad)
def test_dagcbor_roundtrip_values():
for value in [
None,
True,
False,
0,
-1,
42,
"hello",
b"\x00\x01\x02bytes",
[1, "two", [3, 4], None],
{"a": 1, "b": [True, False], "c": {"nested": "map"}},
{},
[],
]:
encoded = to_canonical_dagcbor(value)
assert isinstance(encoded, bytes)
decoded = from_canonical_dagcbor(encoded)
assert decoded == value, f"round-trip mismatch for {value!r}"
def test_dagcbor_float_roundtrip():
value = {"pi": 3.14159, "e": 2.71828}
decoded = from_canonical_dagcbor(to_canonical_dagcbor(value))
assert decoded == value
def test_bytes_decode_as_python_bytes():
decoded = from_canonical_dagcbor(to_canonical_dagcbor(b"raw"))
assert decoded == b"raw"
assert isinstance(decoded, bytes)
def test_tuple_encodes_like_list():
assert to_canonical_dagcbor((1, 2, 3)) == to_canonical_dagcbor([1, 2, 3])
assert from_canonical_dagcbor(to_canonical_dagcbor((1, 2, 3))) == [1, 2, 3]
def test_parse_invalid_cid_string_raises_value_error():
with pytest.raises(ValueError):
ContentId.parse("not-a-cid")
def test_from_bytes_invalid_raises_value_error():
with pytest.raises(ValueError):
ContentId.from_bytes(b"\xff\xff\xff not a cid")
def test_unsupported_type_raises_type_error():
class Custom:
pass
with pytest.raises(TypeError):
to_canonical_dagcbor(Custom())
REORDERED_KEYS = bytes([0xA2, 0x62, 0x62, 0x62, 0x01, 0x61, 0x61, 0x02])
NON_MINIMAL_INT = bytes([0xA1, 0x61, 0x61, 0x18, 0x01])
@pytest.mark.parametrize(
"name,raw",
[("reordered_keys", REORDERED_KEYS), ("non_minimal_int", NON_MINIMAL_INT)],
)
def test_checked_decode_refuses_non_canonical_bytes(name, raw):
with pytest.raises(ValueError):
from_canonical_dagcbor_checked(raw)
@pytest.mark.parametrize(
"name,raw",
[("reordered_keys", REORDERED_KEYS), ("non_minimal_int", NON_MINIMAL_INT)],
)
def test_unchecked_decode_accepts_them_and_the_identity_shifts(name, raw):
value = from_canonical_dagcbor(raw)
assert to_canonical_dagcbor(value) != raw
assert content_id(value) != ContentId.from_canonical_bytes(raw)
def test_a_python_decode_cannot_drop_a_field():
record = {"alpha": 1, "zeta": 26}
raw = to_canonical_dagcbor(record)
assert from_canonical_dagcbor_checked(raw) == record
assert to_canonical_dagcbor(from_canonical_dagcbor_checked(raw)) == raw
assert content_id(record) == ContentId.from_canonical_bytes(raw)
def test_checked_decode_accepts_canonical_bytes_and_preserves_identity():
for value in [None, True, 0, -1, 42, "hello", b"bytes", [1, [2]], {}, {"a": 1}]:
raw = to_canonical_dagcbor(value)
assert from_canonical_dagcbor_checked(raw) == value
assert (
content_id(from_canonical_dagcbor_checked(raw))
== ContentId.from_canonical_bytes(raw)
), f"identity must survive the checked round trip for {value!r}"
def test_checked_decode_rejects_non_cbor_garbage():
with pytest.raises(ValueError):
from_canonical_dagcbor_checked(b"\xff\xff\xff\xff")
def _link_bytes(cid):
payload = b"\x00" + cid.to_bytes()
return bytes([0xD8, 0x2A, 0x58, len(payload)]) + payload
def test_the_checked_door_accepts_a_canonical_link():
cid = content_id({"a": 1})
raw = _link_bytes(cid)
assert from_canonical_dagcbor_checked(raw) == cid
assert isinstance(from_canonical_dagcbor_checked(raw), ContentId)
def test_a_decoded_link_cannot_be_re_encoded_yet():
cid = content_id({"a": 1})
raw = _link_bytes(cid)
decoded = from_canonical_dagcbor_checked(raw)
with pytest.raises(TypeError):
to_canonical_dagcbor(decoded)
nested = bytes([0xA1, 0x64]) + b"link" + raw
value = from_canonical_dagcbor_checked(nested)
assert value == {"link": cid}
with pytest.raises(TypeError):
to_canonical_dagcbor(value)
validated = from_canonical_dagcbor_checked(nested) assert validated == {"link": cid}
minted = ContentId.from_canonical_bytes(nested)
assert str(minted).startswith("b") and len(minted.digest_hex()) == 64
def test_checked_decode_rejects_a_link_to_a_foreign_cid():
foreign_link = bytes.fromhex("d82a58250001711220" + "07" * 32)
with pytest.raises(ValueError):
from_canonical_dagcbor_checked(foreign_link)
with pytest.raises(ValueError):
from_canonical_dagcbor(foreign_link)
def test_module_all_and_doc():
assert set(ca.__all__) == {
"ContentId",
"RawContentId",
"to_canonical_dagcbor",
"from_canonical_dagcbor",
"from_canonical_dagcbor_checked",
"content_id",
}
assert ca.__doc__
_VECTORS_PATH = pathlib.Path(__file__).parent / "vectors.json"
_BYTES_TAG = "$bytes"
def _expand_vector_value(value):
if isinstance(value, dict):
if len(value) == 1 and _BYTES_TAG in value:
return bytes.fromhex(value[_BYTES_TAG])
return {k: _expand_vector_value(v) for k, v in value.items()}
if isinstance(value, list):
return [_expand_vector_value(v) for v in value]
return value
def _load_vectors():
with _VECTORS_PATH.open(encoding="utf-8") as fh:
return json.load(fh)
_VECTORS = _load_vectors()
def test_vectors_file_present_and_covers_required_cases():
assert len(_VECTORS) >= 8, f"need >= 8 vectors, found {len(_VECTORS)}"
names = {v["name"] for v in _VECTORS}
for required in (
"empty_map",
"empty_list",
"nested_map",
"map_order_independent",
"ints_strings_bytes_mix",
"sample_a",
):
assert required in names, f"vectors.json must contain {required!r}"
@pytest.mark.parametrize("vector", _VECTORS, ids=[v["name"] for v in _VECTORS])
def test_golden_vector_matches_python_face(vector):
value = _expand_vector_value(vector["value"])
canon = to_canonical_dagcbor(value)
assert canon.hex() == vector["canonical_dagcbor_hex"], (
f"[{vector['name']}] canonical dag-cbor hex drifted"
)
cid = content_id(value)
assert str(cid) == vector["content_id_str"], (
f"[{vector['name']}] ContentId string drifted"
)
assert cid.to_bytes().hex() == vector["content_id_bytes_hex"], (
f"[{vector['name']}] ContentId bytes drifted"
)
assert cid.digest_hex() == vector["digest_hex"], (
f"[{vector['name']}] digest_hex drifted"
)
assert cid.digest_bytes().hex() == vector["digest_hex"], (
f"[{vector['name']}] digest_bytes() disagrees with pinned digest_hex"
)
assert vector["content_id_bytes_hex"] == "01711e20" + vector["digest_hex"], (
f"[{vector['name']}] digest_hex must be the CID-bytes tail"
)
assert ContentId.from_bytes(bytes.fromhex(vector["content_id_bytes_hex"])) == cid
_RAW_VECTORS_PATH = pathlib.Path(__file__).parent / "raw_vectors.json"
def _load_raw_vectors():
with _RAW_VECTORS_PATH.open(encoding="utf-8") as fh:
return json.load(fh)
_RAW_VECTORS = _load_raw_vectors()
def test_raw_vectors_present():
assert len(_RAW_VECTORS) >= 6
names = {v["name"] for v in _RAW_VECTORS}
for required in ("empty", "hello", "all_byte_values", "dagcbor_empty_map_as_raw_bytes"):
assert required in names, f"raw_vectors.json must contain {required!r}"
@pytest.mark.parametrize("vector", _RAW_VECTORS, ids=[v["name"] for v in _RAW_VECTORS])
def test_raw_golden_vector_matches_python_face(vector):
content = bytes.fromhex(vector["content_hex"])
rid = RawContentId.from_content(content)
assert str(rid) == vector["raw_content_id_str"]
assert rid.to_bytes() == bytes.fromhex(vector["raw_content_id_bytes_hex"])
assert rid.digest_hex() == vector["digest_hex"]
assert rid.digest_bytes() == bytes.fromhex(vector["digest_hex"])
assert rid.verify(content)
assert not rid.verify(content + b"x")
assert vector["raw_content_id_bytes_hex"].startswith("01551e20")
assert RawContentId.parse(vector["raw_content_id_str"]) == rid
assert RawContentId.from_bytes(rid.to_bytes()) == rid
assert RawContentId.from_blake3_digest(rid.digest_bytes()) == rid
@pytest.mark.parametrize("vector", _RAW_VECTORS, ids=[v["name"] for v in _RAW_VECTORS])
def test_raw_and_dagcbor_profiles_never_compare_equal(vector):
rid = RawContentId.parse(vector["raw_content_id_str"])
cid = ContentId.parse(vector["content_id_of_same_digest_str"])
assert rid.digest_hex() == cid.digest_hex()
assert str(rid) != str(cid)
assert rid.to_bytes() != cid.to_bytes()
assert (rid == cid) is False
assert (cid == rid) is False
with pytest.raises(ValueError):
RawContentId.parse(vector["content_id_of_same_digest_str"])
with pytest.raises(ValueError):
ContentId.parse(vector["raw_content_id_str"])
with pytest.raises(ValueError):
RawContentId.from_bytes(cid.to_bytes())
with pytest.raises(ValueError):
ContentId.from_bytes(rid.to_bytes())
def test_raw_content_id_matches_independent_blake3_cidv1():
blake3 = pytest.importorskip("blake3")
content = b"independent raw check"
digest = blake3.blake3(content).digest()
assert RawContentId.from_content(content).to_bytes() == bytes([0x01, 0x55, 0x1E, 0x20]) + digest
def test_raw_content_id_rejects_wrong_digest_length_and_garbage():
with pytest.raises(ValueError):
RawContentId.from_blake3_digest(b"\x00" * 31)
with pytest.raises(ValueError):
RawContentId.parse("not a cid")
with pytest.raises(ValueError):
RawContentId.from_bytes(b"nope")
def test_raw_content_id_repr_hash_and_dict_key():
a = RawContentId.from_content(b"k")
b = RawContentId.from_content(b"k")
assert repr(a).startswith("RawContentId('b")
assert a == b and hash(a) == hash(b)
assert {a: 1}[b] == 1
def test_from_dag_cbor_digest_is_the_explicit_successor():
cid = content_id({"a": 1})
assert ContentId.from_dag_cbor_digest(cid.digest_bytes()) == cid
assert ContentId.from_dag_cbor_digest(cid.digest_bytes()) == ContentId.from_blake3_content_digest(
cid.digest_bytes()
)
with pytest.raises(ValueError):
ContentId.from_dag_cbor_digest(b"\x00" * 8)
_LEGACY_VECTORS_PATH = pathlib.Path(__file__).parent / "legacy_vectors.json"
def _load_legacy_vectors():
with _LEGACY_VECTORS_PATH.open(encoding="utf-8") as fh:
return json.load(fh)
_LEGACY_VECTORS = _load_legacy_vectors()
def test_legacy_vectors_present():
assert len(_LEGACY_VECTORS) >= 4
names = {v["name"] for v in _LEGACY_VECTORS}
assert {"empty", "hello", "quick_brown_fox"} <= names
@pytest.mark.parametrize(
"vector", _LEGACY_VECTORS, ids=[v["name"] for v in _LEGACY_VECTORS]
)
def test_legacy_blake3_dialects_name_the_same_identity(vector):
content = bytes.fromhex(vector["content_hex"])
rid = RawContentId.from_content(content)
assert str(rid) == vector["expect_raw_content_id_str"]
assert vector["kyln_envelope_hex"] == rid.to_bytes().hex()
assert RawContentId.from_bytes(bytes.fromhex(vector["kyln_envelope_hex"])) == rid
assert vector["nessie_blake3_text"] == f"blake3:{rid.digest_hex()}"
assert vector["bare_blake3_hex"] == rid.digest_hex()
assert RawContentId.from_blake3_digest(bytes.fromhex(vector["bare_blake3_hex"])) == rid
@pytest.mark.parametrize(
"vector", _LEGACY_VECTORS, ids=[v["name"] for v in _LEGACY_VECTORS]
)
def test_nessie_sha256_is_genuine_and_stays_foreign(vector):
content = bytes.fromhex(vector["content_hex"])
algo, sha_hex = vector["nessie_sha256_text"].split(":")
assert algo == "sha2-256"
assert sha_hex == hashlib.sha256(content).hexdigest()
assert vector["expect_foreign_cid_bytes_hex"] == "01551220" + sha_hex
with pytest.raises(ValueError):
RawContentId.parse(vector["expect_foreign_cid_str"])
with pytest.raises(ValueError):
ContentId.parse(vector["expect_foreign_cid_str"])
with pytest.raises(ValueError):
RawContentId.from_bytes(bytes.fromhex(vector["expect_foreign_cid_bytes_hex"]))
with pytest.raises(ValueError):
ContentId.from_bytes(bytes.fromhex(vector["expect_foreign_cid_bytes_hex"]))
assert vector["expect_foreign_cid_str"] != vector["expect_raw_content_id_str"]
def test_legacy_dialects_are_not_accepted_by_the_canonical_parsers():
v = _LEGACY_VECTORS[1] for dialect in (
v["kyln_envelope_hex"], v["nessie_blake3_text"], v["nessie_sha256_text"], v["bare_blake3_hex"], ):
with pytest.raises(ValueError):
RawContentId.parse(dialect)
with pytest.raises(ValueError):
ContentId.parse(dialect)