Skip to content

Identity hash doesn't enforce length == len(data) constraint #50

Description

@sumanjeet0012

Go's Sum() explicitly rejects identity hashes where the requested truncation length doesn't match the data length. Python's _do_digest() doesn't have this check, allowing invalid identity multihashes to be created.

Problem

In Go's sum.go:

func encodeHash(hasher hash.Hash, code uint64, length int) (Multihash, error) {
    sum := hasher.Sum(nil)
    if length >= 0 {
        if code == IDENTITY {
            if length != len(sum) {
                return nil, fmt.Errorf(
                    "the length of the identity hash (%d) must be equal to the length of the data (%d)",
                    length, len(sum))
            }
        }
        sum = sum[:length]
    }
    return Encode(sum, code)
}

Python's _do_digest() has no equivalent check:

def _do_digest(data, func, length=None):
    # ... hash computation ...
    # Handle truncation
    if not is_shake and length is not None and length != -1:
        if length > len(digest_bytes):
            raise TruncationError(...)
        digest_bytes = digest_bytes[:length]  # ← No identity check!
    return digest_bytes

This allows creating invalid identity multihashes:

from multihash import sum, Func

# Go would reject this, Python allows it:
mh = sum(b"hello world", Func.identity, length=5)
# Creates a multihash with truncated identity hash — invalid per spec

Proposed Solution

Add an identity hash check in _do_digest():

def _do_digest(data, func, length=None):
    # ... existing code ...

    if not is_shake and length is not None and length != -1:
        if length < 0:
            raise TruncationError(...)
        if length == 0:
            raise TruncationError("truncation length cannot be zero")

        # Identity hash: length must equal data length
        if func == Func.identity and length != len(digest_bytes):
            raise TruncationError(
                f"the length of the identity hash ({length}) must be equal "
                f"to the length of the data ({len(digest_bytes)})"
            )

        if length > len(digest_bytes):
            raise TruncationError(...)
        digest_bytes = digest_bytes[:length]

    return digest_bytes

Add the same check in sum_stream().

Add tests:

def test_identity_hash_length_must_match():
    # Valid: length matches data
    mh = sum(b"hello", Func.identity, length=5)
    assert mh.digest == b"hello"

    # Invalid: length doesn't match
    with pytest.raises(TruncationError, match="identity hash"):
        sum(b"hello", Func.identity, length=3)

Related

  • Go implementation: go-multihash sum.go encodeHash()
  • Go test: TestSmallerLengthHashID in sum_test.go

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions