multiformats / multiformats/py-multihash

BLAKE3 only supports fixed 32-byte output — should support variable length up to 128 bytes

Open
#49 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

Blake3Hash always produces a 32-byte digest. Go's BLAKE3 registration supports variable-length output (32, 64, 128 bytes, up to max 128). Calling sum(data, "blake3", length=64) raises TruncationError because the digest is only 32 bytes, even though BLAKE3 natively supports extended output.

Problem

In multihash/funcs.py:

class Blake3Hash:
    digest_size: int = 32  # Always 32 bytes

    def digest(self) -> bytes:
        return self._hasher.digest()  # Always returns 32 bytes

The blake3 Python library supports variable-length output:

import blake3
h = blake3.blake3()
h.update(b"foo")
h.digest(64)   # Returns 64 bytes ✅
h.digest(128)  # Returns 128 bytes ✅

But the wrapper doesn't use this capability. So:

from multihash import sum, Func

# This raises TruncationError:
mh = sum(b"foo", Func.blake3, length=64)
# TruncationError: truncation length 64 exceeds digest size 32

Go's implementation uses RegisterVariableSize:

multihash.RegisterVariableSize(multihash.BLAKE3, func(size int) (hash.Hash, bool) {
    if size == -1 { size = 32 }
    else if size > 128 || size <= 0 { return nil, false }
    h := blake3.New(size, nil)
    return h, true
})

Go test vectors include BLAKE3 at 32, 64, and 128 bytes.

Proposed Solution

Modify Blake3Hash to accept a length parameter:

class Blake3Hash:
    MAX_SIZE = 128

    def __init__(self, length: int = 32):
        self._hasher = blake3.blake3()
        self._length = length
        self.name = "blake3"
        self.digest_size = length
        self.block_size = 64

    def digest(self) -> bytes:
        return self._hasher.digest(self._length)

    def copy(self):
        c = Blake3Hash(self._length)
        c._hasher = self._hasher.copy()
        return c

Update FuncReg.hash_from_func() to pass length for BLAKE3 (similar to SHAKE):

@classmethod
def hash_from_func(cls, func, length=None):
    new = cls._func_hash[func].new
    if new is None:
        # Handle SHAKE and BLAKE3 with variable length
        if func == Func.blake3:
            blake3_len = length if length and length > 0 else 32
            if blake3_len > Blake3Hash.MAX_SIZE:
                return None
            return Blake3Hash(blake3_len)
        # ... existing SHAKE handling ...
    return new()

Add tests matching Go's test vectors for BLAKE3 at 32, 64, and 128 bytes.

Related

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 in multihash/funcs.py with Blake3Hash and FuncReg.hash_from_func(), then compare the referenced Go registration and test vectors. Support BLAKE3 output lengths of 32, 64, and 128 bytes while preserving the default behavior and rejecting lengths above 128. Add tests covering those three lengths and verify the existing 32-byte case still passes.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.