From 6a616d45f9eb528b6b6bf1f485f55f4806a659f8 Mon Sep 17 00:00:00 2001 From: benma's agent Date: Fri, 2 Oct 2026 12:39:08 +0200 Subject: [PATCH] eth: preserve signed integer encodings EIP-712 intN values are sent as signed big-endian two's-complement bytes. The JavaScript-number path strips the leading zero supplied by the signed serializer, so e.g. int16(128) becomes -128 on the device. String and bigint inputs already preserve the sign correctly. Use bigIntToSignedBytesBE directly for all accepted input types, without stripping its sign byte. Add positive, negative and zero boundary cases that check identical encodings for number, string and bigint inputs. Unsigned integer encoding and the firmware protocol are unchanged. No firmware version gate is needed. Related PRs: - Go: https://github.com/BitBoxSwiss/bitbox02-api-go/pull/189 - Rust: https://github.com/BitBoxSwiss/bitbox-api-rs/pull/135 --- CHANGELOG.md | 1 + src/internal/eth/eip712.ts | 7 ------- test/eth-eip712.test.ts | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d348a85..04ec6c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Preserve the sign of positive EIP-712 signed integers supplied as JavaScript numbers. - Reset unfinished sessions when connecting to firmware v9.28.0 or newer, allowing host reconnects while the device remains powered on. diff --git a/src/internal/eth/eip712.ts b/src/internal/eth/eip712.ts index f985551..2357f9a 100644 --- a/src/internal/eth/eip712.ts +++ b/src/internal/eth/eip712.ts @@ -255,13 +255,6 @@ export function encodeValue( } case DataType.INT: { const n = parseIntValue(value); - if (typeof value === 'number') { - const bytes = bigIntToSignedBytesBE(n); - if (bytes.length > 0 && bytes[0] === 0x00) { - return bytes.subarray(1); - } - return bytes; - } return bigIntToSignedBytesBE(n); } case DataType.BOOL: { diff --git a/test/eth-eip712.test.ts b/test/eth-eip712.test.ts index 8057283..7eca418 100644 --- a/test/eth-eip712.test.ts +++ b/test/eth-eip712.test.ts @@ -163,6 +163,21 @@ describe('encodeValue', () => { ]); }); + it.each([ + [0, '00'], [1, '01'], [-1, 'ff'], + [127, '7f'], [128, '0080'], [129, '0081'], + [-127, '81'], [-128, '80'], [-129, 'ff7f'], + [255, '00ff'], [256, '0100'], [-255, 'ff01'], [-256, 'ff00'], + [32767, '7fff'], [32768, '008000'], + [-32768, '8000'], [-32769, 'ff7fff'], + [65535, '00ffff'], [65536, '010000'], + ] as const)('encodes signed %s consistently across input types', (value, expected) => { + const typ = parseType('int64', empty); + for (const input of [value, String(value), BigInt(value)]) { + expect(Buffer.from(encodeValue(typ, input)).toString('hex')).toBe(expected); + } + }); + it('encodes booleans', () => { expect(Array.from(encodeValue(parseType('bool', empty), false))).toEqual([0]); expect(Array.from(encodeValue(parseType('bool', empty), true))).toEqual([1]);