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: 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/ diff --git a/c/src/verifier/btc/btc.c b/c/src/verifier/btc/btc.c index a329cdfc5..fa80d9ab6 100644 --- a/c/src/verifier/btc/btc.c +++ b/c/src/verifier/btc/btc.c @@ -518,77 +518,114 @@ 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; } in3_ret_t send_transaction(btc_target_conf_t* conf, in3_rpc_handle_ctx_t* ctx) { UNUSED_VAR(conf); - - 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 + + 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: { + 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; + } + } + + 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 = 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 + // 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"]} + // 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); - // TODO: prepare tx - TRY(btc_sign_tx(ctx->req, &tx, selected_utxo_list, utxo_list_len, &pub_key)); + 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)); btc_serialize_tx(&tx, &signed_tx); sb_t sb = {0}; sb_add_rawbytes(&sb, "\"", signed_tx, 0); 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); + // 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); + // 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); } @@ -651,26 +688,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 7769267d1..fcdcf3c27 100644 --- a/c/src/verifier/btc/btc_sign.c +++ b/c/src/verifier/btc/btc_sign.c @@ -3,44 +3,64 @@ #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" +// copy a byte array in reverse order +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++) { + dst[(len - 1) - 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) { +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! -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) { - 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); } + // 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 + 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); + 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 +68,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,79 +85,192 @@ 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 + bytes_t hash_message; + + if (is_segwit) { + // 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_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; + 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_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_message, index, utxo_list[utxo_index].tx_out.value); + index += 8; + 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_message, 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_message.len = tmp_tx.all.len + 4; + hash_message.data = alloca(hash_message.len * sizeof(uint8_t)); - // Copy serialized transaction - for (uint32_t i = 0; i < tmp_tx.all.len; i++) { - hash_input.data[i] = tmp_tx.all.data[i]; + // Copy serialized transaction + 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_message, 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 - bytes_t sig; - 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); - 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, *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 - // -- 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 (tx_in->script.len == 22 && tx_in->script.data[1] == 20) { + // Pay-To-Witness-Public-Key-Hash (P2WPKH). + // 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 + + // 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 + 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; - bytes_t* b = &tx_in->script; - uint32_t index = 0; + 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++] = pub_key->len; // write PUB_KEY_LEN + memcpy(witness.data + index, pub_key->data, pub_key->len); - 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++]; + add_witness_to_tx(req, tx, &witness); + } + 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) + // 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); + } } - b->data[index++] = 65; // write pubkey len - // write pubkey - i = 0; - while (i < 65) { - b->data[index++] = pub_key->data[i++]; + 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 + 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); + + bytes_t* b = &tx_in->script; + uint32_t index = 0; + + 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++] = 64; // write pubkey len + // write pubkey + i = 0; + while (i < 64) { + b->data[index++] = pub_key->data[i++]; + } } // signature is complete _free(tmp_tx.all.data); _free(tmp_tx.input.data); - // _free(hash_input.data); 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 + // -- 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_CATCH(btc_sign_tx_in(req, tx, selected_utxo_list, i, pub_key, &tx_in, BTC_SIGHASH_ALL), + 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);) + TRY_FINAL(add_input_to_tx(req, tx, &tx_in), _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); } return IN3_OK; } \ No newline at end of file diff --git a/c/src/verifier/btc/btc_sign.h b/c/src/verifier/btc/btc_sign.h index f9c188552..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* 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(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 a97a4f496..02bf43f25 100644 --- a/c/src/verifier/btc/btc_types.c +++ b/c/src/verifier/btc/btc_types.c @@ -3,6 +3,7 @@ #include "../../core/client/request_internal.h" #include "../../core/util/mem.h" #include "../../core/util/utils.h" +#include "btc_script.h" #include "btc_serialize.h" // Transaction fixed size values @@ -18,7 +19,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) { @@ -63,13 +65,14 @@ in3_ret_t btc_serialize_tx_in(in3_req_t* req, btc_tx_in_t* tx_in, bytes_t* dst) 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 +111,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 @@ -118,11 +121,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); } @@ -178,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(sizeof(*dst->data)); + dst->data = _calloc(tx_size, 1); dst->len = tx_size; // Serialize transaction data @@ -196,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); @@ -248,78 +248,14 @@ 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); } - bytes_t raw_src, *dst; + bytes_t raw_src = NULL_BYTES, *dst; uint32_t old_len; + bool must_free = false; switch (field_type) { case BTC_INPUT: @@ -327,12 +263,20 @@ in3_ret_t add_to_tx(in3_req_t* req, btc_tx_t* tx, void* src, btc_tx_field_t fiel 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; + dst = &tx->witnesses; + raw_src.len = ((bytes_t*) src)->len; + raw_src.data = ((bytes_t*) src)->data; break; default: // TODO: Implement better error handling @@ -340,12 +284,16 @@ 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); + 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; + } - // Add bytes to tx field - for (uint32_t i = 0; i < raw_src.len; i++) { - dst->data[old_len + i] = raw_src.data[i]; + if (must_free) { + _free(raw_src.data); } return IN3_OK; } @@ -358,13 +306,18 @@ 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++) { 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"))); - uint64_t value = d_get_long(d_get(output, key("value")), 0L); + 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; uint32_t script_len = strlen(script_string) / 2; @@ -374,16 +327,20 @@ 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; } -// utxos must be freed -in3_ret_t btc_prepare_utxo(d_token_t* utxo_inputs, btc_utxo_t** utxos, uint32_t* len) { - *len = d_len(utxo_inputs); - *utxos = _malloc(*len * sizeof(btc_utxo_t)); +// 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); + + *len = d_len(utxo_inputs); + *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); @@ -405,8 +362,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 90aea0fe0..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); @@ -56,6 +56,10 @@ 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); + +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;