Skip to content

eth: preserve signed integer encodings - #189

Merged
benma merged 1 commit into
BitBoxSwiss:masterfrom
benma-agent:benma-agent/eip712-signed-integers
Oct 7, 2026
Merged

benma merged 1 commit into
BitBoxSwiss:masterfrom
benma-agent:benma-agent/eip712-signed-integers

Conversation

@benma-agent

@benma-agent benma-agent commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

EIP-712 intN values are sent as signed big-endian two's-complement bytes.
Using big.Int.Bytes for a positive value loses its sign when the most
significant bit is set, so e.g. int16(128) is interpreted as -128.

Keep the existing two's-complement conversion. Prepend 0x00 to nonnegative
values when a sign byte is needed, and represent zero with one byte. Trim
redundant 0xff sign extension for negative values such as -128. Unsigned
integer encoding is unchanged.

Add sign-boundary and width tests, plus a simulator regression that verifies
the signature against an independently computed EIP-712 digest. The encoding
uses the existing firmware protocol and needs no firmware version gate.

Related PRs:

benma-agent added a commit to benma-agent/bitbox-api-ts that referenced this pull request Oct 2, 2026
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: BitBoxSwiss/bitbox02-api-go#189
- Rust: BitBoxSwiss/bitbox-api-rs#135
benma-agent added a commit to benma-agent/bitbox-api-rs that referenced this pull request Oct 2, 2026
EIP-712 intN values are sent as signed big-endian two's-complement bytes.
The JSON-number path strips the leading zero supplied by the signed BigInt
serializer, so e.g. int16(128) becomes -128 on the device. The string path
already preserves the sign correctly.

Use to_signed_bytes_be directly for JSON numbers as well, without stripping
its sign byte. Add boundary cases for positive values, negative values and
zero, checking that numeric and string inputs produce the same encoding.

Unsigned integer encoding and the firmware protocol are unchanged. No
firmware version gate is needed.

Related PRs:

- Go: BitBoxSwiss/bitbox02-api-go#189
- TypeScript: BitBoxSwiss/bitbox-api-ts#14
@benma-agent
benma-agent force-pushed the benma-agent/eip712-signed-integers branch from 4607105 to 6972319 Compare October 2, 2026 10:39
@benma-agent
benma-agent force-pushed the benma-agent/eip712-signed-integers branch from 6972319 to 81d1804 Compare October 2, 2026 10:49
@benma
benma requested a review from Tomasvrba October 2, 2026 10:52
@benma
benma marked this pull request as ready for review October 2, 2026 10:52

@Tomasvrba Tomasvrba left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with a small nit

Comment thread api/firmware/eth.go Outdated
if integer.Sign() >= 0 {
return integer.Bytes()
bytes := integer.Bytes()
// Add a sign byte if needed; encode zero as a single zero byte.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: probably don't need both of these comments:

// Return the shortest encoding that includes a sign bit, including one byte for zero.
// Add a sign byte if needed; encode zero as a single zero byte.

EIP-712 intN values are sent as signed big-endian two's-complement bytes.
Using big.Int.Bytes for a positive value loses its sign when the most
significant bit is set, so e.g. int16(128) is interpreted as -128.

Keep the existing two's-complement conversion. Prepend 0x00 to nonnegative
values when a sign byte is needed, and represent zero with one byte. Trim
redundant 0xff sign extension for negative values such as -128. Unsigned
integer encoding is unchanged.

Add sign-boundary and width tests, plus a simulator regression that verifies
the signature against an independently computed EIP-712 digest. The encoding
uses the existing firmware protocol and needs no firmware version gate.

Related PRs:

- Rust: BitBoxSwiss/bitbox-api-rs#135
- TypeScript: BitBoxSwiss/bitbox-api-ts#14
@benma-agent
benma-agent force-pushed the benma-agent/eip712-signed-integers branch from 81d1804 to a09b7d9 Compare October 7, 2026 16:47
@benma
benma merged commit c407d2b into BitBoxSwiss:master Oct 7, 2026
3 checks passed
benma-agent added a commit to benma-agent/bitbox-api-rs that referenced this pull request Oct 7, 2026
EIP-712 intN values are sent as signed big-endian two's-complement bytes.
The JSON-number path strips the leading zero supplied by the signed BigInt
serializer, so e.g. int16(128) becomes -128 on the device. The string path
already preserves the sign correctly.

Use to_signed_bytes_be directly for JSON numbers as well, without stripping
its sign byte. Add boundary cases for positive values, negative values and
zero, checking that numeric and string inputs produce the same encoding.

Add a simulator regression that signs int16(+128), int8(-128) and int8(0)
and checks the signature against an independently calculated EIP-712
digest. This covers the signed values through the public API and firmware.

Unsigned integer encoding and the firmware protocol are unchanged. No
firmware version gate is needed.

Related PRs:

- Go: BitBoxSwiss/bitbox02-api-go#189
- TypeScript: BitBoxSwiss/bitbox-api-ts#14
benma-agent added a commit to benma-agent/bitbox-api-ts that referenced this pull request Oct 7, 2026
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.

Add a simulator regression that signs int16(+128), int8(-128) and int8(0)
and checks the signature against an independently calculated EIP-712
digest. This covers the signed values through the public API and firmware.

Unsigned integer encoding and the firmware protocol are unchanged. No
firmware version gate is needed.

Related PRs:

- Go: BitBoxSwiss/bitbox02-api-go#189
- Rust: BitBoxSwiss/bitbox-api-rs#135
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants