Direct Integration with Bitcoin

Not sure I follow. Are you referring to the thread above about signed transactions? Maybe I’m misreading something, but you can see the issue was resolved (3rd party developer accidentally misread something… something we have all done) and then dev fixed it.

5 Likes

This is awesome!

I recall your Supernova presentation when you showed the developer preview integration – I was looking forward to the “real” integration already then. :slight_smile:

Great work!

6 Likes

Can someone familiar with Stacks (https://stacks.org/) explain how it is different (not technically, but rather practically) from the IC native integration? It seems eerily similar…

3 Likes
4 Likes

This is a very solid article that answers these questions IC&BTC Integration 02: Differences From Other Cross-chain Integrated BTC Solutions

1 Like

FYI folks:

Docs and sample dapps are live for the testnet:

7 Likes

Stacks, as far as I can recall, uses over-collateralisation bring BTC on chain. So every BTC on the network should be backed by an equal amount of STX.

Practically also, I am not sure if the smart contracts can send real BTC. STX can read the BTC blockchain but it doesn’t have anything like the threshold ECDSA needed to send BTC transactions, so the applications using BTC seem to be more limited to things like atomic swaps.

3 Likes

When I test the interface locally, it failed:

I started the bitcoind, and:

dfx --version
dfx 0.11.1

dfx replica --enable-bitcoin --bitcoin-node 127.0.0.1:18444

~/.cache/dfinity/versions/0.11.1/icx-proxy \
    --fetch-root-key \
    --address 127.0.0.1:8000 \
    --replica http://localhost:8080

dfx canister call basic_bitcoin get_balance '("mijgLqRdTmiRVUW4aYHG5myWBmnPvXPQuU")'
Error: Failed update call.
Caused by: Failed update call.
  The Replica returned an error: code 5, message: "Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: Panicked at 'called `Result::unwrap()` on an `Err` value: (CanisterReject, "The bitcoin API is not enabled on this subnet.")', src/basic_bitcoin/src/bitcoin_api.rs:31:17"

According to The bitcoin API is not enabled on this subnet., what is wrong with my startup command of dfx replica?

From what I know, ThorChain is closest of all chains out there to what the IC is doing in terms of integration with other chains. It uses also a native integration where they run a full node of the target blockchain per node to do the protocol level integration which we did with an adapter and a lightweight node. They also use threshold ECDSA, but they use a scheme that has the following drawbacks in contrast to ours:

  • Their protocol relies on a synchronous network assumption, which does not reflect reality of communication networks
  • A dishonest node can make the signing protocol fail, while the IC protocol degrades gracefully in such a case
6 Likes

Looks like a dfx bug to me. @ericswanson is best equipped to figure this out.

@flyq, can you try two things for me, please?

  1. try the same with dfx start instead of dfx replica
  2. try configuring the same information through dfx.json. The syntax should be like this:
"defaults": {
  "bitcoin": {
    "enabled": true,
    "nodes": ["127.0.0.1:18444"]
  }
}
1 Like

In addition to what @Severin mentioned, you need dfx 0.11.1-beta.2 or later. I’d suggest you look at the sample code to see how to set up your project.

3 Likes

After changed from dfx replica to dfx start and added bitcoin to dfx.json config, it works. the dfx version is 0.11.1

1 Like

@dfisher
I briefly looked into Stacks to provide a response to your question, and the result is pretty underwhelming considering the current situation of Stacks.

https://docs.stacks.co/docs/understand-stacks/accounts

Stacks accounts cannot hold bitcoins. The best way to obtain corresponding BTC balances is to derive the BTC address from the Stacks address (using c32check) and query the Bitcoin network.

See below image:

Decidable language - Clarity contracts have direct visibility into Bitcoin state.(1)
(1) More work needed to realize the future vision. In particular, Clarity contract can’t write back to Bitcoin (yet)

If this 3rd-party information is correct, this would mean that Stacks does not have true Bitcoin smart contracts, at least not yet. And there is no information to be found on how they would do it. The main point where Bitcoin comes in is that Stacks uses Bitcoin as a base layer for securing its own chain, which means they suffer from the same finality times as Bitcoin, but at least have much greater throughout thanks to microblocks in between the anchor blocks that correspond to Bitcoin blocks. But this means major implications on how to write smart contracts as you don’t have the 1s finality as on the IC, but that of Bitcoin. Their mechanism of using Bitcoin as a base layer to secure their chain gives their smart contracts visibility into the Bitcoin state, but without write access, so they can trigger actions based on what happens on Bitcoin, but that’s it.

To me, this is pretty underwhelming in terms of Bitcoin DeFi, essentially, there’s no true Bitcoin smart contracts at this time in Stacks, but they can only use Bitcoin information to trigger actions in the smart contracts on Stacks.

Hope that helps.

10 Likes

Thank you for the response. Really appreciate it. Can sigh a breath of relief!

1 Like

I was able to reproduce this. The problem is that the current version of the ic-starter doesn’t overwrite the subnet configuration if it already exists. My understanding is that this is in the process of being fixed.

What this means is if you run dfx start or dfx replica, without enabling bitcoin integration, and then try to run dfx start --enable-bitcoin or dfx replica --enable-bitcoin, the subnet won’t have the bitcoin feature enabled.

The workaround is either dfx start --enable-bitcoin --clean, or to remove the .dfx/state and .dfx/local directories first.

5 Likes

So in basic terms, ICP is successfully moving forward?

2 Likes

Id like to think it never stopped, but this is certainly undeniably a large jump.

8 Likes

I may have lost 0.01277662 BTC, worth about 300 u.

This is entirely my own fault and has nothing to do with the security of the ECDSA and Bitcoin interface.

Here is the details:

A few days ago, I deployed canister to the mainnet and tested the BTC on the testnet, everything works fine!

So why not test the mainnet BTC? Although I saw the announcement saying not to use valuable BTC, I thought this is mainly used to explain that the key of ecdsa may be changed when it is officially released in the future, but it does not matter, because I will transfer the BTC out immediately.

So I ran:

dfx canister --network ic install basic_bitcoin --argument='(variant {Mainnet})' -m=reinstall

dfx canister --network ic call basic_bitcoin get_p2pkh_address
("1T6xcKM23GjYNNeZXrQrctSEFAKcfmGq2")

Everything looked good, and I could get the address of the BTC mainnet correctly. It seemed everything is normal, so I withdrew a small amount of BTC from the exchange to this address. I knew the risks, so I limited the possible losses to what I can afford. In the boring waiting, (compared to IC, BTC is really slow), I tried to randomly obtain an address with a balance from the BTC explorer, to test whether the balance can be obtained correctly:

dfx canister --network ic call basic_bitcoin get_balance '("<btc address>")'
Error: Failed update call.
Caused by: Failed update call.
  The Replica returned an error: code 5, message: "Canister zso2k-nqaaa-aaaak-aao3q-cai trapped explicitly: Panicked at 'called `Result::unwrap()` on an `Err` value: (CanisterReject, "Received request for mainnet but the subnet supports testnet")', src/bitcoin_api.rs:31:17"

I realized that something was wrong, so I quickly went to check the withdrawal interface, the 2min cancellation period has passed, the transaction has been sent to the node’s memory pool, and I want to consume the utxo by sending another transaction with a higher fee price again to cancel the previous transaction, but the withdrawal is through the exchange, and it may time out waiting for customer service.


src

Since the interface of ecdsa is not banned (getting address requires getting public key from ecdsa), then theoretically I should be able to withdraw from this address.

The interface get_current_fee_percentiles to get fee_price (fee per byte) is not available, I deleted it, and then checked some recent transactions from the btc explorer, most of them are below 100, so setting it to 110.

Then, deleting the get_utxos, input the UTXOs as a parameter. The height and value in Utxo are easy to find from the BTC explorer, and the txid in OutPoint generated from tx_hash:

    // the hash is from: https://www.blockchain.com/btc/tx/e1d3f2afe6ef975a1c2675cac813d4fe1c981e6beeb3edec1ad0d872996edb21
    let hash = bitcoin_hashes::sha256d::Hash::from_str(
        "e1d3f2afe6ef975a1c2675cac813d4fe1c981e6beeb3edec1ad0d872996edb21",
    )
    .unwrap();

    let txid = bitcoin::hash_types::Txid::from_hash(hash);
    let txid_vec = txid.to_vec();

vout may be the index of utxo under this address, because there is only one transaction, so I set it to 0;

send_transaction doesn’t work either, so I return the signed transaction bytes.

Finally achieved this effect:

dfx canister --network ic call basic_bitcoin tx '(record {destination_address= "3QnYGgtC1hNrSRBHdUnDn7EBZ3R8fwezVh"; amount_in_satoshi=1247662; }, record {height=747887;value=1277662; outpoint=record {txid=vec{33;219;110;153;114;216;208;26;236;237;179;238;107;30;152;28;254;212;19;200;202;117;38;28;90;151;239;230;175;242;211;225;};vout=0;}})'
(
  "020000000121db6e9972d8d01aecedb3ee6b1e981cfed413c8ca75261c5a97efe6aff2d3e1000000006b483045022100e2644f76115c826a73488967e11390a444f0d673686b1f4e00b1b16a9ebb1e670220131e74ffb8cbf116446c84c99c805b0c1b3824d453a6a85d3ffcd60a78161d6c012103dcdb13eda6c212e807626e6e72ef8aca901a4c0f31f1bf7036803e3756240b96ffffffff02ae0913000000000017a914fd564e358f1f3f7f88990244ed1b5293946964558718750000000000001976a91404efca051ff7d7d8ba81f7dcc5c783c7381566b188ac00000000",
  blob "\02\00\00\00\01!\dbn\99r\d8\d0\1a\ec\ed\b3\eek\1e\98\1c\fe\d4\13\c8\cau&\1cZ\97\ef\e6\af\f2\d3\e1\00\00\00\00kH0E\02!\00\e2dOv\11\5c\82jsH\89g\e1\13\90\a4D\f0\d6shk\1fN\00\b1\b1j\9e\bb\1eg\02 \13\1et\ff\b8\cb\f1\16Dl\84\c9\9c\80[\0c\1b8$\d4S\a6\a8]?\fc\d6\0ax\16\1dl\01!\03\dc\db\13\ed\a6\c2\12\e8\07bnnr\ef\8a\ca\90\1aL\0f1\f1\bfp6\80>7V$\0b\96\ff\ff\ff\ff\02\ae\09\13\00\00\00\00\00\17\a9\14\fdVN5\8f\1f?\7f\88\99\02D\ed\1bR\93\94idU\87\18u\00\00\00\00\00\00\19v\a9\14\04\ef\ca\05\1f\f7\d7\d8\ba\81\f7\dc\c5\c7\83\c78\15f\b1\88\ac\00\00\00\00",
)

curl -H "Authorization: Bearer <API TOKEN>"  -d '{"tx": "020000000121db6e9972d8d01aecedb3ee6b1e981cfed413c8ca75261c5a97efe6aff2d3e1000000006b483045022100f79de358f564aed17cb8fd7cf95d79aee65218698bcd1cf037e136605029f93702203dba9fed10b25da52d6db6bd643631af832442e87051eea8f19a0f6e8d72b2f9012103dcdb13eda6c212e807626e6e72ef8aca901a4c0f31f1bf7036803e3756240b96ffffffff02ae0913000000000017a914fd564e358f1f3f7f88990244ed1b5293946964558718750000000000001976a91404efca051ff7d7d8ba81f7dcc5c783c7381566b188ac00000000"}' -X POST https://ubiquity.api.blockdaemon.com/v2/bitcoin/mainnet/tx/send
{"type":"bad-request","code":16397,"title":"Bad Request","status":400,"detail":"failed to send transaction. Error: bad-txns-inputs-missingorspent. Code: -25"}%  

# If I changed to vout to 1, and got another error, it seems vout = 0 is correct:
{"type":"bad-request","code":16397,"title":"Bad Request","status":400,"detail":"failed to send transaction. Error: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element). Code: -26"}%  

I have added permission control to interfaces that needs to be signed with an ECDSA key.

the code of tx interface
pub async fn tx(
    network: Network,
    derivation_path: Vec<Vec<u8>>,
    key_name: String,
    dst_address: String,
    amount: Satoshi,
    utxos: Vec<Utxo>,
) -> Vec<u8> {
    // Get fee percentiles from previous transactions to estimate our own fee.

    let fee_per_byte = 110;

    // Fetch our public key, P2PKH address, and UTXOs.
    let own_public_key =
        ecdsa_api::ecdsa_public_key(key_name.clone(), derivation_path.clone()).await;
    let own_address = public_key_to_p2pkh_address(network, &own_public_key);

    print("Fetching UTXOs...");
    let own_utxos = utxos;

    let own_address = Address::from_str(&own_address).unwrap();
    let dst_address = Address::from_str(&dst_address).unwrap();

    // Build the transaction that sends `amount` to the destination address.
    let transaction = build_transaction(
        &own_public_key,
        &own_address,
        &own_utxos,
        &dst_address,
        amount,
        fee_per_byte,
    )
    .await;

    // Sign the transaction.
    let signed_transaction = sign_transaction(
        &own_public_key,
        &own_address,
        transaction,
        key_name,
        derivation_path,
        ecdsa_api::sign_with_ecdsa,
    )
    .await;

    let signed_transaction_bytes = signed_transaction.serialize();

    signed_transaction_bytes
}

Finally, still failed.

  1. Is there any problem with the above steps?
  2. It has been determined that the ECDSA key will be modified before the application subnet supports the BTC mainnet interface, so waiting for the development of the BTC interface will not solve the problem, is there any other solution?
3 Likes

The ICP BTC integration only supports BTC testnet at the moment. So you cannot use the bitcoin API to send transaction or receive utxos or check balances of anything on the BTC mainnet.

Since you have already sent some bitcoin to the address, you will have to manually craft a raw transaction, and send it via other means (e.g. bitcoin-cli command line) to the BTC mainnet to get the fund out. You will have to use your canister to sign on the raw transaction, which uses threshold ECDSA API, which is not related to and thus not affected by the bitcoin API.

So your fund is not lost, and retrieve it definitely can be done, just that you can’t use existing bitcoin API to send the transaction. Hope it helps!

10 Likes

This doesn’t seem to be the reason why your transaction got rejected but please note that as far as I know your fee_per_byte in your tx interface is below the relay fee as fee_per_byte is in millisatoshis per byte. Maybe you wanted to type 1100 instead of 110 ?

4 Likes