feat(legacy): Implement EIP-712 signing

pull/2029/head
Alisina Bahadori 2 years ago committed by matejcik
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"

@ -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

@ -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…
Cancel
Save