feat(eip712): device-driven field streaming for structured typed data - #39
Merged
Merged
Conversation
Five messages (1704-1708) that let the device drive an EIP-712 walk instead of
being handed the document.
The device asks for one type definition, or one leaf VALUE, at a time, and
hashes each value in the same pass that displays it. The host owns the
document; the device holds only the digest stack for the containers currently
open. A 10,000-element array costs the same RAM as a 2-element one, and there
is no document-size limit to raise later.
This replaces Ethereum712TypesValues, which shipped the whole thing as two
2048-byte JSON blobs and was withdrawn in 7.14.2 because its parser could not
guarantee the value on screen was the value being hashed. Here that is
structural rather than reviewed: a value is displayed and absorbed from the
same buffer in the same call, and each member_path is requested exactly once.
That last clause is not incidental. Trezor shipped this same protocol with a
hole until 2.12.0 -- nothing bound repeated answers for one path to each other,
so a host could answer the domain name one way for the summary screen and
another for the hashing pass. Requesting each path once closes it by
construction rather than by caching around it.
ONE deliberate divergence from Trezor and OneKey: arrays.
Both describe an array as a field whose entry_type is another EthereumFieldType
-- a self-referential message. Trezor can, because core is Python. OneKey does
it on nanopb by compiling that one field as a POINTER (PB_ENABLE_MALLOC) and
then flattening the pointer chain into a fixed pool to sever the recursion.
KeepKey's nanopb is static-allocation only, and a heap inside a signing device
is not a liability worth taking on for one field. So array nesting is FLATTENED
onto the wire the way Ledger describes it: data_type is always the LEAF type,
and array_levels carries the dimensions in written order, 0 for dynamic:
uint256 -> UINT, size=32, array_levels=[]
address[] -> ADDRESS, array_levels=[0]
Person[3] -> STRUCT, struct_name="Person", array_levels=[3]
int16[2][][4] -> INT, size=2, array_levels=[2,0,4]
Nothing EIP-712 permits is lost and the encoding is bounded, flat and
statically sized. Enum values still match Trezor's so a shared host keeps its
mapping; ARRAY is reserved and never sent.
Values arrive as raw big-endian bytes of the declared width, not JSON. That
deletes the whole decimal-parsing step from the device -- and with it the
64-bit integer ceiling that made the old path refuse an unlimited approval,
which is the most common permit there is.
Validated with protoc 3.5.1 in kktech/firmware:v8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five messages (1704–1708) that let the device drive an EIP-712 walk instead of being handed the document.
The device asks for one type definition, or one leaf value, at a time, and hashes each value in the same pass that displays it. The host owns the document; the device holds only the digest stack for the containers currently open. A 10,000-element array costs the same RAM as a 2-element one.
This replaces
Ethereum712TypesValues, which shipped the whole thing as two 2048-byte JSON blobs and was withdrawn in 7.14.2 because its parser could not guarantee the value on screen was the value being hashed. Here that is structural rather than reviewed — a value is displayed and absorbed from the same buffer in the same call, and eachmember_pathis requested exactly once.That last clause is not incidental: Trezor shipped this same protocol with a hole until 2.12.0 — nothing bound repeated answers for one path together, so a host could answer the domain name one way for the summary screen and another for the hashing pass.
One deliberate divergence: arrays
Trezor and OneKey describe an array as a field whose
entry_typeis anotherEthereumFieldType— a self-referential message. Trezor can, because core is Python. OneKey does it on nanopb by compiling that field as a pointer (PB_ENABLE_MALLOC) then flattening the chain into a pool to sever the recursion.KeepKey's nanopb is static-allocation only, and a heap inside a signing device isn't a liability worth taking on for one field. So nesting is flattened, the way Ledger describes it:
Nothing EIP-712 permits is lost. Enum values still match Trezor's so a shared host keeps its mapping.
Values arrive as raw big-endian bytes, not JSON — deleting the decimal-parsing step and with it the 64-bit integer ceiling that made the old path refuse an unlimited approval, the most common permit there is.
Validated with protoc 3.5.1 in
kktech/firmware:v8.