From 2350309969ca116db5f9599fb59a962db9c343ba Mon Sep 17 00:00:00 2001 From: Simon Jentzsch Date: Wed, 10 Nov 2021 14:00:54 +0100 Subject: [PATCH 01/17] fix btc-leaks --- c/src/verifier/btc/btc_types.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index a97a4f496..e54178cf6 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -62,14 +62,16 @@ in3_ret_t btc_serialize_tx_in(in3_req_t* req, btc_tx_in_t* tx_in, bytes_t* dst) get_compact_uint_size((uint64_t) tx_in->script.len) + tx_in->script.len + BTC_TX_IN_SEQUENCE_SIZE_BYTES); - - // alloc memory in dst - dst->data = malloc(sizeof(*dst->data)); - dst->len = tx_in_size; - + // serialize tx_in // -- Previous outpoint if (!tx_in->prev_tx_hash) return req_set_error(req, "missing prevtash_hash", IN3_ERPC); + + // alloc memory in dst + dst->data =_malloc(tx_in_size); + dst->len = tx_in_size; + + uint32_t index = 0; for (uint32_t i = 0; i < 32; i++) { dst->data[index++] = tx_in->prev_tx_hash[31 - i]; @@ -108,7 +110,7 @@ void btc_serialize_tx_out(btc_tx_out_t* tx_out, bytes_t* dst) { tx_out->script.len); // alloc memory in dst - dst->data = malloc(tx_out_size * sizeof(*dst->data)); + dst->data = _malloc(tx_out_size); dst->len = tx_out_size; // serialize tx_out @@ -178,7 +180,7 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst) { (tx->flag ? tx->witnesses.len : 0) + BTC_TX_LOCKTIME_SIZE_BYTES); - dst->data = malloc(sizeof(*dst->data)); + dst->data =_malloc(tx_size); dst->len = tx_size; // Serialize transaction data @@ -266,14 +268,14 @@ in3_ret_t btc_tx_id(btc_tx_t* tx, bytes32_t dst) { // // Get inputs // // -- serialize inputs -// bytes_t* serialized_inputs = malloc(tx_in_len * sizeof(bytes_t)); +// bytes_t* serialized_inputs =_malloc(tx_in_len * sizeof(bytes_t)); // uint32_t raw_input_size = 0; // for (uint32_t i = 0; i < tx_in_len; i++) { // btc_serialize_tx_in(&tx_in[i], &serialized_inputs[i]); // raw_input_size += serialized_inputs[i].len; // } // // -- Copy raw inputs into tx -// tx.input.data = malloc(raw_input_size); +// tx.input.data =_malloc(raw_input_size); // tx.input.len = raw_input_size; // uint32_t prev_input_len = 0; // for (uint32_t i = 0; i < tx_in_len; i++) { @@ -285,14 +287,14 @@ in3_ret_t btc_tx_id(btc_tx_t* tx, bytes32_t dst) { // // Get Outputs // // -- serialize outputs -// bytes_t* serialized_outputs = malloc(tx_out_len * sizeof(bytes_t)); +// bytes_t* serialized_outputs =_malloc(tx_out_len * sizeof(bytes_t)); // uint32_t raw_output_size = 0; // for (uint32_t i = 0; i < tx_out_len; i++) { // btc_serialize_tx_out(&tx_out[i], &serialized_outputs[i]); // raw_output_size += serialized_outputs[i].len; // } // // -- Copy raw outputs into tx -// tx.output.data = malloc(raw_output_size); +// tx.output.data =_malloc(raw_output_size); // tx.output.len = raw_output_size; // uint32_t prev_output_len = 0; // for (uint32_t i = 0; i < tx_out_len; i++) { @@ -341,7 +343,7 @@ in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t fiel dst->len += raw_src.len; size_t mem_size = dst->len * sizeof(*dst->data); - dst->data = (!dst->data) ? malloc(mem_size) : realloc(dst->data, mem_size); + dst->data = (!dst->data) ?_malloc(mem_size) : realloc(dst->data, mem_size); // Add bytes to tx field for (uint32_t i = 0; i < raw_src.len; i++) { From 2dec505c19cb4a1c19b6c0968cecfdc0f32e3ae8 Mon Sep 17 00:00:00 2001 From: Simon Jentzsch Date: Wed, 10 Nov 2021 14:53:48 +0100 Subject: [PATCH 02/17] fix memleak in btc_types --- c/src/verifier/btc/btc_types.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index e54178cf6..c9c19c701 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -341,9 +341,9 @@ in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t fiel return req_set_error(req, "Unrecognized transaction field code. No action was performed", IN3_EINVAL); } - dst->len += raw_src.len; - size_t mem_size = dst->len * sizeof(*dst->data); - dst->data = (!dst->data) ?_malloc(mem_size) : realloc(dst->data, mem_size); + size_t mem_size = raw_src.len; + dst->data = (!dst->data) ?_malloc(mem_size) : _realloc(dst->data, mem_size, dst->len); + dst->len += raw_src.len; // Add bytes to tx field for (uint32_t i = 0; i < raw_src.len; i++) { From f737765e97acef497f2afc781c83fa1515dd1e5b Mon Sep 17 00:00:00 2001 From: Simon Jentzsch Date: Wed, 10 Nov 2021 14:54:40 +0100 Subject: [PATCH 03/17] format --- c/src/verifier/btc/btc_types.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index c9c19c701..82bd0433b 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -62,16 +62,15 @@ in3_ret_t btc_serialize_tx_in(in3_req_t* req, btc_tx_in_t* tx_in, bytes_t* dst) get_compact_uint_size((uint64_t) tx_in->script.len) + tx_in->script.len + BTC_TX_IN_SEQUENCE_SIZE_BYTES); - + // serialize tx_in // -- Previous outpoint if (!tx_in->prev_tx_hash) return req_set_error(req, "missing prevtash_hash", IN3_ERPC); - // alloc memory in dst - dst->data =_malloc(tx_in_size); + // alloc memory in dst + dst->data = _malloc(tx_in_size); dst->len = tx_in_size; - uint32_t index = 0; for (uint32_t i = 0; i < 32; i++) { dst->data[index++] = tx_in->prev_tx_hash[31 - i]; @@ -180,7 +179,7 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst) { (tx->flag ? tx->witnesses.len : 0) + BTC_TX_LOCKTIME_SIZE_BYTES); - dst->data =_malloc(tx_size); + dst->data = _malloc(tx_size); dst->len = tx_size; // Serialize transaction data @@ -342,8 +341,8 @@ in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t fiel } size_t mem_size = raw_src.len; - dst->data = (!dst->data) ?_malloc(mem_size) : _realloc(dst->data, mem_size, dst->len); - dst->len += raw_src.len; + dst->data = (!dst->data) ? _malloc(mem_size) : _realloc(dst->data, mem_size, dst->len); + dst->len += raw_src.len; // Add bytes to tx field for (uint32_t i = 0; i < raw_src.len; i++) { From 7d6f5b79503d7c8a7025549689b8506b907fb4ee Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Thu, 4 Nov 2021 16:54:27 +0100 Subject: [PATCH 04/17] Support btc segwit message building for input signature --- c/src/verifier/btc/btc_sign.c | 115 ++++++++++++++++++++++++++++----- c/src/verifier/btc/btc_sign.h | 2 +- c/src/verifier/btc/btc_types.c | 4 +- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index 7769267d1..a54c6a48b 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -6,6 +6,16 @@ #include "btc_serialize.h" #include "btc_types.h" +// copy a byte array in reverse order +static void rev_memcpy(uint8_t* src, uint8_t* dst, uint32_t len) { + // TODO: Accuse error in case the following statement is false + if (src && dst) { + for (uint32_t i = 0; i < len; i++) { + dst[len - i] = src[i]; + } + } +} + // Fill tx_in fields, preparing the input for signing // WARNING: You need to free tx_in->prev_tx_hash after calling this function static void prepare_tx_in(const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { @@ -28,19 +38,21 @@ static void prepare_tx_in(const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { } // WARNING: You need to free tx_in->script.data after calling this function! -in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { +in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { if (!tx_in || !pub_key) { return req_set_error(req, "ERROR: in btc_sign_tx_in: function arguments cannot be NULL.", IN3_ERPC); } + // TODO: Implement support for other sighashes if (sighash != BTC_SIGHASH_ALL) { - return req_set_error(req, "ERROR: in btc_sign_tx_in: Sighash not supported.", IN3_ERPC); + return req_set_error(req, "ERROR: in btc_sign_tx_in: Sighash not yet supported.", IN3_ERPC); } // Generate an unsigned transaction. This will be used to henerate the hash provided to // the ecdsa signing algorithm btc_tx_t tmp_tx; btc_init_tx(&tmp_tx); + tmp_tx.flag = tx->flag; tmp_tx.version = tx->version; tmp_tx.output_count = tx->output_count; tmp_tx.output.len = tx->output.len; @@ -48,10 +60,9 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u for (uint32_t i = 0; i < tmp_tx.output.len; i++) tmp_tx.output.data[i] = tx->output.data[i]; tmp_tx.lock_time = tx->lock_time; - // TODO: This should probably be set before calling the signer function. If this is done outside this function, then the utxo is probably not needed. // Include inputs into unsigned tx btc_tx_in_t tmp_tx_in; - for (uint32_t i = 0; i < tx->input_count; i++) { + for (uint32_t i = 0; i < utxo_list_len; i++) { if (i == utxo_index) { tmp_tx_in = *tx_in; } @@ -66,20 +77,93 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u } // prepare array for hashing - btc_serialize_tx(&tmp_tx, &(tmp_tx.all)); + bytes_t hash_input; - hash_input.len = tmp_tx.all.len + 4; - hash_input.data = alloca(hash_input.len * sizeof(uint8_t)); - // TODO: Implement this in a more efficient way. Right now we copy - // the whole tx just to add 4 bytes at the end of the stream + if (tmp_tx.flag) { + // segwit transaction + // TODO: Abstract all code inside this block in a separate function + bytes_t prev_outputs, sequence; + uint8_t hash_prev_outputs[32], hash_sequence[32], hash_outputs[32]; + + prev_outputs.len = utxo_list_len * 36; // 32 bytes tx_hash + 4 bytes tx_index + sequence.len = utxo_list_len * 4; + + prev_outputs.data = alloca(prev_outputs.len * sizeof(*prev_outputs.data)); + sequence.data = alloca(sequence.len * sizeof(*sequence.data)); + + uint32_t default_sequence = 0xffffffff; + for (uint32_t i = 0; i < utxo_list_len; i++) { + rev_memcpy(prev_outputs.data + (36 * i), utxo_list[i].tx_hash, 32); + rev_memcpy(prev_outputs.data + (32 * i), (uint8_t*) &utxo_list[i].tx_index, 4); + rev_memcpy(sequence.data + (4 * i), (uint8_t*) &default_sequence, 4); + } + + if (!(sighash & BTC_SIGHASH_ANYONECANPAY)) { + btc_hash(prev_outputs, hash_prev_outputs); + if ((sighash & 0x1f) != BTC_SIGHASH_ALL) { + btc_hash(sequence, hash_sequence); + } + } + + if ((sighash & 0x1f) == BTC_SIGHASH_ALL) { + btc_hash(tx->output, hash_outputs); + } + else if (((sighash & 0x1f) != BTC_SIGHASH_SINGLE) && utxo_index < tx->output_count) { + // TODO: Implement support for sighashes other than SIGHASH_ALL + // In pseudo-code, this is what should happen: + // -- serialized_outputs = little_endian(outputs[utxo_index].value, 8_bytes) + // -- serialized_outputs.append(outputs[utxo_index].script) + // -- hash_outputs = dsha256(serialized_outputs) + } + + // Build message for hashing and signing: + // version | hash_prev_outputs | hash_sequence | prev_tx | prev_tx_index | utxo_script | utxo_value | input_sequence | hash_outputs | locktime | sighash + uint32_t index = 0; + hash_input.len = (get_compact_uint_size((uint64_t) utxo_index) + + utxo_list[utxo_index].tx_out.script.len + + +156); + + hash_input.data = alloca(hash_input.len * sizeof(uint8_t)); + uint8_t* d = hash_input.data; + uint_to_le(&hash_input, index, tx->version); + index += 4; + memcpy(d + index, hash_prev_outputs, 32); + index += 32; + memcpy(d + index, hash_sequence, 32); + index += 32; + rev_memcpy(d + index, utxo_list[utxo_index].tx_hash, 32); + index += 32; + uint_to_le(&hash_input, index, utxo_list[utxo_index].tx_index); + index += 4; + memcpy(d + index, utxo_list[utxo_index].tx_out.script.data, utxo_list[utxo_index].tx_out.script.len); + index += utxo_list[utxo_index].tx_out.script.len; + long_to_le(&hash_input, index, utxo_list[utxo_index].tx_out.value); + index += 8; + uint_to_le(&hash_input, index, 0xffffffff); // This is the 'sequence' field. Until BIP 125 sequence fields were unused. TODO: Implement support for BIP 125 + index += 4; + memcpy(d + index, hash_outputs, 32); + index += 32; + memcpy(d + index, (uint8_t*) &tx->lock_time, 4); + index += 4; + uint_to_le(&hash_input, index, sighash); + } + else { + // legacy transaction + + // TODO: Implement this in a more efficient way. Right now we copy + // the whole tx just to add 4 bytes at the end of the stream + + btc_serialize_tx(&tmp_tx, &(tmp_tx.all)); + hash_input.len = tmp_tx.all.len + 4; + hash_input.data = alloca(hash_input.len * sizeof(uint8_t)); + + // Copy serialized transaction + memcpy(hash_input.data, tmp_tx.all.data, tmp_tx.all.len); - // Copy serialized transaction - for (uint32_t i = 0; i < tmp_tx.all.len; i++) { - hash_input.data[i] = tmp_tx.all.data[i]; + // write sighash (4 bytes) at the end of the input + uint_to_le(&hash_input, tmp_tx.all.len, sighash); } - // write sighash (4 bytes) at the end of the input - uint_to_le(&hash_input, tmp_tx.all.len, sighash); // Finally, sign transaction input // -- Obtain DER signature @@ -121,7 +205,6 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u // signature is complete _free(tmp_tx.all.data); _free(tmp_tx.input.data); - // _free(hash_input.data); return IN3_OK; } @@ -133,7 +216,7 @@ in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_u // TODO: Allow setting a specific pub_key for each input btc_tx_in_t tx_in = {0}; prepare_tx_in(&selected_utxo_list[i], &tx_in); - TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, i, pub_key, &tx_in, BTC_SIGHASH_ALL), + TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, pub_key, &tx_in, BTC_SIGHASH_ALL), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) add_input_to_tx(req, tx, &tx_in); diff --git a/c/src/verifier/btc/btc_sign.h b/c/src/verifier/btc/btc_sign.h index f9c188552..91bfc9520 100644 --- a/c/src/verifier/btc/btc_sign.h +++ b/c/src/verifier/btc/btc_sign.h @@ -9,7 +9,7 @@ #define BTC_SIGHASH_SINGLE 0x3 #define BTC_SIGHASH_ANYONECANPAY 0x80 -in3_ret_t btc_sign_tx_in(in3_req_t* ctx, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); +in3_ret_t btc_sign_tx_in(in3_req_t* ctx, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_index, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); in3_ret_t btc_sign_tx(in3_req_t* ctx, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* pub_key); #endif \ No newline at end of file diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 82bd0433b..2333a0fff 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -119,11 +119,11 @@ void btc_serialize_tx_out(btc_tx_out_t* tx_out, bytes_t* dst) { long_to_le(dst, index, tx_out->value); index += 8; - // -- pk_script size + // -- lock-script size long_to_compact_uint(dst, index, tx_out->script.len); index += get_compact_uint_size((uint64_t) tx_out->script.len); - // -- pk_script + // -- lock-script memcpy(dst->data + index, tx_out->script.data, tx_out->script.len); } From 70a70842aa6b06217c0e167ae965cb4e5c0738c1 Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Fri, 5 Nov 2021 13:14:43 +0100 Subject: [PATCH 05/17] Implement support for BTC P2WPKH transactions --- c/src/verifier/btc/btc_sign.c | 125 +++++++++++++++++++++++---------- c/src/verifier/btc/btc_sign.h | 2 +- c/src/verifier/btc/btc_types.c | 79 ++++----------------- c/src/verifier/btc/btc_types.h | 1 + 4 files changed, 100 insertions(+), 107 deletions(-) diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index a54c6a48b..cd66c9191 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -3,6 +3,7 @@ #include "../../core/client/request.h" #include "../../core/client/request_internal.h" #include "../../third-party/crypto/secp256k1.h" +#include "btc_script.h" #include "btc_serialize.h" #include "btc_types.h" @@ -38,7 +39,7 @@ static void prepare_tx_in(const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { } // WARNING: You need to free tx_in->script.data after calling this function! -in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { +in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { if (!tx_in || !pub_key) { return req_set_error(req, "ERROR: in btc_sign_tx_in: function arguments cannot be NULL.", IN3_ERPC); } @@ -78,9 +79,9 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u // prepare array for hashing - bytes_t hash_input; + bytes_t hash_message; - if (tmp_tx.flag) { + if (is_segwit) { // segwit transaction // TODO: Abstract all code inside this block in a separate function bytes_t prev_outputs, sequence; @@ -119,14 +120,14 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u // Build message for hashing and signing: // version | hash_prev_outputs | hash_sequence | prev_tx | prev_tx_index | utxo_script | utxo_value | input_sequence | hash_outputs | locktime | sighash - uint32_t index = 0; - hash_input.len = (get_compact_uint_size((uint64_t) utxo_index) + - utxo_list[utxo_index].tx_out.script.len + - +156); - - hash_input.data = alloca(hash_input.len * sizeof(uint8_t)); - uint8_t* d = hash_input.data; - uint_to_le(&hash_input, index, tx->version); + uint32_t index = 0; + hash_message.len = (get_compact_uint_size((uint64_t) utxo_index) + + utxo_list[utxo_index].tx_out.script.len + + +156); + + hash_message.data = alloca(hash_message.len * sizeof(uint8_t)); + uint8_t* d = hash_message.data; + uint_to_le(&hash_message, index, tx->version); index += 4; memcpy(d + index, hash_prev_outputs, 32); index += 32; @@ -134,19 +135,19 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u index += 32; rev_memcpy(d + index, utxo_list[utxo_index].tx_hash, 32); index += 32; - uint_to_le(&hash_input, index, utxo_list[utxo_index].tx_index); + uint_to_le(&hash_message, index, utxo_list[utxo_index].tx_index); index += 4; memcpy(d + index, utxo_list[utxo_index].tx_out.script.data, utxo_list[utxo_index].tx_out.script.len); index += utxo_list[utxo_index].tx_out.script.len; - long_to_le(&hash_input, index, utxo_list[utxo_index].tx_out.value); + long_to_le(&hash_message, index, utxo_list[utxo_index].tx_out.value); index += 8; - uint_to_le(&hash_input, index, 0xffffffff); // This is the 'sequence' field. Until BIP 125 sequence fields were unused. TODO: Implement support for BIP 125 + uint_to_le(&hash_message, index, 0xffffffff); // This is the 'sequence' field. Until BIP 125 sequence fields were unused. TODO: Implement support for BIP 125 index += 4; memcpy(d + index, hash_outputs, 32); index += 32; memcpy(d + index, (uint8_t*) &tx->lock_time, 4); index += 4; - uint_to_le(&hash_input, index, sighash); + uint_to_le(&hash_message, index, sighash); } else { // legacy transaction @@ -155,14 +156,14 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u // the whole tx just to add 4 bytes at the end of the stream btc_serialize_tx(&tmp_tx, &(tmp_tx.all)); - hash_input.len = tmp_tx.all.len + 4; - hash_input.data = alloca(hash_input.len * sizeof(uint8_t)); + hash_message.len = tmp_tx.all.len + 4; + hash_message.data = alloca(hash_message.len * sizeof(uint8_t)); // Copy serialized transaction - memcpy(hash_input.data, tmp_tx.all.data, tmp_tx.all.len); + memcpy(hash_message.data, tmp_tx.all.data, tmp_tx.all.len); // write sighash (4 bytes) at the end of the input - uint_to_le(&hash_input, tmp_tx.all.len, sighash); + uint_to_le(&hash_message, tmp_tx.all.len, sighash); } // Finally, sign transaction input @@ -175,31 +176,76 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, const btc_tx_t* tx, const btc_utxo_t* u der_sig.data = alloca(sizeof(uint8_t) * 75); - TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_input, *pub_key, req->requests[0])) + TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_message, *pub_key, req->requests[0])) der_sig.len = ecdsa_sig_to_der(sig.data, der_sig.data); der_sig.data[der_sig.len++] = sig.data[64]; // append verification byte to end of DER signature - // -- build scriptSig: DER_LEN|DER_SIG|PUB_KEY_LEN|PUB_BEY - uint32_t scriptsig_len = der_sig.len + 1 + 65; // DER_SIG + 1 byte PUBKEY_LEN + PUBKEY - tx_in->script.len = 1 + scriptsig_len; // Also account for 1 byte DER_SIG_LEN - tx_in->script.data = malloc(sizeof(uint8_t) * tx_in->script.len); + // -- build scriptSig + if (is_segwit) { + // witness-enabled input + if (tmp_tx_in.script.len == 21) { + // Pay-To-Witness-Public-Key-Hash (P2WPKH). + // scriptPubKey(received from utxo) = VERSION_BYTE | HASH160(PUB_KEY) --> total: 21 bytes + // scriptSig(written to tx_in) should be empty. Data will be written in witness field + // witness(we write this to transaction) = NUM_ELEMENTS | ZERO_BYTE | DER_SIG_LEN | DER_SIG | PUB_KEY_LEN | PUB_KEY + + // As we don't still support multisig, NUM_ELEMENTS is fixed in 3 (a zero byte, the signature (len+signature) and the public key) + // TODO: IMplement multisig support + + tx_in->script.len = 0; + tx_in->script.data = NULL; + + bytes_t witness; + witness.len = 1 + 1 + 1 + der_sig.len + 65; // NUM_ELEMENTS + ZERO_BYTE + DER_SIG_LEN + DER_SIG + PUB_KEY_LEN + PUB_KEY + witness.data = alloca(sizeof(uint8_t) * witness.len); + uint32_t index = 0; + + witness.data[index++] = 3; // write NUM_ELEMENTS. When multisig is implemented, this value should change according to the number of signatures + witness.data[index++] = 0; // write zero byte + long_to_compact_uint(&witness, index++, der_sig.len); // it is safe to assume this field only has 1 byte in a correct execution + memcpy(witness.data + index, der_sig.data, der_sig.len); + index += der_sig.len; + witness.data[index++] = 65; // write PUB_KEY_LEN + memcpy(witness.data + index, pub_key->data, pub_key->len); + + add_witness_to_tx(req, tx, &witness); + } + else if (tmp_tx_in.script.len == 33) { + // Pay-To-Witness-Script-Hash (P2WSH) + // TODO: Implement multisig support + // TODO: Implement BIP16 support (Where P2SH was defined) + // TODO: Implement support to Pay-To-Witness-Script-Hash (P2WSH) + return req_set_error(req, "ERROR: in btc_sign_tx_in: P2WSH is not implemented yet", IN3_ERPC); + } + else { + return req_set_error(req, "ERROR: in btc_sign_tx_in: signature algorithm could not be determined.", IN3_ERPC); + } + } + else { + // Pay-To-Public-Key-Hash (P2PKH). scriptSig = DER_LEN|DER_SIG|PUB_KEY_LEN|PUB_BEY + // TODO: Abstract this block of code into a separate function + tx_in->script.len = 1 + der_sig.len + 1 + 65; // DER_SIG_LEN + DER_SIG + PUBKEY_LEN + PUBKEY + if (tx->flag) tx_in->script.len++; // We need to include a zero byte it it is a witness transaction + tx_in->script.data = malloc(sizeof(uint8_t) * tx_in->script.len); - bytes_t* b = &tx_in->script; - uint32_t index = 0; + bytes_t* b = &tx_in->script; + uint32_t index = 0; - long_to_compact_uint(b, index, der_sig.len); // write der_sig len field - index += 1; // it is safe to assume the previous field only has 1 byte in a correct execution. - // write der signature - uint32_t i = 0; - while (i < der_sig.len) { - b->data[index++] = der_sig.data[i++]; - } - b->data[index++] = 65; // write pubkey len - // write pubkey - i = 0; - while (i < 65) { - b->data[index++] = pub_key->data[i++]; + if (tx->flag) b->data[index++] = 0; // write zero byte if we are dealing with a witness transaction + long_to_compact_uint(b, index, der_sig.len); // write der_sig len field + index++; // it is safe to assume the previous field only has 1 byte in a correct execution. + // write DER signature + uint32_t i = 0; + while (i < der_sig.len) { + b->data[index++] = der_sig.data[i++]; + } + b->data[index++] = 65; // write pubkey len + // write pubkey + i = 0; + while (i < 65) { + b->data[index++] = pub_key->data[i++]; + } } // signature is complete @@ -216,7 +262,8 @@ in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_u // TODO: Allow setting a specific pub_key for each input btc_tx_in_t tx_in = {0}; prepare_tx_in(&selected_utxo_list[i], &tx_in); - TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, pub_key, &tx_in, BTC_SIGHASH_ALL), + bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); + TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, is_segwit, pub_key, &tx_in, BTC_SIGHASH_ALL), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) add_input_to_tx(req, tx, &tx_in); diff --git a/c/src/verifier/btc/btc_sign.h b/c/src/verifier/btc/btc_sign.h index 91bfc9520..9402a712e 100644 --- a/c/src/verifier/btc/btc_sign.h +++ b/c/src/verifier/btc/btc_sign.h @@ -9,7 +9,7 @@ #define BTC_SIGHASH_SINGLE 0x3 #define BTC_SIGHASH_ANYONECANPAY 0x80 -in3_ret_t btc_sign_tx_in(in3_req_t* ctx, const btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_index, const uint32_t utxo_index, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); +in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); in3_ret_t btc_sign_tx(in3_req_t* ctx, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* pub_key); #endif \ No newline at end of file diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 2333a0fff..8336c1241 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -18,7 +18,8 @@ typedef enum btc_tx_field { BTC_INPUT, - BTC_OUTPUT + BTC_OUTPUT, + BTC_WITNESS } btc_tx_field_t; void btc_init_tx(btc_tx_t* tx) { @@ -249,72 +250,8 @@ in3_ret_t btc_tx_id(btc_tx_t* tx, bytes32_t dst) { return IN3_OK; } -// // creates a raw unsigned transaction -// // TODO: implement better error handling -// // TODO: Support witnesses -// void create_raw_tx(btc_tx_in_t* tx_in, uint32_t tx_in_len, btc_tx_out_t* tx_out, uint32_t tx_out_len, uint32_t lock_time, bytes_t* dst_raw_tx) { -// if (!tx_in || !tx_out || !dst_raw_tx || tx_in_len == 0 || tx_out_len == 0) { -// // TODO: Implement better error handling -// printf("ERROR: arguments for creating a btc transaction can not be null\n"); -// return; -// } -// btc_tx_t tx; -// tx.version = 1; -// tx.flag = 0; -// tx.input_count = tx_in_len; -// tx.output_count = tx_out_len; -// tx.lock_time = lock_time; - -// // Get inputs -// // -- serialize inputs -// bytes_t* serialized_inputs =_malloc(tx_in_len * sizeof(bytes_t)); -// uint32_t raw_input_size = 0; -// for (uint32_t i = 0; i < tx_in_len; i++) { -// btc_serialize_tx_in(&tx_in[i], &serialized_inputs[i]); -// raw_input_size += serialized_inputs[i].len; -// } -// // -- Copy raw inputs into tx -// tx.input.data =_malloc(raw_input_size); -// tx.input.len = raw_input_size; -// uint32_t prev_input_len = 0; -// for (uint32_t i = 0; i < tx_in_len; i++) { -// for (uint32_t j = 0; j < serialized_inputs[i].len; j++) { -// tx.input.data[j + prev_input_len] = serialized_inputs[i].data[j]; -// } -// prev_input_len = serialized_inputs[i].len; -// } - -// // Get Outputs -// // -- serialize outputs -// bytes_t* serialized_outputs =_malloc(tx_out_len * sizeof(bytes_t)); -// uint32_t raw_output_size = 0; -// for (uint32_t i = 0; i < tx_out_len; i++) { -// btc_serialize_tx_out(&tx_out[i], &serialized_outputs[i]); -// raw_output_size += serialized_outputs[i].len; -// } -// // -- Copy raw outputs into tx -// tx.output.data =_malloc(raw_output_size); -// tx.output.len = raw_output_size; -// uint32_t prev_output_len = 0; -// for (uint32_t i = 0; i < tx_out_len; i++) { -// for (uint32_t j = 0; j < serialized_outputs[i].len; j++) { -// tx.output.data[j + prev_output_len] = serialized_outputs[i].data[j]; -// } -// prev_output_len = serialized_outputs[i].len; -// } - -// // free buffers -// for (uint32_t i = 0; i < tx_in_len; i++) { -// _free(serialized_inputs[i].data); -// } -// _free(serialized_inputs); -// for (uint32_t i = 0; i < tx_out_len; i++) { -// _free(serialized_outputs[i].data); -// } -// _free(serialized_outputs); -// } - -in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t field_type) { + +static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t field_type) { if (!tx || !src) { return req_set_error(req, "ERROR: in add_to_tx: Function arguments cannot be null!", IN3_EINVAL); } @@ -335,6 +272,10 @@ in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t fiel dst = &tx->output; tx->output_count++; break; + case BTC_WITNESS: + old_len = tx->witnesses.len; + dst = &tx->witnesses; + break; default: // TODO: Implement better error handling return req_set_error(req, "Unrecognized transaction field code. No action was performed", IN3_EINVAL); @@ -359,6 +300,10 @@ in3_ret_t add_output_to_tx(in3_req_t* req, btc_tx_t* tx, btc_tx_out_t* tx_out) { return add_to_tx(req, tx, tx_out, BTC_OUTPUT); } +in3_ret_t add_witness_to_tx(in3_req_t* req, btc_tx_t* tx, bytes_t* witness) { + return add_to_tx(req, tx, witness, BTC_WITNESS); +} + in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { uint32_t len = d_len(outputs); for (uint32_t i = 0; i < len; i++) { diff --git a/c/src/verifier/btc/btc_types.h b/c/src/verifier/btc/btc_types.h index 90aea0fe0..25334e2ad 100644 --- a/c/src/verifier/btc/btc_types.h +++ b/c/src/verifier/btc/btc_types.h @@ -56,6 +56,7 @@ uint32_t btc_weight(btc_tx_t* tx); in3_ret_t add_input_to_tx(in3_req_t* req, btc_tx_t* tx, btc_tx_in_t* tx_in); in3_ret_t add_output_to_tx(in3_req_t* req, btc_tx_t* tx, btc_tx_out_t* tx_out); +in3_ret_t add_witness_to_tx(in3_req_t* req, btc_tx_t* tx, bytes_t* witness); static inline bool btc_is_witness(bytes_t tx) { return tx.data[4] == 0 && tx.data[5] == 1; From 1862a47e10c073a045f18c09a9f4b4473cbf5e29 Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Fri, 5 Nov 2021 15:10:10 +0100 Subject: [PATCH 06/17] Fix write to non-allocated memory on btc sign --- c/src/verifier/btc/btc_sign.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index cd66c9191..d89c50bd1 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -12,7 +12,7 @@ static void rev_memcpy(uint8_t* src, uint8_t* dst, uint32_t len) { // TODO: Accuse error in case the following statement is false if (src && dst) { for (uint32_t i = 0; i < len; i++) { - dst[len - i] = src[i]; + dst[(len - 1) - i] = src[i]; } } } From ba55a645e4e3eb74438bdb88e9a0b026b0d0ff99 Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Fri, 5 Nov 2021 22:17:02 +0100 Subject: [PATCH 07/17] Fix btc signer generating invalid transaction data --- c/src/verifier/btc/btc.c | 32 ++++++++++++++---------- c/src/verifier/btc/btc_sign.c | 45 ++++++++++++++++++++++------------ c/src/verifier/btc/btc_sign.h | 4 +-- c/src/verifier/btc/btc_types.c | 13 +++++----- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index a329cdfc5..ee16ef097 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -549,24 +549,30 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { // This is the RPC that abstracts most of what is done in the background before sending a transaction: // Get outputs we want to send d_token_t* params = ctx->params; + char* pub_key_str; + bytes_t account; bytes_t pub_key; - pub_key.len = 20; - TRY_PARAM_GET_REQUIRED_ADDRESS(pub_key.data, ctx, 0) - d_token_t* outputs = d_get_at(params, 1); - d_token_t* utxo_list = d_get_at(params, 2); + pub_key.len = 65; // TODO: Implement support to compressed public keys as well (33-bytes) + pub_key.data = alloca(pub_key.len * sizeof(uint8_t)); + account.len = 20; + TRY_PARAM_GET_REQUIRED_ADDRESS(account.data, ctx, 0) + TRY_PARAM_GET_REQUIRED_STRING(pub_key_str, ctx, 1) + hex_to_bytes(pub_key_str, -1, pub_key.data, pub_key.len); + d_token_t* outputs = d_get_at(params, 2); + d_token_t* utxo_list = d_get_at(params, 3); - // select "best" set of UTXOs - // btc_utxo_t* utxo_list = NULL; uint32_t miner_fee = 0, outputs_total = 0, utxo_total = 0; - // ---- select utxos here + + // select "best" set of UTXOs + // btc_utxo_t* utxo_list = NULL; + // ---- PLACEHOLDER: select utxos here + // create output for receiving the transaction "change", discounting miner fee btc_tx_out_t tx_out_change; btc_init_tx_out(&tx_out_change); tx_out_change.value = utxo_total - miner_fee - outputs_total; + // create raw unsigned transaction using selected set of utxos (inputs) and outputs (both received in Command Line and created "change") - - // {"method":"sendrawtransaction", "params":["0100000001f90c6776e0aff73fdc67d57beadc283cea8c63cc7b8d48249bd79ce6a7823fc0000000008a47304402201955addf93c52fe20ce468603e97d3ea495c3072d3ebe92120ad0a200abbb51902204a9fe8e85fcee5621fc902796bbdf7ce490dc06cbfe7acb37eeb2f8c9406710e0141046d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2487e6222a6664e079c8edf7518defd562dbeda1e7593dfd7f0be285880a24dabffffffff01e803000000000000015100000000"]} - bytes_t signed_tx = NULL_BYTES; btc_tx_t tx; btc_init_tx(&tx); @@ -575,8 +581,8 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { btc_utxo_t* selected_utxo_list = NULL; uint32_t utxo_list_len = 0; btc_prepare_utxo(utxo_list, &selected_utxo_list, &utxo_list_len); - // TODO: prepare tx - TRY(btc_sign_tx(ctx->req, &tx, selected_utxo_list, utxo_list_len, &pub_key)); + + TRY(btc_sign_tx(ctx->req, &tx, selected_utxo_list, utxo_list_len, &account, &pub_key)); btc_serialize_tx(&tx, &signed_tx); sb_t sb = {0}; @@ -584,7 +590,7 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { sb_add_chars(&sb, "\""); // now that we included the signature in the rpc-request, we can free it + the old rpc-request. - _free(signed_tx.data); + // _free(signed_tx.data); d_token_t* result = NULL; TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index d89c50bd1..06932bba4 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -39,8 +39,8 @@ static void prepare_tx_in(const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { } // WARNING: You need to free tx_in->script.data after calling this function! -in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { - if (!tx_in || !pub_key) { +in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* account, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash) { + if (!tx_in || !account) { return req_set_error(req, "ERROR: in btc_sign_tx_in: function arguments cannot be NULL.", IN3_ERPC); } @@ -49,7 +49,14 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li return req_set_error(req, "ERROR: in btc_sign_tx_in: Sighash not yet supported.", IN3_ERPC); } - // Generate an unsigned transaction. This will be used to henerate the hash provided to + if (pub_key->len != 33 && pub_key->len != 65) { + return req_set_error(req, "ERROR: in btc_sign_tx_in: Public key not supported. BTC public keys should be either compressed (33 bytes) or uncompressed (65 bytes).", IN3_ERPC); + } + else if ((pub_key->len == 65 && pub_key->data[0] != 0x4) || (pub_key->len == 33 && pub_key->data[0] != 0x2 && pub_key->data[0] != 0x3)) { + return req_set_error(req, "ERROR: in btc_sign_tx_in: Invalid public key format", IN3_ERPC); + } + + // Generate an unsigned transaction. This will be used to generate the hash provided to // the ecdsa signing algorithm btc_tx_t tmp_tx; btc_init_tx(&tmp_tx); @@ -176,7 +183,7 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li der_sig.data = alloca(sizeof(uint8_t) * 75); - TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_message, *pub_key, req->requests[0])) + TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_message, *account, req->requests[0])) der_sig.len = ecdsa_sig_to_der(sig.data, der_sig.data); der_sig.data[der_sig.len++] = sig.data[64]; // append verification byte to end of DER signature @@ -190,23 +197,22 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li // scriptSig(written to tx_in) should be empty. Data will be written in witness field // witness(we write this to transaction) = NUM_ELEMENTS | ZERO_BYTE | DER_SIG_LEN | DER_SIG | PUB_KEY_LEN | PUB_KEY - // As we don't still support multisig, NUM_ELEMENTS is fixed in 3 (a zero byte, the signature (len+signature) and the public key) + // As we don't still support multisig, NUM_ELEMENTS is fixed in 2 (the signature and the public key) // TODO: IMplement multisig support tx_in->script.len = 0; tx_in->script.data = NULL; bytes_t witness; - witness.len = 1 + 1 + 1 + der_sig.len + 65; // NUM_ELEMENTS + ZERO_BYTE + DER_SIG_LEN + DER_SIG + PUB_KEY_LEN + PUB_KEY + witness.len = 1 + 1 + der_sig.len + 1 + pub_key->len; // NUM_ELEMENTS + DER_SIG_LEN + DER_SIG + PUB_KEY_LEN + PUB_KEY witness.data = alloca(sizeof(uint8_t) * witness.len); uint32_t index = 0; - witness.data[index++] = 3; // write NUM_ELEMENTS. When multisig is implemented, this value should change according to the number of signatures - witness.data[index++] = 0; // write zero byte + witness.data[index++] = 2; // write NUM_ELEMENTS. When multisig is implemented, this value should change according to the number of signatures long_to_compact_uint(&witness, index++, der_sig.len); // it is safe to assume this field only has 1 byte in a correct execution memcpy(witness.data + index, der_sig.data, der_sig.len); index += der_sig.len; - witness.data[index++] = 65; // write PUB_KEY_LEN + witness.data[index++] = pub_key->len; // write PUB_KEY_LEN memcpy(witness.data + index, pub_key->data, pub_key->len); add_witness_to_tx(req, tx, &witness); @@ -225,7 +231,7 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li else { // Pay-To-Public-Key-Hash (P2PKH). scriptSig = DER_LEN|DER_SIG|PUB_KEY_LEN|PUB_BEY // TODO: Abstract this block of code into a separate function - tx_in->script.len = 1 + der_sig.len + 1 + 65; // DER_SIG_LEN + DER_SIG + PUBKEY_LEN + PUBKEY + tx_in->script.len = 1 + der_sig.len + 1 + 64; // DER_SIG_LEN + DER_SIG + PUBKEY_LEN + PUBKEY if (tx->flag) tx_in->script.len++; // We need to include a zero byte it it is a witness transaction tx_in->script.data = malloc(sizeof(uint8_t) * tx_in->script.len); @@ -240,10 +246,10 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li while (i < der_sig.len) { b->data[index++] = der_sig.data[i++]; } - b->data[index++] = 65; // write pubkey len + b->data[index++] = 64; // write pubkey len // write pubkey i = 0; - while (i < 65) { + while (i < 64) { b->data[index++] = pub_key->data[i++]; } } @@ -255,15 +261,22 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li return IN3_OK; } -in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* pub_key) { +in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* account, bytes_t* pub_key) { // for each input in a tx: for (uint32_t i = 0; i < utxo_list_len; i++) { - // -- for each pub_key (assume we only have one pub key for now): - // TODO: Allow setting a specific pub_key for each input + bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); + if (is_segwit) { + tx->flag = 1; + break; + } + } + for (uint32_t i = 0; i < utxo_list_len; i++) { + // -- for each account (assume we only have one pub key for now): + // TODO: Allow setting a specific account for each input btc_tx_in_t tx_in = {0}; prepare_tx_in(&selected_utxo_list[i], &tx_in); bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); - TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, is_segwit, pub_key, &tx_in, BTC_SIGHASH_ALL), + TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, is_segwit, account, pub_key, &tx_in, BTC_SIGHASH_ALL), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) add_input_to_tx(req, tx, &tx_in); diff --git a/c/src/verifier/btc/btc_sign.h b/c/src/verifier/btc/btc_sign.h index 9402a712e..aaa4fdb7d 100644 --- a/c/src/verifier/btc/btc_sign.h +++ b/c/src/verifier/btc/btc_sign.h @@ -9,7 +9,7 @@ #define BTC_SIGHASH_SINGLE 0x3 #define BTC_SIGHASH_ANYONECANPAY 0x80 -in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); -in3_ret_t btc_sign_tx(in3_req_t* ctx, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* pub_key); +in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_list, const uint32_t utxo_list_len, const uint32_t utxo_index, const bool is_segwit, const bytes_t* account, const bytes_t* pub_key, btc_tx_in_t* tx_in, uint8_t sighash); +in3_ret_t btc_sign_tx(in3_req_t* ctx, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* account, bytes_t* pub_key); #endif \ No newline at end of file diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 8336c1241..2116b7a0b 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -125,7 +125,7 @@ void btc_serialize_tx_out(btc_tx_out_t* tx_out, bytes_t* dst) { index += get_compact_uint_size((uint64_t) tx_out->script.len); // -- lock-script - memcpy(dst->data + index, tx_out->script.data, tx_out->script.len); + memcpy(dst->data + index, tx_out->script.data, tx_out->script.len); } in3_ret_t btc_parse_tx(bytes_t tx, btc_tx_t* dst) { @@ -275,6 +275,8 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field case BTC_WITNESS: old_len = tx->witnesses.len; dst = &tx->witnesses; + raw_src.len = ((bytes_t*) src)->len; + raw_src.data = ((bytes_t*) src)->data; break; default: // TODO: Implement better error handling @@ -284,11 +286,10 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field size_t mem_size = raw_src.len; dst->data = (!dst->data) ? _malloc(mem_size) : _realloc(dst->data, mem_size, dst->len); dst->len += raw_src.len; - // Add bytes to tx field - for (uint32_t i = 0; i < raw_src.len; i++) { - dst->data[old_len + i] = raw_src.data[i]; - } + // for (uint32_t i = 0; i < raw_src.len; i++) { + // dst->data[old_len + i] = raw_src.data[i]; + // } return IN3_OK; } @@ -310,7 +311,7 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { d_token_t* output = d_get_at(outputs, i); const char* script_string = d_string(d_get(output, key("script"))); - uint64_t value = d_get_long(d_get(output, key("value")), 0L); + uint64_t value = d_get_long(output, key("value")); btc_tx_out_t tx_out; uint32_t script_len = strlen(script_string) / 2; From b10b3854af6e9b3c6c9646a42c2d529c0c25ce1e Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Wed, 10 Nov 2021 11:47:43 +0100 Subject: [PATCH 08/17] Refactor some btc api functions --- c/src/verifier/btc/btc.c | 13 +++++-------- c/src/verifier/btc/btc_sign.c | 11 ++--------- c/src/verifier/btc/btc_types.c | 24 ++++++++++++++++++++---- c/src/verifier/btc/btc_types.h | 5 ++++- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index ee16ef097..9ebffb11a 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -544,10 +544,9 @@ static in3_ret_t in3_verify_btc(btc_target_conf_t* conf, in3_vctx_t* vc) { in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { UNUSED_VAR(conf); + // This is the RPC that abstracts most of what is done in the background before sending a transaction: in3_req_t* req = ctx->req; - // This is the RPC that abstracts most of what is done in the background before sending a transaction: - // Get outputs we want to send d_token_t* params = ctx->params; char* pub_key_str; bytes_t account; @@ -563,24 +562,22 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { uint32_t miner_fee = 0, outputs_total = 0, utxo_total = 0; - // select "best" set of UTXOs - // btc_utxo_t* utxo_list = NULL; - // ---- PLACEHOLDER: select utxos here - // create output for receiving the transaction "change", discounting miner fee btc_tx_out_t tx_out_change; btc_init_tx_out(&tx_out_change); tx_out_change.value = utxo_total - miner_fee - outputs_total; - // create raw unsigned transaction using selected set of utxos (inputs) and outputs (both received in Command Line and created "change") + // create unsigned transaction bytes_t signed_tx = NULL_BYTES; btc_tx_t tx; btc_init_tx(&tx); add_outputs_to_tx(req, outputs, &tx); + // select "best" set of UTXOs btc_utxo_t* selected_utxo_list = NULL; uint32_t utxo_list_len = 0; - btc_prepare_utxo(utxo_list, &selected_utxo_list, &utxo_list_len); + btc_prepare_utxos(&tx, utxo_list, &selected_utxo_list, &utxo_list_len); + btc_set_segwit(&tx, selected_utxo_list, utxo_list_len); TRY(btc_sign_tx(ctx->req, &tx, selected_utxo_list, utxo_list_len, &account, &pub_key)); diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index 06932bba4..88ef100a2 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -264,15 +264,8 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, uint32_t utxo_list_len, bytes_t* account, bytes_t* pub_key) { // for each input in a tx: for (uint32_t i = 0; i < utxo_list_len; i++) { - bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); - if (is_segwit) { - tx->flag = 1; - break; - } - } - for (uint32_t i = 0; i < utxo_list_len; i++) { - // -- for each account (assume we only have one pub key for now): - // TODO: Allow setting a specific account for each input + // -- for each public key (assume we only have one pub key for now): + // TODO: Allow setting a specific public key for each input btc_tx_in_t tx_in = {0}; prepare_tx_in(&selected_utxo_list[i], &tx_in); bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 2116b7a0b..e1ec2a75a 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -4,6 +4,7 @@ #include "../../core/util/mem.h" #include "../../core/util/utils.h" #include "btc_serialize.h" +#include "btc_script.h" // Transaction fixed size values #define BTC_TX_VERSION_SIZE_BYTES 4 @@ -326,11 +327,15 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { return IN3_OK; } -// utxos must be freed -in3_ret_t btc_prepare_utxo(d_token_t* utxo_inputs, btc_utxo_t** utxos, uint32_t* len) { +// WARNING: You must free selected_utxos pointer after calling this function +// TODO: Currently we are adding all utxo_inputs to the list of selected_utxos. Implement an algorithm to select only the necessary utxos for the transaction, given the outputs. +in3_ret_t btc_prepare_utxos(const btc_tx_t* tx, d_token_t* utxo_inputs, btc_utxo_t** selected_utxos, uint32_t* len) { + UNUSED_VAR(tx); + *len = d_len(utxo_inputs); - *utxos = _malloc(*len * sizeof(btc_utxo_t)); + *selected_utxos = _malloc(*len * sizeof(btc_utxo_t)); + // TODO: Only add the necessary utxos to selected_utxos for (uint32_t i = 0; i < *len; i++) { btc_utxo_t utxo; d_token_t* utxo_input = d_get_at(utxo_inputs, i); @@ -352,8 +357,19 @@ in3_ret_t btc_prepare_utxo(d_token_t* utxo_inputs, btc_utxo_t** utxos, uint32_t* utxo.tx_out.value = value; utxo.tx_out.script = tx_script; - *utxos[i] = utxo; + *selected_utxos[i] = utxo; } return IN3_OK; } + +in3_ret_t btc_set_segwit(btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, const uint32_t utxo_list_len) { + tx->flag = 0; + for (uint32_t i = 0; i < utxo_list_len; i++) { + if (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1) { + tx->flag = 1; + break; + } + } + return IN3_OK; +} diff --git a/c/src/verifier/btc/btc_types.h b/c/src/verifier/btc/btc_types.h index 25334e2ad..822583305 100644 --- a/c/src/verifier/btc/btc_types.h +++ b/c/src/verifier/btc/btc_types.h @@ -46,7 +46,7 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst); in3_ret_t btc_tx_id(btc_tx_t* tx, bytes32_t dst); uint8_t* btc_parse_tx_in(uint8_t* data, btc_tx_in_t* dst, uint8_t* limit); -in3_ret_t btc_prepare_utxo(d_token_t* utxo_inputs, btc_utxo_t** utxos, uint32_t* len); +in3_ret_t btc_serialize_tx_in(in3_req_t* req, btc_tx_in_t* tx_in, bytes_t* dst); uint8_t* btc_parse_tx_out(uint8_t* data, btc_tx_out_t* dst); void btc_serialize_tx_out(btc_tx_out_t* tx_out, bytes_t* dst); @@ -58,6 +58,9 @@ in3_ret_t add_input_to_tx(in3_req_t* req, btc_tx_t* tx, btc_tx_in_t* tx_in); in3_ret_t add_output_to_tx(in3_req_t* req, btc_tx_t* tx, btc_tx_out_t* tx_out); in3_ret_t add_witness_to_tx(in3_req_t* req, btc_tx_t* tx, bytes_t* witness); +in3_ret_t btc_prepare_utxos(const btc_tx_t* tx, d_token_t* utxo_inputs, btc_utxo_t** selected_utxos, uint32_t* len); +in3_ret_t btc_set_segwit(btc_tx_t* tx, const btc_utxo_t* selected_utxo_list, const uint32_t utxo_list_len); + static inline bool btc_is_witness(bytes_t tx) { return tx.data[4] == 0 && tx.data[5] == 1; } From b1d9d7b918b84d8c8968d26d072fc02366a5e0c6 Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Thu, 11 Nov 2021 13:50:47 +0100 Subject: [PATCH 09/17] Fix several memory leaks --- c/src/verifier/btc/btc.c | 67 +++++++++++++++++++--------------- c/src/verifier/btc/btc_sign.c | 26 ++++++------- c/src/verifier/btc/btc_types.c | 23 +++++++----- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index 9ebffb11a..f331aaf21 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -546,6 +546,19 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { UNUSED_VAR(conf); // This is the RPC that abstracts most of what is done in the background before sending a transaction: + in3_req_t* sub = req_find_required(ctx->req, "sendrawtransaction", NULL); + if (sub) { // do we have a result? + switch (in3_req_state(sub)) { + case REQ_ERROR: + return req_set_error(ctx->req, sub->error, sub->verification_state ? sub->verification_state : IN3_ERPC); + case REQ_SUCCESS: + return IN3_OK; + case REQ_WAITING_TO_SEND: + case REQ_WAITING_FOR_RESPONSE: + return IN3_WAITING; + } + } + in3_req_t* req = ctx->req; d_token_t* params = ctx->params; char* pub_key_str; @@ -583,16 +596,32 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { btc_serialize_tx(&tx, &signed_tx); sb_t sb = {0}; + sb_add_chars(&sb, "{\"method\":\"sendrawtransaction\",\"params\":["); sb_add_rawbytes(&sb, "\"", signed_tx, 0); - sb_add_chars(&sb, "\""); + sb_add_chars(&sb, "\"]}"); + + // Now that we wrote the request, we can free all allocated memory + if (signed_tx.data) _free(signed_tx.data); + + if (selected_utxo_list) { + for (uint32_t i = 0; i < utxo_list_len; i++) { + _free(selected_utxo_list[i].tx_hash); + _free(selected_utxo_list[i].tx_out.script.data); + } + _free(selected_utxo_list); + } + + if (tx.input.data) _free(tx.input.data); + if (tx.output.data) _free(tx.output.data); + if (tx.witnesses.data) _free(tx.witnesses.data); - // now that we included the signature in the rpc-request, we can free it + the old rpc-request. - // _free(signed_tx.data); + // finally, send transaction + d_token_t* result = NULL; + TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); - d_token_t* result = NULL; - TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); - sb_add_json(in3_rpc_handle_start(ctx), "", result); - return in3_rpc_handle_finish(ctx); + sb_add_json(in3_rpc_handle_start(ctx), "", result); + return in3_rpc_handle_finish(ctx); + //return req_add_required(ctx->req, req_new(ctx->req->client, sb.data)); } static in3_ret_t btc_handle_intern(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { @@ -654,26 +683,4 @@ in3_ret_t in3_register_btc(in3_t* c) { tc->max_diff = 10; tc->dap_limit = 20; return in3_plugin_register(c, PLGN_ACT_RPC_VERIFY | PLGN_ACT_TERM | PLGN_ACT_CONFIG_GET | PLGN_ACT_CONFIG_SET | PLGN_ACT_RPC_HANDLE, handle_btc, tc, false); -} -/* -static void print_hex(char* prefix, uint8_t* data, int len) { - printf("%s0x", prefix); - for (int i = 0; i < len; i++) printf("%02x", data[i]); - printf("\n"); -} -static void print(char* prefix, bytes_t data, char* type) { - uint8_t tmp[32]; - if (strcmp(type, "hash") == 0) { - rev_copy(tmp, data.data); - data.data = tmp; - } - - if (strcmp(type, "int") == 0) { - printf("%s%i\n", prefix, le_to_int(data.data)); - return; - } - - print_hex(prefix, data.data, data.len); -} - -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index 88ef100a2..5afdc4081 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -19,23 +19,23 @@ static void rev_memcpy(uint8_t* src, uint8_t* dst, uint32_t len) { // Fill tx_in fields, preparing the input for signing // WARNING: You need to free tx_in->prev_tx_hash after calling this function -static void prepare_tx_in(const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { +static in3_ret_t prepare_tx_in(in3_req_t* req, const btc_utxo_t* utxo, btc_tx_in_t* tx_in) { if (!utxo || !tx_in) { // TODO: Implement better error treatment - printf("ERROR: in prepare_tx_in: function arguments can not be null!\n"); - return; + return req_set_error(req, "ERROR: in prepare_tx_in: function arguments can not be null!", IN3_EINVAL); } tx_in->prev_tx_index = utxo->tx_index; - tx_in->prev_tx_hash = malloc(32 * sizeof(uint8_t)); + tx_in->prev_tx_hash = _malloc(32); memcpy(tx_in->prev_tx_hash, utxo->tx_hash, 32); // Before signing, input script field should temporarilly be equal to the utxo we want to redeem tx_in->script.len = utxo->tx_out.script.len; - tx_in->script.data = malloc(tx_in->script.len * sizeof(uint8_t)); + tx_in->script.data = _malloc(tx_in->script.len); memcpy(tx_in->script.data, utxo->tx_out.script.data, tx_in->script.len); tx_in->sequence = 0xffffffff; // Until BIP 125 sequence fields were unused. TODO: Implement support for BIP 125 + return IN3_OK; } // WARNING: You need to free tx_in->script.data after calling this function! @@ -175,16 +175,16 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li // Finally, sign transaction input // -- Obtain DER signature - bytes_t sig; - sig.data = NULL; - sig.len = 65; + bytes_t sig = NULL_BYTES; + // sig.data = NULL; + // sig.len = 65; bytes_t der_sig; der_sig.data = alloca(sizeof(uint8_t) * 75); TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_message, *account, req->requests[0])) - + der_sig.len = ecdsa_sig_to_der(sig.data, der_sig.data); der_sig.data[der_sig.len++] = sig.data[64]; // append verification byte to end of DER signature @@ -267,14 +267,14 @@ in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_u // -- for each public key (assume we only have one pub key for now): // TODO: Allow setting a specific public key for each input btc_tx_in_t tx_in = {0}; - prepare_tx_in(&selected_utxo_list[i], &tx_in); + TRY(prepare_tx_in(req, &selected_utxo_list[i], &tx_in)) bool is_segwit = (selected_utxo_list[i].tx_out.script.data[0] < OP_PUSHDATA1); TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, is_segwit, account, pub_key, &tx_in, BTC_SIGHASH_ALL), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) - add_input_to_tx(req, tx, &tx_in); - _free(tx_in.script.data); - _free(tx_in.prev_tx_hash); + TRY_FINAL(add_input_to_tx(req, tx, &tx_in), + _free(tx_in.script.data); + _free(tx_in.prev_tx_hash);) } return IN3_OK; } \ No newline at end of file diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index e1ec2a75a..3b6242846 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -181,7 +181,7 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst) { (tx->flag ? tx->witnesses.len : 0) + BTC_TX_LOCKTIME_SIZE_BYTES); - dst->data = _malloc(tx_size); + dst->data = _calloc(tx_size, 1); dst->len = tx_size; // Serialize transaction data @@ -259,6 +259,7 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field bytes_t raw_src, *dst; uint32_t old_len; + bool must_free = false; switch (field_type) { case BTC_INPUT: @@ -266,12 +267,14 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field old_len = tx->input.len; dst = &tx->input; tx->input_count++; + must_free = true; break; case BTC_OUTPUT: btc_serialize_tx_out((btc_tx_out_t*) src, &raw_src); old_len = tx->output.len; dst = &tx->output; tx->output_count++; + must_free = true; break; case BTC_WITNESS: old_len = tx->witnesses.len; @@ -285,12 +288,13 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field } size_t mem_size = raw_src.len; - dst->data = (!dst->data) ? _malloc(mem_size) : _realloc(dst->data, mem_size, dst->len); dst->len += raw_src.len; - // Add bytes to tx field - // for (uint32_t i = 0; i < raw_src.len; i++) { - // dst->data[old_len + i] = raw_src.data[i]; - // } + dst->data = (dst->data) ? _realloc(dst->data, mem_size, dst->len) : _malloc(mem_size); + memcpy(dst->data + old_len, raw_src.data, raw_src.len); + + if (must_free) { + _free(raw_src.data); + } return IN3_OK; } @@ -310,8 +314,9 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { uint32_t len = d_len(outputs); for (uint32_t i = 0; i < len; i++) { d_token_t* output = d_get_at(outputs, i); - + if (!output) return req_set_error(req, "ERROR: Transaction output data is missing", IN3_EINVAL); const char* script_string = d_string(d_get(output, key("script"))); + if (!script_string) return req_set_error(req, "ERROR: Transaction output script is missing", IN3_EINVAL); uint64_t value = d_get_long(output, key("value")); btc_tx_out_t tx_out; @@ -322,12 +327,12 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { tx_out.script = script; tx_out.value = value; - TRY_CATCH(add_output_to_tx(req, tx, &tx_out), _free(script.data);) + TRY_FINAL(add_output_to_tx(req, tx, &tx_out), _free(script.data);) } return IN3_OK; } -// WARNING: You must free selected_utxos pointer after calling this function +// WARNING: You must free selected_utxos pointer after calling this function, as well as the pointed utxos tx_hash and tx_out.data fields // TODO: Currently we are adding all utxo_inputs to the list of selected_utxos. Implement an algorithm to select only the necessary utxos for the transaction, given the outputs. in3_ret_t btc_prepare_utxos(const btc_tx_t* tx, d_token_t* utxo_inputs, btc_utxo_t** selected_utxos, uint32_t* len) { UNUSED_VAR(tx); From 46b609195b5351cabf2b5a1ea19be4112e804d1c Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Thu, 11 Nov 2021 14:28:24 +0100 Subject: [PATCH 10/17] Fix memory leaks on btc api --- c/src/verifier/btc/btc_types.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 3b6242846..7ff21889f 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -199,22 +199,19 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst) { index += get_compact_uint_size(tx->input_count); // inputs // TODO: serialize struct if tx_in is not null - for (uint32_t i = 0; i < tx->input.len; i++) { - dst->data[index++] = tx->input.data[i]; - } + memcpy(dst->data+index, tx->input.data, tx->input.len); + index += tx->input.len; // output_count long_to_compact_uint(dst, index, tx->output_count); index += get_compact_uint_size(tx->output_count); // outputs // TODO: serialize struct if tx_out is not null - for (uint32_t i = 0; i < tx->output.len; i++) { - dst->data[index++] = tx->output.data[i]; - } - // Include witness + memcpy(dst->data+index, tx->output.data, tx->output.len); + index += tx->output.len; + // witnesses if (tx->flag) { - for (uint32_t i = 0; i < tx->witnesses.len; i++) { - dst->data[index++] = tx->witnesses.data[i]; - } + memcpy(dst->data+index, tx->witnesses.data, tx->witnesses.len); + index += tx->output.len; } // locktime dst->data[index + 3] = ((tx->lock_time >> 24) & 0xff); @@ -287,9 +284,8 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field return req_set_error(req, "Unrecognized transaction field code. No action was performed", IN3_EINVAL); } - size_t mem_size = raw_src.len; dst->len += raw_src.len; - dst->data = (dst->data) ? _realloc(dst->data, mem_size, dst->len) : _malloc(mem_size); + dst->data = (dst->data) ? _realloc(dst->data, dst->len, old_len) : _malloc(dst->len); memcpy(dst->data + old_len, raw_src.data, raw_src.len); if (must_free) { From c3d672614431085d1bb2902e3e0bebbfc141d0de Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Thu, 11 Nov 2021 15:21:46 +0100 Subject: [PATCH 11/17] Fix reading from uninitialized values --- c/src/verifier/btc/btc_sign.c | 2 +- c/src/verifier/btc/btc_types.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index 5afdc4081..c6386061f 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -8,7 +8,7 @@ #include "btc_types.h" // copy a byte array in reverse order -static void rev_memcpy(uint8_t* src, uint8_t* dst, uint32_t len) { +static void rev_memcpy(uint8_t* dst, uint8_t* src, uint32_t len) { // TODO: Accuse error in case the following statement is false if (src && dst) { for (uint32_t i = 0; i < len; i++) { diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index 7ff21889f..b113a823d 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -254,7 +254,7 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field return req_set_error(req, "ERROR: in add_to_tx: Function arguments cannot be null!", IN3_EINVAL); } - bytes_t raw_src, *dst; + bytes_t raw_src = NULL_BYTES, *dst; uint32_t old_len; bool must_free = false; From d2907f813bf9679bcdf12997932f8149a76a7b45 Mon Sep 17 00:00:00 2001 From: leonardotc Date: Thu, 11 Nov 2021 15:39:24 +0100 Subject: [PATCH 12/17] Change payload build on sendtransaction for btc --- c/src/verifier/btc/btc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index f331aaf21..920e426a9 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -596,9 +596,8 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { btc_serialize_tx(&tx, &signed_tx); sb_t sb = {0}; - sb_add_chars(&sb, "{\"method\":\"sendrawtransaction\",\"params\":["); sb_add_rawbytes(&sb, "\"", signed_tx, 0); - sb_add_chars(&sb, "\"]}"); + sb_add_chars(&sb, "\""); // Now that we wrote the request, we can free all allocated memory if (signed_tx.data) _free(signed_tx.data); From 7bb13365d051dbc89ea042e8ef2d894633e980bf Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Thu, 11 Nov 2021 18:41:01 +0100 Subject: [PATCH 13/17] refactor and format btc api code --- c/src/verifier/btc/btc.c | 18 +++++++++--------- c/src/verifier/btc/btc_sign.c | 4 ++-- c/src/verifier/btc/btc_types.c | 32 ++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index 920e426a9..7f4326678 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -559,14 +559,14 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { } } - in3_req_t* req = ctx->req; + in3_req_t* req = ctx->req; d_token_t* params = ctx->params; char* pub_key_str; bytes_t account; bytes_t pub_key; - pub_key.len = 65; // TODO: Implement support to compressed public keys as well (33-bytes) + pub_key.len = 65; // TODO: Implement support to compressed public keys as well (33-bytes) pub_key.data = alloca(pub_key.len * sizeof(uint8_t)); - account.len = 20; + account.len = 20; TRY_PARAM_GET_REQUIRED_ADDRESS(account.data, ctx, 0) TRY_PARAM_GET_REQUIRED_STRING(pub_key_str, ctx, 1) hex_to_bytes(pub_key_str, -1, pub_key.data, pub_key.len); @@ -579,7 +579,7 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { btc_tx_out_t tx_out_change; btc_init_tx_out(&tx_out_change); tx_out_change.value = utxo_total - miner_fee - outputs_total; - + // create unsigned transaction bytes_t signed_tx = NULL_BYTES; btc_tx_t tx; @@ -615,12 +615,12 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { if (tx.witnesses.data) _free(tx.witnesses.data); // finally, send transaction - d_token_t* result = NULL; - TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); + d_token_t* result = NULL; + TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); - sb_add_json(in3_rpc_handle_start(ctx), "", result); - return in3_rpc_handle_finish(ctx); - //return req_add_required(ctx->req, req_new(ctx->req->client, sb.data)); + sb_add_json(in3_rpc_handle_start(ctx), "", result); + return in3_rpc_handle_finish(ctx); + // return req_add_required(ctx->req, req_new(ctx->req->client, sb.data)); } static in3_ret_t btc_handle_intern(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index c6386061f..e788c7a12 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -184,7 +184,7 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li der_sig.data = alloca(sizeof(uint8_t) * 75); TRY(req_require_signature(req, SIGN_EC_BTC, PL_SIGN_BTCTX, &sig, hash_message, *account, req->requests[0])) - + der_sig.len = ecdsa_sig_to_der(sig.data, der_sig.data); der_sig.data[der_sig.len++] = sig.data[64]; // append verification byte to end of DER signature @@ -272,7 +272,7 @@ in3_ret_t btc_sign_tx(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* selected_u TRY_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, utxo_list_len, i, is_segwit, account, pub_key, &tx_in, BTC_SIGHASH_ALL), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) - TRY_FINAL(add_input_to_tx(req, tx, &tx_in), + TRY_FINAL(add_input_to_tx(req, tx, &tx_in), _free(tx_in.script.data); _free(tx_in.prev_tx_hash);) } diff --git a/c/src/verifier/btc/btc_types.c b/c/src/verifier/btc/btc_types.c index b113a823d..02bf43f25 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -3,8 +3,8 @@ #include "../../core/client/request_internal.h" #include "../../core/util/mem.h" #include "../../core/util/utils.h" -#include "btc_serialize.h" #include "btc_script.h" +#include "btc_serialize.h" // Transaction fixed size values #define BTC_TX_VERSION_SIZE_BYTES 4 @@ -126,7 +126,7 @@ void btc_serialize_tx_out(btc_tx_out_t* tx_out, bytes_t* dst) { index += get_compact_uint_size((uint64_t) tx_out->script.len); // -- lock-script - memcpy(dst->data + index, tx_out->script.data, tx_out->script.len); + memcpy(dst->data + index, tx_out->script.data, tx_out->script.len); } in3_ret_t btc_parse_tx(bytes_t tx, btc_tx_t* dst) { @@ -199,18 +199,18 @@ in3_ret_t btc_serialize_tx(btc_tx_t* tx, bytes_t* dst) { index += get_compact_uint_size(tx->input_count); // inputs // TODO: serialize struct if tx_in is not null - memcpy(dst->data+index, tx->input.data, tx->input.len); + memcpy(dst->data + index, tx->input.data, tx->input.len); index += tx->input.len; // output_count long_to_compact_uint(dst, index, tx->output_count); index += get_compact_uint_size(tx->output_count); // outputs // TODO: serialize struct if tx_out is not null - memcpy(dst->data+index, tx->output.data, tx->output.len); + memcpy(dst->data + index, tx->output.data, tx->output.len); index += tx->output.len; // witnesses if (tx->flag) { - memcpy(dst->data+index, tx->witnesses.data, tx->witnesses.len); + memcpy(dst->data + index, tx->witnesses.data, tx->witnesses.len); index += tx->output.len; } // locktime @@ -248,7 +248,6 @@ in3_ret_t btc_tx_id(btc_tx_t* tx, bytes32_t dst) { return IN3_OK; } - static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t field_type) { if (!tx || !src) { return req_set_error(req, "ERROR: in add_to_tx: Function arguments cannot be null!", IN3_EINVAL); @@ -256,7 +255,7 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field bytes_t raw_src = NULL_BYTES, *dst; uint32_t old_len; - bool must_free = false; + bool must_free = false; switch (field_type) { case BTC_INPUT: @@ -274,9 +273,9 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field must_free = true; break; case BTC_WITNESS: - old_len = tx->witnesses.len; - dst = &tx->witnesses; - raw_src.len = ((bytes_t*) src)->len; + old_len = tx->witnesses.len; + dst = &tx->witnesses; + raw_src.len = ((bytes_t*) src)->len; raw_src.data = ((bytes_t*) src)->data; break; default: @@ -285,8 +284,13 @@ static in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field } dst->len += raw_src.len; - dst->data = (dst->data) ? _realloc(dst->data, dst->len, old_len) : _malloc(dst->len); - memcpy(dst->data + old_len, raw_src.data, raw_src.len); + if (raw_src.data) { + dst->data = (dst->data) ? _realloc(dst->data, dst->len, old_len) : _malloc(dst->len); + memcpy(dst->data + old_len, raw_src.data, raw_src.len); + } + else { + dst->data = NULL; + } if (must_free) { _free(raw_src.data); @@ -313,7 +317,7 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { if (!output) return req_set_error(req, "ERROR: Transaction output data is missing", IN3_EINVAL); const char* script_string = d_string(d_get(output, key("script"))); if (!script_string) return req_set_error(req, "ERROR: Transaction output script is missing", IN3_EINVAL); - uint64_t value = d_get_long(output, key("value")); + uint64_t value = d_get_long(output, key("value")); btc_tx_out_t tx_out; uint32_t script_len = strlen(script_string) / 2; @@ -333,7 +337,7 @@ in3_ret_t add_outputs_to_tx(in3_req_t* req, d_token_t* outputs, btc_tx_t* tx) { in3_ret_t btc_prepare_utxos(const btc_tx_t* tx, d_token_t* utxo_inputs, btc_utxo_t** selected_utxos, uint32_t* len) { UNUSED_VAR(tx); - *len = d_len(utxo_inputs); + *len = d_len(utxo_inputs); *selected_utxos = _malloc(*len * sizeof(btc_utxo_t)); // TODO: Only add the necessary utxos to selected_utxos From 4996d2a2cebd11cb24d6a41839b3740293cf0193 Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Fri, 12 Nov 2021 12:45:38 +0100 Subject: [PATCH 14/17] Fix btc signer accepting incorrect parameters for segwit --- c/src/verifier/btc/btc_sign.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index e788c7a12..2d9e77e60 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -191,9 +191,9 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li // -- build scriptSig if (is_segwit) { // witness-enabled input - if (tmp_tx_in.script.len == 21) { + if (tx_in->script.len == 22 && tx_in->script.data[1] == 20) { // Pay-To-Witness-Public-Key-Hash (P2WPKH). - // scriptPubKey(received from utxo) = VERSION_BYTE | HASH160(PUB_KEY) --> total: 21 bytes + // scriptPubKey(received from utxo) = VERSION_BYTE | SCRIPT_LEN | HASH160(PUB_KEY) --> total: 22 bytes // scriptSig(written to tx_in) should be empty. Data will be written in witness field // witness(we write this to transaction) = NUM_ELEMENTS | ZERO_BYTE | DER_SIG_LEN | DER_SIG | PUB_KEY_LEN | PUB_KEY @@ -217,7 +217,7 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li add_witness_to_tx(req, tx, &witness); } - else if (tmp_tx_in.script.len == 33) { + else if (tx_in->script.len == 34 && tx_in->script.data[1] == 32) { // Pay-To-Witness-Script-Hash (P2WSH) // TODO: Implement multisig support // TODO: Implement BIP16 support (Where P2SH was defined) From c565d8d22106d5bb5f0a28437a655734e862e6cf Mon Sep 17 00:00:00 2001 From: Leonardo Souza Date: Fri, 12 Nov 2021 17:35:47 +0100 Subject: [PATCH 15/17] Fix btc sendtransaction returning without result --- c/src/verifier/btc/btc.c | 52 +++++++++++++++++++---------------- c/src/verifier/btc/btc_sign.c | 6 +--- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index 7f4326678..fa80d9ab6 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -518,27 +518,27 @@ static in3_ret_t in3_verify_btc(btc_target_conf_t* conf, in3_vctx_t* vc) { // } // #endif -#if !defined(RPC_ONLY) || defined(RPC_SIGNTRANSACTION) + // #if !defined(RPC_ONLY) || defined(RPC_SIGNTRANSACTION) - if (VERIFY_RPC("signtransaction")) { - REQUIRE_EXPERIMENTAL(vc->req, "btc") - // Get raw unsigned transaction - // As we will have custody of the user priv keys, this should be obtained from our server somehow - // sign transaction - // return raw signed transaction - } -#endif + // if (VERIFY_RPC("signtransaction")) { + // REQUIRE_EXPERIMENTAL(vc->req, "btc") + // // Get raw unsigned transaction + // // As we will have custody of the user priv keys, this should be obtained from our server somehow + // // sign transaction + // // return raw signed transaction + // } + // #endif -#if !defined(RPC_ONLY) || defined(RPC_SENDRAWTRANSACTION) + // #if !defined(RPC_ONLY) || defined(RPC_SENDRAWTRANSACTION) - if (VERIFY_RPC("sendrawtransaction")) { - REQUIRE_EXPERIMENTAL(vc->req, "btc") - // Get raw signed transaction - // verify if transaction is well-formed and signed before sending - // send transaction to in3 server - // return success or error code - } -#endif + // if (VERIFY_RPC("sendrawtransaction")) { + // REQUIRE_EXPERIMENTAL(vc->req, "btc") + // // Get raw signed transaction + // // verify if transaction is well-formed and signed before sending + // // send transaction to in3 server + // // return success or error code + // } + // #endif return IN3_EIGNORE; } @@ -551,8 +551,17 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { switch (in3_req_state(sub)) { case REQ_ERROR: return req_set_error(ctx->req, sub->error, sub->verification_state ? sub->verification_state : IN3_ERPC); - case REQ_SUCCESS: - return IN3_OK; + case REQ_SUCCESS: { + d_token_t* result = d_get(sub->responses[0], K_RESULT); + if (result) { + sb_add_json(in3_rpc_handle_start(ctx), "", result); + } + else { + char* error_msg = d_get_string(d_get(sub->responses[0], K_ERROR), K_MESSAGE); + return req_set_error(ctx->req, error_msg ? error_msg : "Unable to send transaction", IN3_ERPC); + } + return in3_rpc_handle_finish(ctx); + } case REQ_WAITING_TO_SEND: case REQ_WAITING_FOR_RESPONSE: return IN3_WAITING; @@ -617,10 +626,7 @@ in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { // finally, send transaction d_token_t* result = NULL; TRY_FINAL(req_send_sub_request(req, "sendrawtransaction", sb.data, NULL, &result, NULL), _free(sb.data)); - - sb_add_json(in3_rpc_handle_start(ctx), "", result); return in3_rpc_handle_finish(ctx); - // return req_add_required(ctx->req, req_new(ctx->req->client, sb.data)); } static in3_ret_t btc_handle_intern(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { diff --git a/c/src/verifier/btc/btc_sign.c b/c/src/verifier/btc/btc_sign.c index 2d9e77e60..fcdcf3c27 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -175,11 +175,7 @@ in3_ret_t btc_sign_tx_in(in3_req_t* req, btc_tx_t* tx, const btc_utxo_t* utxo_li // Finally, sign transaction input // -- Obtain DER signature - bytes_t sig = NULL_BYTES; - // sig.data = NULL; - // sig.len = 65; - - bytes_t der_sig; + bytes_t sig = NULL_BYTES, der_sig = NULL_BYTES; der_sig.data = alloca(sizeof(uint8_t) * 75); From 6f03e30197f40b0fdd572095f9896fe898f57ad8 Mon Sep 17 00:00:00 2001 From: leonardotc Date: Tue, 16 Nov 2021 15:32:16 +0100 Subject: [PATCH 16/17] Fix pages pipeline --- c/ci-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c/ci-deploy.yml b/c/ci-deploy.yml index a172d596e..34f236ada 100644 --- a/c/ci-deploy.yml +++ b/c/ci-deploy.yml @@ -52,7 +52,7 @@ readthedocs: - ./update_examples.sh - cd .. - export PRE_DOC=`cat wasm/docs/*.md` - - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@git.slock.it/in3/doc.git + - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@git.slock.it/in3/c/in3-doc.git doc - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@git.slock.it/tools/generator.git - cp python_multilib/docs/documentation.md doc/docs/api-python.md - cp dotnet/docs/api-dotnet.md doc/docs/ From cbb8b02cbc048492698d5c42d174ad035e1bd1c0 Mon Sep 17 00:00:00 2001 From: leonardotc Date: Mon, 29 Nov 2021 13:45:23 +0100 Subject: [PATCH 17/17] Downgrade cpd image --- c/ci-analyse.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c/ci-analyse.yml b/c/ci-analyse.yml index d8f4cd88c..9bec17c2d 100644 --- a/c/ci-analyse.yml +++ b/c/ci-analyse.yml @@ -123,7 +123,7 @@ format: cpd: stage: analysis needs: [] - image: rawdee/pmd + image: rawdee/pmd:6.40.0 tags: - short-jobs script: