Add EIP 712 signing v0

This commit is contained in:
BTChip github
2020-09-26 15:49:36 +02:00
committed by TamtamHero
parent c308b55535
commit 27c34e271a
10 changed files with 356 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
Ethereum application : Common Technical Specifications
=======================================================
Ledger Firmware Team <hello@ledger.fr>
Application version 1.1.10 - 4th of February 2019
Application version 1.5.0 - 25th of September 2020
## 1.0
- Initial release
@@ -16,6 +16,9 @@ Application version 1.1.10 - 4th of February 2019
## 1.1.10
- Add PROVIDE ERC 20 TOKEN INFORMATION
## 1.5.0
- Add SIGN ETH EIP 712
## About
This application describes the APDU messages interface to communicate with the Ethereum application.
@@ -268,6 +271,49 @@ None
| Application patch version | 01
|==============================================================================================================================
### SIGN ETH EIP 712
#### Description
This command signs an Ethereum message following the EIP 712 specification (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)
For implementation version 0, the domain hash and message hash are provided to the device, which displays them and returns the signature
This command has been supported since firmware version 1.5.0
#### Coding
'Command'
[width="80%"]
|==============================================================================================================================
| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le*
| E0 | 0C | 00
| implementation version : 00 | variable | variable
|==============================================================================================================================
'Input data'
[width="80%"]
|==============================================================================================================================
| *Description* | *Length*
| Number of BIP 32 derivations to perform (max 10) | 1
| First derivation index (big endian) | 4
| ... | 4
| Last derivation index (big endian) | 4
| Domain hash | 32
| Message hash | 32
|==============================================================================================================================
'Output data'
[width="80%"]
|==============================================================================================================================
| *Description* | *Length*
| v | 1
| r | 32
| s | 32
|==============================================================================================================================
## Transport protocol

98
examples/signMessageEIP711v0.py Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""
*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
"""
from __future__ import print_function
from ledgerblue.comm import getDongle
from ledgerblue.commException import CommException
from decimal import Decimal
from Crypto.Hash import keccak
from eth_keys import KeyAPI
import argparse
import struct
import binascii
# Define here Chain_ID
CHAIN_ID = 0
# Magic define
SIGN_MAGIC = b'\x19\x01'
def parse_bip32_path(path):
if len(path) == 0:
return b""
result = b""
elements = path.split('/')
for pathElement in elements:
element = pathElement.split('\'')
if len(element) == 1:
result = result + struct.pack(">I", int(element[0]))
else:
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
return result
parser = argparse.ArgumentParser()
parser.add_argument('--path', help="BIP 32 path to sign with")
parser.add_argument('--domainHash', help="Domain Hash (hex)", required=True)
parser.add_argument('--messageHash', help='Message Hash (hex)', required=True)
args = parser.parse_args()
if args.path == None:
args.path = "44'/60'"
domainHash = binascii.unhexlify(args.domainHash)
messageHash = binascii.unhexlify(args.messageHash)
encodedTx = domainHash + messageHash
donglePath = parse_bip32_path(args.path)
apdu = bytearray.fromhex("e00c0000")
apdu.append(len(donglePath) + 1 + len(encodedTx))
apdu.append(len(donglePath) // 4)
apdu += donglePath + encodedTx
dongle = getDongle(True)
result = dongle.exchange(bytes(apdu))
v = int(result[0])
# Compute parity
if (CHAIN_ID*2 + 35) + 1 > 255:
ecc_parity = v - ((CHAIN_ID*2 + 35) % 256)
else:
ecc_parity = (v + 1) % 2
v = "%02X" % ecc_parity
r = binascii.hexlify(result[1:1 + 32]).decode()
s = binascii.hexlify(result[1 + 32: 1 + 32 + 32]).decode()
msg_to_sign = SIGN_MAGIC + domainHash + messageHash
hash = keccak.new(digest_bits=256, data=msg_to_sign).digest()
signature = KeyAPI.Signature(vrs=(int(v, 16), int(r, 16), int(s, 16)))
pubkey = KeyAPI.PublicKey.recover_from_msg_hash(hash, signature)
print("[INFO] Hash is: 0x", binascii.hexlify(hash).decode(), sep='');
print('{')
print(' "address": "', pubkey.to_address(), '",', sep='')
print(' "domain hash": "', binascii.hexlify(domainHash),'",', sep='')
print(' "message hash": "', binascii.hexlify(messageHash),'",', sep='')
print(' "sig": "', signature, '",', sep = '')
print(' "version": "3"')
print(' "signed": "ledger"')
print('}')

View File

@@ -10,6 +10,7 @@
#define INS_GET_APP_CONFIGURATION 0x06
#define INS_SIGN_PERSONAL_MESSAGE 0x08
#define INS_PROVIDE_ERC20_TOKEN_INFORMATION 0x0A
#define INS_SIGN_EIP_712_MESSAGE 0x0C
#define P1_CONFIRM 0x01
#define P1_NON_CONFIRM 0x00
#define P2_NO_CHAINCODE 0x00

View File

@@ -544,6 +544,11 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx);
break;
case INS_SIGN_EIP_712_MESSAGE:
os_memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
handleSignEIP712Message(G_io_apdu_buffer[OFFSET_P1], G_io_apdu_buffer[OFFSET_P2], G_io_apdu_buffer + OFFSET_CDATA, G_io_apdu_buffer[OFFSET_LC], flags, tx);
break;
#if 0
case 0xFF: // return to dashboard
goto return_to_dashboard;

View File

@@ -71,10 +71,19 @@ typedef struct messageSigningContext_t {
uint32_t remainingLength;
} messageSigningContext_t;
typedef struct messageSigningContext712_t {
uint8_t pathLength;
uint32_t bip32Path[MAX_BIP32_PATH];
uint8_t domainHash[32];
uint8_t messageHash[32];
} messageSigningContext712_t;
typedef union {
publicKeyContext_t publicKeyContext;
transactionContext_t transactionContext;
messageSigningContext_t messageSigningContext;
messageSigningContext712_t messageSigningContext712;
} tmpCtx_t;
typedef union {

View File

@@ -10,6 +10,8 @@ unsigned int io_seproxyhal_touch_signMessage_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_signMessage_cancel(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_data_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_data_cancel(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_signMessage712_v0_ok(const bagl_element_t *e);
unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(const bagl_element_t *e);
void ui_idle(void);

View File

@@ -20,10 +20,12 @@ extern const ux_flow_step_t * const ux_approval_allowance_flow [];
extern const ux_flow_step_t * const ux_sign_flow [];
extern const ux_flow_step_t * const ux_display_stark_public_flow [];
extern const ux_flow_step_t * const ux_sign_712_v0_flow [];
#ifdef HAVE_STARKWARE
extern const ux_flow_step_t * const ux_display_stark_public_flow [];
extern const ux_flow_step_t * const ux_stark_limit_order_flow [];
extern const ux_flow_step_t * const ux_stark_transfer_flow [];

View File

@@ -0,0 +1,64 @@
#include "shared_context.h"
#include "apdu_constants.h"
#include "utils.h"
#ifdef TARGET_BLUE
#include "ui_blue.h"
#endif
#ifdef HAVE_UX_FLOW
#include "ui_flow.h"
#endif
static const char const SIGN_MAGIC[] = "\x19"
"Ethereum Signed Message:\n";
void handleSignEIP712Message(uint8_t p1, uint8_t p2, uint8_t *workBuffer, uint16_t dataLength, unsigned int *flags, unsigned int *tx) {
uint8_t i;
UNUSED(tx);
if ((p1 != 00) || (p2 != 00)) {
THROW(0x6B00);
}
if (appState != APP_STATE_IDLE) {
reset_app_context();
}
if (dataLength < 1) {
PRINTF("Invalid data\n");
THROW(0x6a80);
}
tmpCtx.messageSigningContext712.pathLength = workBuffer[0];
if ((tmpCtx.messageSigningContext712.pathLength < 0x01) ||
(tmpCtx.messageSigningContext712.pathLength > MAX_BIP32_PATH)) {
PRINTF("Invalid path\n");
THROW(0x6a80);
}
workBuffer++;
dataLength--;
for (i = 0; i < tmpCtx.messageSigningContext712.pathLength; i++) {
if (dataLength < 4) {
PRINTF("Invalid data\n");
THROW(0x6a80);
}
tmpCtx.messageSigningContext712.bip32Path[i] = U4BE(workBuffer, 0);
workBuffer += 4;
dataLength -= 4;
}
if (dataLength < 32 + 32) {
PRINTF("Invalid data\n");
THROW(0x6a80);
}
memmove(tmpCtx.messageSigningContext712.domainHash, workBuffer, 32);
memmove(tmpCtx.messageSigningContext712.messageHash, workBuffer + 32, 32);
#ifdef NO_CONSENT
io_seproxyhal_touch_signMessage_ok(NULL);
#else //NO_CONSENT
#if defined(TARGET_BLUE)
// TODO implement
ui_approval_message_sign_blue_init();
#else
ux_flow_init(0, ux_sign_712_v0_flow, NULL);
#endif // #if TARGET_ID
#endif // NO_CONSENT
*flags |= IO_ASYNCH_REPLY;
}

View File

@@ -0,0 +1,64 @@
#include "shared_context.h"
#include "ui_callbacks.h"
static const uint8_t const EIP_712_MAGIC[] = { 0x19, 0x01 };
unsigned int io_seproxyhal_touch_signMessage712_v0_ok(const bagl_element_t *e) {
uint8_t privateKeyData[32];
uint8_t hash[32];
uint8_t signature[100];
uint8_t signatureLength;
cx_ecfp_private_key_t privateKey;
uint32_t tx = 0;
io_seproxyhal_io_heartbeat();
cx_keccak_init(&global_sha3, 256);
cx_hash((cx_hash_t *)&global_sha3, 0, (uint8_t*)EIP_712_MAGIC, sizeof(EIP_712_MAGIC), NULL, 0);
cx_hash((cx_hash_t *)&global_sha3, 0, tmpCtx.messageSigningContext712.domainHash,
sizeof(tmpCtx.messageSigningContext712.domainHash), NULL, 0);
cx_hash((cx_hash_t *)&global_sha3, CX_LAST, tmpCtx.messageSigningContext712.messageHash,
sizeof(tmpCtx.messageSigningContext712.messageHash), hash, sizeof(hash));
PRINTF("EIP712 hash to sign %.*H\n", 32, hash);
io_seproxyhal_io_heartbeat();
os_perso_derive_node_bip32(
CX_CURVE_256K1, tmpCtx.messageSigningContext712.bip32Path,
tmpCtx.messageSigningContext712.pathLength, privateKeyData, NULL);
io_seproxyhal_io_heartbeat();
cx_ecfp_init_private_key(CX_CURVE_256K1, privateKeyData, 32, &privateKey);
os_memset(privateKeyData, 0, sizeof(privateKeyData));
unsigned int info = 0;
io_seproxyhal_io_heartbeat();
signatureLength =
cx_ecdsa_sign(&privateKey, CX_RND_RFC6979 | CX_LAST, CX_SHA256,
hash,
sizeof(hash), signature, sizeof(signature), &info);
os_memset(&privateKey, 0, sizeof(privateKey));
G_io_apdu_buffer[0] = 27;
if (info & CX_ECCINFO_PARITY_ODD) {
G_io_apdu_buffer[0]++;
}
if (info & CX_ECCINFO_xGTn) {
G_io_apdu_buffer[0] += 2;
}
format_signature_out(signature);
tx = 65;
G_io_apdu_buffer[tx++] = 0x90;
G_io_apdu_buffer[tx++] = 0x00;
reset_app_context();
// Send back the response, do not restart the event loop
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, tx);
// Display back the original UX
ui_idle();
return 0; // do not redraw the widget
}
unsigned int io_seproxyhal_touch_signMessage712_v0_cancel(const bagl_element_t *e) {
reset_app_context();
G_io_apdu_buffer[0] = 0x69;
G_io_apdu_buffer[1] = 0x85;
// Send back the response, do not restart the event loop
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2);
// Display back the original UX
ui_idle();
return 0; // do not redraw the widget
}

View File

@@ -0,0 +1,63 @@
#include "shared_context.h"
#include "ui_callbacks.h"
void prepare_domain_hash_v0() {
snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.domainHash);
}
void prepare_message_hash_v0() {
snprintf(strings.tmp.tmp, 70, "0x%.*H", 32, tmpCtx.messageSigningContext712.messageHash);
}
UX_FLOW_DEF_NOCB(
ux_sign_712_v0_flow_1_step,
pnn,
{
&C_icon_certificate,
"Sign",
"typed message",
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_2_step,
bnnn_paging,
prepare_domain_hash_v0(),
{
.title = "Domain hash",
.text = strings.tmp.tmp,
});
UX_STEP_NOCB_INIT(
ux_sign_712_v0_flow_3_step,
bnnn_paging,
prepare_message_hash_v0(),
{
.title = "Message hash",
.text = strings.tmp.tmp,
});
UX_FLOW_DEF_VALID(
ux_sign_712_v0_flow_4_step,
pbb,
io_seproxyhal_touch_signMessage712_v0_ok(NULL),
{
&C_icon_validate_14,
"Sign",
"message",
});
UX_FLOW_DEF_VALID(
ux_sign_712_v0_flow_5_step,
pbb,
io_seproxyhal_touch_signMessage712_v0_cancel(NULL),
{
&C_icon_crossmark,
"Cancel",
"signature",
});
const ux_flow_step_t * const ux_sign_712_v0_flow [] = {
&ux_sign_712_v0_flow_1_step,
&ux_sign_712_v0_flow_2_step,
&ux_sign_712_v0_flow_3_step,
&ux_sign_712_v0_flow_4_step,
&ux_sign_712_v0_flow_5_step,
FLOW_END_STEP,
};