Skip to content

hexToBytes changes odd-length byte values when size is specified #5439

Closed

Description

@omerbek

Check existing issues

Viem Version

2.57.16; also reproduced against current main at 5628e6440f1d7605001fc655158102ebedcbbb6f (Node.js 24.20.0, Windows x64).

Current Behavior

Adding size changes bytes already represented by an accepted odd-length hex value:

import { hexToBytes, toBytes } from 'viem'

Array.from(hexToBytes('0x1'))                 // [1]
Array.from(hexToBytes('0x1', { size: 1 }))    // [16]
Array.from(toBytes('0x1', { size: 2 }))       // [16, 0]
Array.from(hexToBytes('0xabc'))               // [10, 188]
Array.from(hexToBytes('0xabc', { size: 2 }))  // [171, 192]

These are observed offline outputs from the actual current source module, not a reimplementation. The toBytes public dispatch path is affected too. Even-length control hexToBytes('0x12', { size: 2 }) returns [18, 0].

Expected Behavior

Normalize accepted odd-length input as the unsized conversion already does, then append zero bytes for size: [1], [1, 0], and [10, 188] for the sized examples above. This does not propose rejecting odd-length hex or changing isHex.

Steps To Reproduce

Run the snippet above with Viem 2.57.16. For a dependency-free reproduction from the stated source commit, Node 24 can execute the original TypeScript using a resolver hook for the repository's .js source specifiers:

node --input-type=module <<'JS'
import { registerHooks } from 'node:module'
import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
registerHooks({
  resolve(specifier, context, nextResolve) {
    if (specifier.startsWith('.') && specifier.endsWith('.js') && context.parentURL) {
      const url = new URL(specifier.slice(0, -3) + '.ts', context.parentURL)
      if (existsSync(fileURLToPath(url))) return nextResolve(url.href, context)
    }
    return nextResolve(specifier, context)
  },
})
const { hexToBytes, toBytes } = await import('./src/utils/encoding/toBytes.ts')
for (const hex of ['0x1', '0xabc', '0x12'])
  console.log(hex, [...hexToBytes(hex)], [...hexToBytes(hex, { size: 2 })], [...toBytes(hex, { size: 2 })])
JS

Link to Minimal Reproducible Example

Exact implementation at the reproduced commit; standalone reproducer is included above and requires no RPC.

Anything else?

The implementation currently right-pads before normalizing odd nibble counts. A small local patch normalizes the leading nibble first, with colocated regression coverage for both hexToBytes and toBytes, a documentation example, and a patch changeset. The full existing toBytes.test.ts file passes offline (17 tests) using an isolated configuration and the already available Vitest 3.2.4; the repository requests Vitest 4.1.12, so this is not a claim that the standard project harness or full CI passed. A separate real-source Node smoke passed 18 assertions including fromHex(..., { to: 'bytes' }), overflow and invalid-input controls. Full typecheck/lint were not run; an isolated dependency install failed due to disk exhaustion and was removed.

Per AGENTS.md's maintainer-gated commit rule, the patch is local and unstaged: no commit, push, or PR has been made. Would a maintainer approve staging/committing this focused fix and opening a PR, or prefer different odd-input behavior?

Disclosure: this investigation and local patch were AI-assisted, using the authorized human contributor account omerbek.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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