Skip to content

CIDv0 constructor doesn't validate multihash — accepts any hash type #63

Description

@sumanjeet0012

CIDv0.__init__() accepts any multihash without validation. Per the CID specification, CIDv0 must use sha2-256 with a 32-byte digest. Go's tryNewCidV0() enforces this constraint; Python does not.

Problem

In cid/cid.py:

class CIDv0(BaseCID):
    CODEC = "dag-pb"

    def __init__(self, multihash: str | bytes) -> None:
        super().__init__(0, self.CODEC, multihash)
        # ← No validation of the multihash!

This allows creating invalid CIDv0 objects:

import hashlib
import multihash

# Using sha3-512 instead of sha2-256 — invalid for CIDv0!
digest = hashlib.sha3_512(b"hello").digest()
mh = multihash.encode(digest, "sha3-512")
cid = CIDv0(mh)  # ← Succeeds, but produces invalid CIDv0

# Using sha2-256 with truncated digest — also invalid!
digest = hashlib.sha256(b"hello").digest()[:16]
mh = multihash.encode(digest, "sha2-256", 16)
cid = CIDv0(mh)  # ← Succeeds, but digest is only 16 bytes

Go's implementation validates:

func tryNewCidV0(mhash mh.Multihash) (Cid, error) {
    dec, err := mh.Decode(mhash)
    if err != nil {
        return Undef, ErrInvalidCid{err}
    }
    if dec.Code != mh.SHA2_256 || dec.Length != 32 {
        return Undef, ErrInvalidCid{
            fmt.Errorf("invalid hash for cidv0 %d-%d", dec.Code, dec.Length)}
    }
    return Cid{string(mhash)}, nil
}

Proposed Solution

Add validation to CIDv0.__init__():

class CIDv0(BaseCID):
    CODEC = "dag-pb"

    def __init__(self, multihash: str | bytes) -> None:
        multihash = ensure_bytes(multihash)
        # Validate multihash is sha2-256 with 32-byte digest
        try:
            mh_info = mh.decode(multihash)
        except Exception as e:
            raise ValueError(f"invalid multihash for CIDv0: {e}") from e

        if mh_info.code != 0x12 or mh_info.length != 32:
            raise ValueError(
                f"invalid hash for CIDv0: must be sha2-256 with 32-byte digest, "
                f"got {mh_info.name} with {mh_info.length}-byte digest"
            )

        super().__init__(0, self.CODEC, multihash)

Add tests:

def test_cidv0_rejects_non_sha256():
    digest = hashlib.sha3_512(b"hello").digest()
    mh_bytes = multihash.encode(digest, "sha3-512")
    with pytest.raises(ValueError, match="must be sha2-256"):
        CIDv0(mh_bytes)

def test_cidv0_rejects_truncated_digest():
    digest = hashlib.sha256(b"hello").digest()[:16]
    mh_bytes = multihash.encode(digest, "sha2-256", 16)
    with pytest.raises(ValueError, match="32-byte digest"):
        CIDv0(mh_bytes)

Related

  • Go implementation: go-cid cid.go tryNewCidV0()
  • File: cid/cid.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions