mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-05-07 09:29:04 +00:00
feat(legacy): Implement EIP-712 signing
This commit is contained in:
parent
a9a6495c7e
commit
77ab865386
@ -87,12 +87,3 @@ message EthereumTypedDataValueAck {
|
||||
// * array types: number of elements, encoded as uint16.
|
||||
// * struct types: undefined, Trezor will not query a struct field.
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed typed data
|
||||
* @end
|
||||
*/
|
||||
message EthereumTypedDataSignature {
|
||||
required bytes signature = 1; // signature of the typed data
|
||||
required string address = 2; // address used to sign the typed data
|
||||
}
|
||||
|
@ -149,3 +149,24 @@ message EthereumVerifyMessage {
|
||||
required bytes message = 3; // message to verify
|
||||
required string address = 4; // address to verify
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign hash of typed data
|
||||
* @start
|
||||
* @next EthereumTypedDataSignature
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTypedHash {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes domain_separator_hash = 2; // Hash of domainSeparator of typed data to be signed
|
||||
required bytes message_hash = 3; // Hash of the data of typed data to be signed
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed typed data
|
||||
* @end
|
||||
*/
|
||||
message EthereumTypedDataSignature {
|
||||
required bytes signature = 1; // signature of the typed data
|
||||
required string address = 2; // address used to sign the typed data
|
||||
}
|
||||
|
@ -191,6 +191,7 @@ enum MessageType {
|
||||
MessageType_EthereumTypedDataValueRequest = 467 [(wire_out) = true];
|
||||
MessageType_EthereumTypedDataValueAck = 468 [(wire_in) = true];
|
||||
MessageType_EthereumTypedDataSignature = 469 [(wire_out) = true];
|
||||
MessageType_EthereumSignTypedHash = 470 [(wire_in) = true];
|
||||
|
||||
// NEM
|
||||
MessageType_NEMGetAddress = 67 [(wire_in) = true];
|
||||
|
@ -103,6 +103,7 @@ if not utils.BITCOIN_ONLY:
|
||||
EthereumTypedDataValueRequest = 467
|
||||
EthereumTypedDataValueAck = 468
|
||||
EthereumTypedDataSignature = 469
|
||||
EthereumSignTypedHash = 470
|
||||
NEMGetAddress = 67
|
||||
NEMAddress = 68
|
||||
NEMSignTx = 69
|
||||
|
@ -108,6 +108,7 @@ if TYPE_CHECKING:
|
||||
EthereumTypedDataValueRequest = 467
|
||||
EthereumTypedDataValueAck = 468
|
||||
EthereumTypedDataSignature = 469
|
||||
EthereumSignTypedHash = 470
|
||||
NEMGetAddress = 67
|
||||
NEMAddress = 68
|
||||
NEMSignTx = 69
|
||||
|
@ -3235,6 +3235,40 @@ if TYPE_CHECKING:
|
||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumVerifyMessage"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EthereumSignTypedHash(protobuf.MessageType):
|
||||
address_n: "list[int]"
|
||||
domain_separator_hash: "bytes"
|
||||
message_hash: "bytes"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
domain_separator_hash: "bytes",
|
||||
message_hash: "bytes",
|
||||
address_n: "list[int] | None" = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumSignTypedHash"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EthereumTypedDataSignature(protobuf.MessageType):
|
||||
signature: "bytes"
|
||||
address: "str"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
signature: "bytes",
|
||||
address: "str",
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumTypedDataSignature"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EthereumAccessList(protobuf.MessageType):
|
||||
address: "str"
|
||||
storage_keys: "list[bytes]"
|
||||
@ -3325,22 +3359,6 @@ if TYPE_CHECKING:
|
||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumTypedDataValueAck"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EthereumTypedDataSignature(protobuf.MessageType):
|
||||
signature: "bytes"
|
||||
address: "str"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
signature: "bytes",
|
||||
address: "str",
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["EthereumTypedDataSignature"]:
|
||||
return isinstance(msg, cls)
|
||||
|
||||
class EthereumStructMember(protobuf.MessageType):
|
||||
type: "EthereumFieldType"
|
||||
name: "str"
|
||||
|
1
legacy/firmware/.changelog.d/131.added
Normal file
1
legacy/firmware/.changelog.d/131.added
Normal file
@ -0,0 +1 @@
|
||||
Support for blindly signing EIP-712 data.
|
@ -976,6 +976,36 @@ int ethereum_message_verify(const EthereumVerifyMessage *msg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ethereum_typed_hash(const uint8_t domain_separator_hash[32],
|
||||
const uint8_t message_hash[32],
|
||||
uint8_t hash[32]) {
|
||||
struct SHA3_CTX ctx = {0};
|
||||
sha3_256_Init(&ctx);
|
||||
sha3_Update(&ctx, (const uint8_t *)"\x19\x01", 2);
|
||||
sha3_Update(&ctx, domain_separator_hash, 32);
|
||||
sha3_Update(&ctx, message_hash, 32);
|
||||
keccak_Final(&ctx, hash);
|
||||
}
|
||||
|
||||
void ethereum_typed_hash_sign(const EthereumSignTypedHash *msg,
|
||||
const HDNode *node,
|
||||
EthereumTypedDataSignature *resp) {
|
||||
uint8_t hash[32] = {0};
|
||||
ethereum_typed_hash(msg->domain_separator_hash.bytes, msg->message_hash.bytes,
|
||||
hash);
|
||||
|
||||
uint8_t v = 0;
|
||||
if (ecdsa_sign_digest(&secp256k1, node->private_key, hash,
|
||||
resp->signature.bytes, &v, ethereum_is_canonic) != 0) {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
resp->signature.bytes[64] = 27 + v;
|
||||
resp->signature.size = 65;
|
||||
msg_write(MessageType_MessageType_EthereumTypedDataSignature, resp);
|
||||
}
|
||||
|
||||
bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]) {
|
||||
memzero(pubkeyhash, 20);
|
||||
size_t len = strlen(address);
|
||||
|
@ -34,6 +34,9 @@ void ethereum_signing_txack(const EthereumTxAck *msg);
|
||||
void ethereum_message_sign(const EthereumSignMessage *msg, const HDNode *node,
|
||||
EthereumMessageSignature *resp);
|
||||
int ethereum_message_verify(const EthereumVerifyMessage *msg);
|
||||
void ethereum_typed_hash_sign(const EthereumSignTypedHash *msg,
|
||||
const HDNode *node,
|
||||
EthereumTypedDataSignature *resp);
|
||||
bool ethereum_parse(const char *address, uint8_t pubkeyhash[20]);
|
||||
|
||||
#endif
|
||||
|
@ -104,6 +104,7 @@ void fsm_msgEthereumSignTxEIP1559(const EthereumSignTxEIP1559 *msg);
|
||||
void fsm_msgEthereumTxAck(const EthereumTxAck *msg);
|
||||
void fsm_msgEthereumSignMessage(const EthereumSignMessage *msg);
|
||||
void fsm_msgEthereumVerifyMessage(const EthereumVerifyMessage *msg);
|
||||
void fsm_msgEthereumSignTypedHash(const EthereumSignTypedHash *msg);
|
||||
|
||||
// nem
|
||||
void fsm_msgNEMGetAddress(
|
||||
|
@ -212,3 +212,64 @@ void fsm_msgEthereumVerifyMessage(const EthereumVerifyMessage *msg) {
|
||||
|
||||
layoutHome();
|
||||
}
|
||||
|
||||
void fsm_msgEthereumSignTypedHash(const EthereumSignTypedHash *msg) {
|
||||
RESP_INIT(EthereumTypedDataSignature);
|
||||
|
||||
CHECK_INITIALIZED
|
||||
|
||||
CHECK_PIN
|
||||
|
||||
if (msg->domain_separator_hash.size != 32 || msg->message_hash.size != 32) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid hash length"));
|
||||
return;
|
||||
}
|
||||
|
||||
layoutDialogSwipe(&bmp_icon_warning, _("Abort"), _("Continue"), NULL,
|
||||
_("Unable to show"), _("EIP-712 data."), NULL,
|
||||
_("Sign at your own risk."), NULL, NULL);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
layoutHome();
|
||||
return;
|
||||
}
|
||||
|
||||
const HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n,
|
||||
msg->address_n_count, NULL);
|
||||
if (!node) return;
|
||||
|
||||
uint8_t pubkeyhash[20] = {0};
|
||||
if (!hdnode_get_ethereum_pubkeyhash(node, pubkeyhash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
resp->address[0] = '0';
|
||||
resp->address[1] = 'x';
|
||||
ethereum_address_checksum(pubkeyhash, resp->address + 2, false, 0);
|
||||
// ethereum_address_checksum adds trailing zero
|
||||
|
||||
layoutVerifyAddress(NULL, resp->address);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
layoutHome();
|
||||
return;
|
||||
}
|
||||
|
||||
layoutConfirmHash(&bmp_icon_warning, _("EIP-712 domain hash"),
|
||||
msg->domain_separator_hash.bytes, 32);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
layoutHome();
|
||||
return;
|
||||
}
|
||||
layoutConfirmHash(&bmp_icon_warning, _("EIP-712 message hash"),
|
||||
msg->message_hash.bytes, 32);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
layoutHome();
|
||||
return;
|
||||
}
|
||||
|
||||
ethereum_typed_hash_sign(msg, node, resp);
|
||||
layoutHome();
|
||||
}
|
||||
|
@ -1253,3 +1253,22 @@ void layoutConfirmSafetyChecks(SafetyCheckLevel safety_ckeck_level) {
|
||||
_("be unsafe?"), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void layoutConfirmHash(const BITMAP *icon, const char *description,
|
||||
const uint8_t *hash, uint32_t len) {
|
||||
const char **str = split_message_hex(hash, len);
|
||||
|
||||
layoutSwipe();
|
||||
oledClear();
|
||||
oledDrawBitmap(0, 0, icon);
|
||||
oledDrawString(20, 0 * 9, description, FONT_STANDARD);
|
||||
oledDrawString(20, 1 * 9, str[0], FONT_FIXED);
|
||||
oledDrawString(20, 2 * 9, str[1], FONT_FIXED);
|
||||
oledDrawString(20, 3 * 9, str[2], FONT_FIXED);
|
||||
oledDrawString(20, 4 * 9, str[3], FONT_FIXED);
|
||||
oledHLine(OLED_HEIGHT - 13);
|
||||
|
||||
layoutButtonNo(_("Cancel"), &bmp_btn_cancel);
|
||||
layoutButtonYes(_("Confirm"), &bmp_btn_confirm);
|
||||
oledRefresh();
|
||||
}
|
||||
|
@ -110,6 +110,9 @@ void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count,
|
||||
void layoutConfirmAutoLockDelay(uint32_t delay_ms);
|
||||
void layoutConfirmSafetyChecks(SafetyCheckLevel safety_checks_level);
|
||||
|
||||
void layoutConfirmHash(const BITMAP *icon, const char *description,
|
||||
const uint8_t *hash, uint32_t len);
|
||||
|
||||
const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen);
|
||||
const char **split_message_hex(const uint8_t *msg, uint32_t len);
|
||||
|
||||
|
@ -6,7 +6,9 @@ SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdPro
|
||||
DebugLinkRecordScreen DebugLinkReseedRandom DebugLinkShowText DebugLinkEraseSdCard DebugLinkWatchLayout \
|
||||
GetOwnershipProof OwnershipProof GetOwnershipId OwnershipId AuthorizeCoinJoin DoPreauthorized \
|
||||
CancelAuthorization DebugLinkLayout \
|
||||
TxAckInput TxAckOutput TxAckPrev EthereumSignTypedData EthereumTypedData
|
||||
TxAckInput TxAckOutput TxAckPrev \
|
||||
EthereumSignTypedData EthereumTypedDataStructRequest EthereumTypedDataStructAck \
|
||||
EthereumTypedDataValueRequest EthereumTypedDataValueAck
|
||||
|
||||
ifeq ($(BITCOIN_ONLY), 1)
|
||||
SKIPPED_MESSAGES += Ethereum NEM Stellar
|
||||
|
@ -34,6 +34,13 @@ EthereumVerifyMessage.message max_size:1024
|
||||
EthereumMessageSignature.address max_size:43
|
||||
EthereumMessageSignature.signature max_size:65
|
||||
|
||||
EthereumSignTypedHash.address_n max_count:8
|
||||
EthereumSignTypedHash.domain_separator_hash max_size:32
|
||||
EthereumSignTypedHash.message_hash max_size:32
|
||||
|
||||
EthereumTypedDataSignature.address max_size:43
|
||||
EthereumTypedDataSignature.signature max_size:65
|
||||
|
||||
EthereumGetAddress.address_n max_count:8
|
||||
EthereumGetPublicKey.address_n max_count:8
|
||||
|
||||
|
1
python/.changelog.d/1970.added
Normal file
1
python/.changelog.d/1970.added
Normal file
@ -0,0 +1 @@
|
||||
Add support for blind EIP-712 signing for Trezor One
|
@ -441,3 +441,29 @@ def verify_message(
|
||||
"""Verify message signed with Ethereum address."""
|
||||
signature_bytes = ethereum.decode_hex(signature)
|
||||
return ethereum.verify_message(client, address, signature_bytes, message)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("-n", "--address", required=True, help=PATH_HELP)
|
||||
@click.argument("domain_hash_hex")
|
||||
@click.argument("message_hash_hex")
|
||||
@with_client
|
||||
def sign_typed_data_hash(
|
||||
client: "TrezorClient", address: str, domain_hash_hex: str, message_hash_hex: str
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Sign hash of typed data (EIP-712) with Ethereum address.
|
||||
|
||||
For T1 backward compatibility.
|
||||
"""
|
||||
address_n = tools.parse_path(address)
|
||||
domain_hash = ethereum.decode_hex(domain_hash_hex)
|
||||
message_hash = ethereum.decode_hex(message_hash_hex)
|
||||
ret = ethereum.sign_typed_data_hash(client, address_n, domain_hash, message_hash)
|
||||
output = {
|
||||
"domain_hash": domain_hash_hex,
|
||||
"message_hash": message_hash_hex,
|
||||
"address": ret.address,
|
||||
"signature": f"0x{ret.signature.hex()}",
|
||||
}
|
||||
return output
|
||||
|
@ -357,3 +357,16 @@ def verify_message(
|
||||
except exceptions.TrezorFailure:
|
||||
return False
|
||||
return isinstance(resp, messages.Success)
|
||||
|
||||
|
||||
@expect(messages.EthereumTypedDataSignature)
|
||||
def sign_typed_data_hash(
|
||||
client: "TrezorClient", n: "Address", domain_hash: bytes, message_hash: bytes
|
||||
) -> "MessageType":
|
||||
return client.call(
|
||||
messages.EthereumSignTypedHash(
|
||||
address_n=n,
|
||||
domain_separator_hash=domain_hash,
|
||||
message_hash=message_hash,
|
||||
)
|
||||
)
|
||||
|
@ -129,6 +129,7 @@ class MessageType(IntEnum):
|
||||
EthereumTypedDataValueRequest = 467
|
||||
EthereumTypedDataValueAck = 468
|
||||
EthereumTypedDataSignature = 469
|
||||
EthereumSignTypedHash = 470
|
||||
NEMGetAddress = 67
|
||||
NEMAddress = 68
|
||||
NEMSignTx = 69
|
||||
@ -4455,23 +4456,6 @@ class EthereumTypedDataValueAck(protobuf.MessageType):
|
||||
self.value = value
|
||||
|
||||
|
||||
class EthereumTypedDataSignature(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 469
|
||||
FIELDS = {
|
||||
1: protobuf.Field("signature", "bytes", repeated=False, required=True),
|
||||
2: protobuf.Field("address", "string", repeated=False, required=True),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
signature: "bytes",
|
||||
address: "str",
|
||||
) -> None:
|
||||
self.signature = signature
|
||||
self.address = address
|
||||
|
||||
|
||||
class EthereumStructMember(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
FIELDS = {
|
||||
@ -4756,6 +4740,43 @@ class EthereumVerifyMessage(protobuf.MessageType):
|
||||
self.address = address
|
||||
|
||||
|
||||
class EthereumSignTypedHash(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 470
|
||||
FIELDS = {
|
||||
1: protobuf.Field("address_n", "uint32", repeated=True, required=False),
|
||||
2: protobuf.Field("domain_separator_hash", "bytes", repeated=False, required=True),
|
||||
3: protobuf.Field("message_hash", "bytes", repeated=False, required=True),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
domain_separator_hash: "bytes",
|
||||
message_hash: "bytes",
|
||||
address_n: Optional[Sequence["int"]] = None,
|
||||
) -> None:
|
||||
self.address_n: Sequence["int"] = address_n if address_n is not None else []
|
||||
self.domain_separator_hash = domain_separator_hash
|
||||
self.message_hash = message_hash
|
||||
|
||||
|
||||
class EthereumTypedDataSignature(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = 469
|
||||
FIELDS = {
|
||||
1: protobuf.Field("signature", "bytes", repeated=False, required=True),
|
||||
2: protobuf.Field("address", "string", repeated=False, required=True),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
signature: "bytes",
|
||||
address: "str",
|
||||
) -> None:
|
||||
self.signature = signature
|
||||
self.address = address
|
||||
|
||||
|
||||
class EthereumAccessList(protobuf.MessageType):
|
||||
MESSAGE_WIRE_TYPE = None
|
||||
FIELDS = {
|
||||
|
Loading…
Reference in New Issue
Block a user