Skip to content

Commit 07dc817

Browse files
authored
Merge pull request #25 from blooo-io/fix/LDG-493--app-fix-withdrawal-signing
fix(handler/withdraw): correct issue in `compute_tx_hash`
2 parents 67d9467 + 3b072b1 commit 07dc817

File tree

7 files changed

+73
-17
lines changed

7 files changed

+73
-17
lines changed

Makefile

+3-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ PATH_APP_LOAD_PARAMS = ""
4747
# Application version
4848
APPVERSION_M = 1
4949
APPVERSION_N = 1
50-
APPVERSION_P = 3
50+
APPVERSION_P = 4
5151
APPVERSION_SUFFIX = # if not empty, appended at the end. Do not add a dash.
5252

5353
ifeq ($(APPVERSION_SUFFIX),)
@@ -163,7 +163,8 @@ DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300
163163

164164
# DEFINES += HAVE_PRINT_STACK_POINTER
165165

166-
DEBUG = 0 # 0 for production, 1 for debug
166+
# 0 for production, 1 for debug
167+
DEBUG = 0
167168
ifeq ($(DEBUG),10)
168169
$(warning Using semihosted PRINTF. Only run with speculos!)
169170
DEFINES += HAVE_PRINTF HAVE_SEMIHOSTED_PRINTF PRINTF=semihosted_printf

src/handler/withdraw.c

+67-12
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,22 @@ static unsigned char const BSM_SIGN_MAGIC[] = {'\x18', 'B', 'i', 't', 'c', 'o',
6060
#error "COIN_VARIANT is not defined"
6161
#elif COIN_VARIANT == COIN_VARIANT_ACRE
6262
// Mainnet hash
63-
static const uint8_t domain_separator_hash[32] = {
64-
0xc4, 0x86, 0x40, 0x56, 0xe2, 0x10, 0x22, 0x91, 0x3a, 0x49, 0x88, 0x4b, 0xa9, 0xfb, 0x40, 0x35,
65-
0x36, 0x4d, 0x5c, 0x2a, 0xb8, 0xb4, 0x0f, 0x03, 0x05, 0x58, 0x3a, 0xe4, 0x19, 0xc7, 0x2f, 0x86};
63+
static const uint8_t abi_encoded_chain_id[32] = {
64+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
6666
#elif COIN_VARIANT == COIN_VARIANT_ACRE_TESTNET
6767
// Testnet hash
68-
static const uint8_t domain_separator_hash[32] = {
69-
0x19, 0x2e, 0xfd, 0x34, 0x00, 0xda, 0x07, 0xca, 0xf5, 0xbd, 0x41, 0x8f, 0xdd, 0xfc, 0x07, 0x7a,
70-
0x97, 0x88, 0x1b, 0x26, 0xb7, 0xca, 0x63, 0x8d, 0xda, 0x2d, 0x52, 0x3f, 0xde, 0xf4, 0xf8, 0x4c};
68+
static const uint8_t abi_encoded_chain_id[32] = {
69+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
70+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x77};
7171
#else
7272
#error "Unsupported COIN_VARIANT value"
7373
#endif
7474

75+
static const uint8_t domain_separator_typehash[32] = {
76+
0x47, 0xe7, 0x95, 0x34, 0xa2, 0x45, 0x95, 0x2e, 0x8b, 0x16, 0x89, 0x3a, 0x33, 0x6b, 0x85, 0xa3,
77+
0xd9, 0xea, 0x9f, 0xa8, 0xc5, 0x73, 0xf3, 0xd8, 0x03, 0xaf, 0xb9, 0x2a, 0x79, 0x46, 0x92, 0x18};
78+
7579
static const uint8_t safe_tx_typehash[32] = {
7680
0xbb, 0x83, 0x10, 0xd4, 0x86, 0x36, 0x8d, 0xb6, 0xbd, 0x6f, 0x84, 0x94, 0x02, 0xfd, 0xd7, 0x3a,
7781
0xd5, 0x3d, 0x31, 0x6b, 0x5a, 0x4b, 0x26, 0x44, 0xad, 0x6e, 0xfe, 0x0f, 0x94, 0x12, 0x86, 0xd8};
@@ -427,11 +431,11 @@ void fetch_and_hash_tx_data(dispatcher_context_t* dc,
427431
}
428432
// Finalize the hash and store the result in output_hash
429433
CX_THROW(cx_hash_no_throw((cx_hash_t*) hash_context,
430-
CX_LAST, // final block mode
431-
NULL, // no more input
432-
0, // no more input length
433-
output_buffer, // output hash buffer
434-
32)); // output hash length (32 bytes)
434+
CX_LAST, // final block mode
435+
NULL, // no more input
436+
0, // no more input length
437+
output_buffer, // output hash buffer
438+
KECCAK_256_HASH_SIZE)); // output hash length (32 bytes)
435439
}
436440

437441
/**
@@ -514,6 +518,55 @@ bool fetch_and_abi_encode_tx_fields(dispatcher_context_t* dc,
514518
return true;
515519
}
516520

521+
/**
522+
* @brief Computes the domain separator hash according to EIP-712 specification.
523+
*
524+
* This function computes the domain separator hash by combining and hashing:
525+
* 1. The ABI-encoded EIP-712 domain separator typehash
526+
* 2. The ABI-encoded chain ID
527+
* 3. The ABI-encoded verifying contract address
528+
*
529+
* The function follows the EIP-712 specification for structured data hashing
530+
* and signing. The computed hash is used as part of the transaction signing
531+
* process.
532+
*
533+
* @param dc Pointer to the dispatcher context used for operations
534+
* @param data_merkle_root Pointer to the Merkle root of the transaction data
535+
* @param n_chunks Number of chunks in the Merkle tree
536+
* @param output_buffer Buffer to store the computed domain separator hash (32 bytes)
537+
*/
538+
void compute_domain_separator_hash(dispatcher_context_t* dc,
539+
uint8_t* data_merkle_root,
540+
size_t n_chunks,
541+
uint8_t* output_buffer) {
542+
cx_sha3_t hash_context;
543+
CX_THROW(cx_keccak_init_no_throw(&hash_context, 256));
544+
// Add the EIP712 domain separator typehash to the hash context (it is already abi-encoded)
545+
CX_THROW(cx_hash_no_throw((cx_hash_t*) &hash_context,
546+
0,
547+
domain_separator_typehash,
548+
sizeof(domain_separator_typehash),
549+
NULL,
550+
0));
551+
552+
// add the abi encoded chainId to the hash context
553+
CX_THROW(cx_hash_no_throw((cx_hash_t*) &hash_context,
554+
0,
555+
abi_encoded_chain_id,
556+
sizeof(abi_encoded_chain_id),
557+
NULL,
558+
0));
559+
// Add the verifying contract address to the hash context (it is already abi-encoded)
560+
fetch_and_add_chunk_to_hash(dc, data_merkle_root, n_chunks, &hash_context, 7, 0, 32);
561+
// Compute the final hash
562+
CX_THROW(cx_hash_no_throw((cx_hash_t*) &hash_context,
563+
CX_LAST,
564+
NULL,
565+
0,
566+
output_buffer,
567+
KECCAK_256_HASH_SIZE));
568+
}
569+
517570
/**
518571
* @brief Computes the transaction hash using Keccak-256.
519572
*
@@ -562,7 +615,9 @@ void compute_tx_hash(dispatcher_context_t* dc,
562615
sizeof(abi_encoded_tx_fields),
563616
keccak_of_abi_encoded_tx_fields,
564617
sizeof(keccak_of_abi_encoded_tx_fields)));
565-
618+
// Compute domain_separator_hash
619+
uint8_t domain_separator_hash[KECCAK_256_HASH_SIZE];
620+
compute_domain_separator_hash(dc, data_merkle_root, n_chunks, domain_separator_hash);
566621
// Abi.encodePacked
567622
// 2 bytes (0x1901) + 2 keccak256 hashes
568623
u_int8_t abi_encode_packed[2 + (KECCAK_256_HASH_SIZE * 2)] = {0x19, 0x01};
-108 Bytes
Loading
2 Bytes
Loading
2 Bytes
Loading
-99 Bytes
Loading

tests/test_sign_withdraw.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ledger_bitcoin.withdraw import AcreWithdrawalData
1010
from .instructions import withdrawal_instruction_approve, withdrawal_instruction_reject
1111

12-
12+
#
1313
def test_sign_withdraw(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str):
1414
data = AcreWithdrawalData(
1515
to= "0xc14972DC5a4443E4f5e89E3655BE48Ee95A795aB",
@@ -22,12 +22,12 @@ def test_sign_withdraw(navigator: Navigator, firmware: Firmware, client: RaggerC
2222
gasToken= "0x0000000000000000000000000000000000000000",
2323
refundReceiver= "0x0000000000000000000000000000000000000000",
2424
nonce= "0x8",
25-
)
25+
) # tx_hash: 0xed3a7a50496ccca6173ddd9a1ad786d61ea737b70541887ab2c2fadcbe36eeaf
2626
path = "m/44'/0'/0'/0/0"
2727
result = client.sign_withdraw(data, path, navigator,
2828
instructions=withdrawal_instruction_approve(firmware),
2929
testname=test_name)
30-
assert result == "H7C63V1KXzqImWjq6Bwy64m1cmdue4lFHEcdo9D4iHOKcKa9ddY7aTeCdq0n/31djYwv486DzZaHaOgtDuNuwZc="
30+
assert result == "IEZDsLh2JweulEOzl2dgLrYvtIqWUW8gFeYdMLAYSk7PeH72uN0JQJGVCYZSpJ5HYRaKZrmSBiG3Ypl+oelXEAM="
3131

3232
def test_sign_withdraw_wrong_address(navigator: Navigator, firmware: Firmware, client: RaggerClient, test_name: str):
3333
data = AcreWithdrawalData(

0 commit comments

Comments
 (0)