multiformats / multiformats/py-multihash

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

Open Beginner friendly
#50 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
17
Forks
17
Avg merge
1h 5m
Merged PRs (30d)
3

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating _do_digest() and sum_stream() in the Python implementation and review their existing truncation handling. Add coverage for matching and mismatched identity lengths, including the expected TruncationError, and run the relevant test suite to confirm valid identity hashes remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cryptography
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.