Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
repository: rootstock/hsm-integration-test
ref: 5.4.0.plus
ref: 6.0.0.plus
path: hsm-integration-test
ssh-key: ${{ secrets.HSM_INTEGRATION_TEST_SSH_KEY }}

Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
repository: rootstock/hsm-integration-test
ref: 5.4.0.plus
ref: 6.0.0.plus
path: hsm-integration-test
ssh-key: ${{ secrets.HSM_INTEGRATION_TEST_SSH_KEY }}

Expand Down
54 changes: 12 additions & 42 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,17 @@ For this operation, depending on the `keyId` parameter, there's two possible for
##### Authorized format

This format is only valid for the BTC and tBTC key ids (see corresponding section for
details). In addition, there are two different sub-formats that can be used for authorized
signing: legacy and segwit. These are shown in the following subsections.

###### Legacy BTC transactions

This sub-format is to be used when signing legacy (i.e., non-segwit) Bitcoin transaction
inputs.
details).

```
{
"command": "sign",
"keyId": "xxxxx", // (*)
"keyId": "xxxxx", // (i)
"message": {
"sighashComputationMode": "legacy",
"tx": "hhhh", // (**)
"input": i // (***)
"tx": "hhhh", // (ii)
"input": i, // (iii)
"witnessScript": "hhhh", // (iv)
"outpointValue": i // (v)
},
"auth": {
"receipt": "hhhh",
Expand All @@ -65,42 +60,17 @@ inputs.
}
```

###### Segwit BTC transactions

This sub-format is to be used when signing segwit Bitcoin transaction inputs.

```
{
"command": "sign",
"keyId": "xxxxx", // (*)
"message": {
"sighashComputationMode": "segwit",
"tx": "hhhh", // (**)
"input": i, // (***)
"witnessScript": "hhhh", // (x)
"outpointValue": i // (xx)
},
"auth": {
"receipt": "hhhh",
"receipt_merkle_proof": [
"hhhh", "hhhh", ..., "hhhh"
]
},
"version": 5
}
```

```
// (*) the given string must be the
// (i) the given string must be the
// BIP44 path of the key to use for signing.
// See valid BIP44 paths below (BTC and tBTC for this format).
// (**) the fully serialized BTC transaction
// that needs to be signed.
// (***) the input index of the BTC transaction
// (ii) the fully serialized BTC transaction
// that needs to be signed.
// (x) the witness script for the input that needs to be signed.
// (xx) the outpoint value (i.e., amount of the UTXO) for the input
// (iii) the input index of the BTC transaction
// that needs to be signed.
// (iv) the non-empty witness script for the input that needs to be signed.
// (v) the outpoint value (i.e., amount of the UTXO) for the input
// that needs to be signed; must be > 0 and <= 0xffffffffffffffff
//
// For the signing process to be successful, the computed receipts trie root
// must match the device's current 'ancestor_receipts_root'.
Expand Down
3 changes: 1 addition & 2 deletions firmware/src/powhsm/src/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ typedef enum {
ERR_AUTH_RECEIPT_HASH_MISMATCH = 0x6A94,
ERR_AUTH_NODE_CHAINING_MISMATCH = 0x6A95,
ERR_AUTH_RECEIPT_ROOT_MISMATCH = 0x6A96,
ERR_AUTH_INVALID_SIGHASH_COMPUTATION_MODE = 0x6A97,
ERR_AUTH_INVALID_EXTRADATA_SIZE = 0x6A98,
ERR_AUTH_INVALID_EXTRADATA_SIZE = 0x6A97,
} err_code_sign_t;

#define AUTH_MAX_EXCHANGE_SIZE RLP_BUFFER_SIZE
Expand Down
20 changes: 11 additions & 9 deletions firmware/src/powhsm/src/auth_receipt.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
// - a specific address (hardcoded, predefined) - the bridge address
// - exactly three topics, topic_0, topic_1 and topic_2, of which:
// - topic_0 (aka the event signature) must match a specific
// value (harcoded, predefined)
// value (hardcoded, predefined)
// - topic_2 must match the current BTC tx hash

// RSK receipt constants
Expand Down Expand Up @@ -200,8 +200,9 @@ static void str_end() {
auth.receipt.aux_offset == sizeof(EVENT_EMITTER) &&
!memcmp(auth.receipt.aux,
(void*)PIC(EVENT_EMITTER),
sizeof(EVENT_EMITTER)))
sizeof(EVENT_EMITTER))) {
SET_FLAG(auth.receipt.flags, IS_VALID_EMITTER);
}
break;
case TOPIC_LEVEL:
if (auth.receipt.index[LOG_LEVEL - 1] == TOPICS_INDEX) {
Expand All @@ -210,15 +211,16 @@ static void str_end() {
auth.receipt.aux_offset == sizeof(EVENT_SIGNATURE) &&
!memcmp(auth.receipt.aux,
(void*)PIC(EVENT_SIGNATURE),
sizeof(EVENT_SIGNATURE)))
sizeof(EVENT_SIGNATURE))) {
SET_FLAG(auth.receipt.flags, IS_VALID_SIGNATURE);
else if (auth.receipt.index[TOPIC_LEVEL - 1] ==
TOPIC_TXHASH_INDEX &&
auth.receipt.aux_offset == sizeof(auth.tx_hash) &&
!memcmp(auth.receipt.aux,
auth.tx_hash,
sizeof(auth.tx_hash)))
} else if (auth.receipt.index[TOPIC_LEVEL - 1] ==
TOPIC_TXHASH_INDEX &&
auth.receipt.aux_offset == sizeof(auth.tx_hash) &&
!memcmp(auth.receipt.aux,
auth.tx_hash,
sizeof(auth.tx_hash))) {
SET_FLAG(auth.receipt.flags, IS_VALID_TXHASH);
}
}
break;
}
Expand Down
178 changes: 37 additions & 141 deletions firmware/src/powhsm/src/auth_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,110 +33,28 @@

#include "hal/log.h"

// IMPORTANT: This callback only executes for the scriptSig at the desired input
// (the one that is requested to sign)
// In all the other cases the parser is not even initialized or called,
// so it is not possible for this callback to execute.
static void btcscript_cb(const btcscript_cb_event_t event) {
uint8_t redeemscript_length_size;
uint8_t redeemscript_length[MAX_SVARINT_ENCODING_SIZE];

if (event == BTCSCRIPT_EV_OPCODE && auth.tx.script_ctx.operand_size > 0 &&
auth.tx.script_ctx.operand_size == auth.tx.script_ctx.bytes_remaining) {
// This is a push of exactly the remaining script bytes => this is the
// last push of the script => this is the redeemScript
auth.tx.redeemscript_found = 1;
// Hash the script size using the size of the operand (redeemScript)
redeemscript_length_size =
svarint_encode(auth.tx.script_ctx.operand_size,
redeemscript_length,
sizeof(redeemscript_length));
hash_sha256_update(&auth.tx.sig_hash_ctx,
redeemscript_length,
redeemscript_length_size);
} else if (event == BTCSCRIPT_EV_OPERAND && auth.tx.redeemscript_found) {
hash_sha256_update(&auth.tx.sig_hash_ctx,
&auth.tx.script_ctx.operand_byte,
sizeof(auth.tx.script_ctx.operand_byte));
}
}

static void btctx_cb(const btctx_cb_event_t event) {
// Update txhash
hash_sha256_update(
&auth.tx.tx_hash_ctx, auth.tx.ctx.raw, auth.tx.ctx.raw_size);

// The bridge currently only generates pegout transactions with
// versions 1 or 2. Validate that.
if (event == BTCTX_EV_VERSION && auth.tx.ctx.parsed.version != 1 &&
auth.tx.ctx.parsed.version != 2) {
LOG("[E] Unsupported TX Version: %u\n", auth.tx.ctx.parsed.version);
THROW(ERR_AUTH_INVALID_TX_VERSION);
}

// Validate that the input index to sign is valid
if (event == BTCTX_EV_VIN_COUNT &&
auth.input_index_to_sign >= auth.tx.ctx.parsed.varint.value) {
LOG("[E] Input index to sign > number of inputs.\n");
THROW(ERR_AUTH_INVALID_TX_INPUT_INDEX);
}

// Update sighash
if (event == BTCTX_EV_VIN_SLENGTH) {
if (auth.tx.ctx.inout_current == auth.input_index_to_sign) {
// Parse this scriptSig
auth.tx.redeemscript_found = 0;
btcscript_init(&auth.tx.script_ctx,
&btcscript_cb,
auth.tx.ctx.parsed.varint.value);
} else {
// All other scriptSigs get replaced by an empty scriptSig
// when calculating the sigHash
hash_sha256_update(&auth.tx.sig_hash_ctx, (uint8_t[]){0x00}, 1);
}
} else if (event == BTCTX_EV_VIN_SCRIPT_DATA &&
auth.tx.ctx.inout_current == auth.input_index_to_sign) {
if (btcscript_consume(auth.tx.ctx.raw, auth.tx.ctx.raw_size) !=
auth.tx.ctx.raw_size) {
LOG("[E] Expected to consume %u bytes from the script but didn't",
auth.tx.ctx.raw_size);
THROW(ERR_AUTH_TX_HASH_MISMATCH);
}

if (btcscript_result() < 0) {
LOG("[E] Error %u parsing the scriptSig", btcscript_result());
THROW(ERR_AUTH_TX_HASH_MISMATCH);
}

if (auth.tx.ctx.script_remaining == 0) {
if (btcscript_result() != BTCSCRIPT_ST_DONE) {
LOG("[E] No more scriptSig bytes to parse but "
"the script parser isn't finished");
THROW(ERR_AUTH_TX_HASH_MISMATCH);
}
if (!auth.tx.redeemscript_found) {
LOG("[E] Finished parsing the scriptSig "
"but the redeemScript was not found");
THROW(ERR_AUTH_TX_HASH_MISMATCH);
}
if (event == BTCTX_EV_VERSION) {
if (auth.tx.ctx.parsed.version != 1 &&
auth.tx.ctx.parsed.version != 2) {
LOG("[E] Unsupported TX Version: %u\n", auth.tx.ctx.parsed.version);
THROW(ERR_AUTH_INVALID_TX_VERSION);
}
} else if (event != BTCTX_EV_VIN_SCRIPT_DATA) {
hash_sha256_update(
&auth.tx.sig_hash_ctx, auth.tx.ctx.raw, auth.tx.ctx.raw_size);
}
}

static void btctx_cb_segwit(const btctx_cb_event_t event) {
// Update txhash
hash_sha256_update(
&auth.tx.tx_hash_ctx, auth.tx.ctx.raw, auth.tx.ctx.raw_size);

if (event == BTCTX_EV_VERSION) {
hash_sha256_update(
&auth.tx.sig_hash_ctx, auth.tx.ctx.raw, auth.tx.ctx.raw_size);
}

if (event == BTCTX_EV_VIN_COUNT) {
if (auth.input_index_to_sign >= auth.tx.ctx.parsed.varint.value) {
LOG("[E] Input index to sign > number of inputs.\n");
THROW(ERR_AUTH_INVALID_TX_INPUT_INDEX);
}

hash_sha256_init(&auth.tx.prevouts_hash_ctx);
hash_sha256_init(&auth.tx.sequence_hash_ctx);
auth.tx.aux_offset = 0;
Expand Down Expand Up @@ -237,15 +155,14 @@ static void btctx_cb_segwit(const btctx_cb_event_t event) {
unsigned int auth_sign_handle_btctx(volatile unsigned int rx) {
uint8_t apdu_offset = 0;

#define TX_METADATA_SIZE \
(BTCTX_LENGTH_SIZE + SIGHASH_COMP_MODE_SIZE + EXTRADATA_SIZE)
#define TX_METADATA_SIZE (BTCTX_LENGTH_SIZE + EXTRADATA_SIZE)

if (auth.state != STATE_AUTH_BTCTX) {
LOG("[E] Expected to be in the BTC tx state\n");
THROW(ERR_AUTH_INVALID_STATE);
}

if (!auth.tx.segwit_processing_extradata) {
if (!auth.tx.processing_extradata) {
// Read little endian TX length
// (part of the legacy protocol, includes this length)
if (auth.tx.remaining_bytes == 0) {
Expand All @@ -258,36 +175,24 @@ unsigned int auth_sign_handle_btctx(volatile unsigned int rx) {
THROW(ERR_AUTH_INVALID_DATA_SIZE);
}
// BTC tx length includes the length of the length
// and the length of the sighash computation mode and
// extradata length
// and the extradata length
auth.tx.remaining_bytes -= TX_METADATA_SIZE;
// Init both hash operations
hash_sha256_init(&auth.tx.tx_hash_ctx);
hash_sha256_init(&auth.tx.sig_hash_ctx);
apdu_offset = BTCTX_LENGTH_SIZE;
// Following three bytes indicate the sighash computation
// mode (1 byte) and extradata size (2 bytes LE, for segwit)
auth.tx.sighash_computation_mode = APDU_DATA_PTR[apdu_offset++];
auth.tx.segwit_processing_extradata = false;
auth.tx.segwit_extradata_size = 0;
auth.tx.segwit_extradata_size += APDU_DATA_PTR[apdu_offset++];
auth.tx.segwit_extradata_size += APDU_DATA_PTR[apdu_offset++] << 8;
// Validate computation mode and init tx parsing context
switch (auth.tx.sighash_computation_mode) {
case SIGHASH_COMPUTE_MODE_LEGACY:
btctx_init(&auth.tx.ctx, &btctx_cb);
break;
case SIGHASH_COMPUTE_MODE_SEGWIT:
btctx_init(&auth.tx.ctx, &btctx_cb_segwit);
if (!auth.tx.segwit_extradata_size) {
LOG("[E] Invalid extradata size for segwit");
THROW(ERR_AUTH_INVALID_EXTRADATA_SIZE);
}
break;
default:
LOG("[E] Invalid sighash computation mode\n");
THROW(ERR_AUTH_INVALID_SIGHASH_COMPUTATION_MODE);
// Following two bytes indicate extradata size
// (2 bytes LE)
auth.tx.processing_extradata = false;
auth.tx.extradata_size = 0;
auth.tx.extradata_size += APDU_DATA_PTR[apdu_offset++];
auth.tx.extradata_size += APDU_DATA_PTR[apdu_offset++] << 8;
// Validate extradata size and init tx parsing context
if (!auth.tx.extradata_size) {
LOG("[E] Invalid extradata size");
THROW(ERR_AUTH_INVALID_EXTRADATA_SIZE);
}
btctx_init(&auth.tx.ctx, &btctx_cb);
}

auth.tx.remaining_bytes -= btctx_consume(
Expand Down Expand Up @@ -318,15 +223,9 @@ unsigned int auth_sign_handle_btctx(volatile unsigned int rx) {
auth.tx_hash[31 - j] = aux;
}

// Segwit?
if (auth.tx.sighash_computation_mode ==
SIGHASH_COMPUTE_MODE_SEGWIT) {
auth.tx.segwit_processing_extradata = true;
auth.tx.remaining_bytes =
(uint32_t)auth.tx.segwit_extradata_size;
} else {
auth.tx.finalise = true;
}
// Move onto extradata processing
auth.tx.processing_extradata = true;
auth.tx.remaining_bytes = (uint32_t)auth.tx.extradata_size;
}
} else {
// Hash extradata
Expand All @@ -339,23 +238,20 @@ unsigned int auth_sign_handle_btctx(volatile unsigned int rx) {
}

if (auth.tx.finalise) {
if (auth.tx.sighash_computation_mode == SIGHASH_COMPUTE_MODE_SEGWIT) {
// Remaining tx items to hash for segwit
hash_sha256_update(&auth.tx.sig_hash_ctx,
auth.tx.ip_seqno,
sizeof(auth.tx.ip_seqno));
hash_sha256_update(&auth.tx.sig_hash_ctx,
auth.tx.outputs_hash,
sizeof(auth.tx.outputs_hash));
hash_sha256_update(&auth.tx.sig_hash_ctx,
auth.tx.lock_time,
sizeof(auth.tx.lock_time));
}
// Hash inputs seqnos, outputs and lock time
hash_sha256_update(
&auth.tx.sig_hash_ctx, auth.tx.ip_seqno, sizeof(auth.tx.ip_seqno));
hash_sha256_update(&auth.tx.sig_hash_ctx,
auth.tx.outputs_hash,
sizeof(auth.tx.outputs_hash));
hash_sha256_update(&auth.tx.sig_hash_ctx,
auth.tx.lock_time,
sizeof(auth.tx.lock_time));

// Add SIGHASH_ALL hash type at the end
hash_sha256_update(&auth.tx.sig_hash_ctx,
(uint8_t[])SIGHASH_ALL_BYTES,
sizeof(SIGHASH_ALL_SIZE));
SIGHASH_ALL_SIZE);
hash_sha256_final(&auth.tx.sig_hash_ctx, auth.sig_hash);

hash_sha256_init(&auth.tx.sig_hash_ctx);
Expand Down
Loading
Loading