Skip to content

Commit

Permalink
Merge pull request #909 from dfinity/tl/basic_bitcoin_using_cdk
Browse files Browse the repository at this point in the history
chore: Use ECDSA and Bitcoin IC CDK Functionality
  • Loading branch information
THLO authored Jun 18, 2024
2 parents 0f0ca7d + 6fd82b1 commit 6a51c37
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 92 deletions.
73 changes: 21 additions & 52 deletions rust/basic_bitcoin/src/basic_bitcoin/src/bitcoin_api.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,20 @@
use candid::Principal;
use ic_cdk::api::call::call_with_payment;
use ic_cdk::api::management_canister::bitcoin::{
BitcoinNetwork, GetBalanceRequest, GetCurrentFeePercentilesRequest, GetUtxosRequest,
GetUtxosResponse, MillisatoshiPerByte, Satoshi, SendTransactionRequest,
bitcoin_get_balance, bitcoin_get_current_fee_percentiles, bitcoin_get_utxos,
bitcoin_send_transaction, BitcoinNetwork, GetBalanceRequest, GetCurrentFeePercentilesRequest,
GetUtxosRequest, GetUtxosResponse, MillisatoshiPerByte, SendTransactionRequest,
};

// The fees for the various bitcoin endpoints.
const GET_BALANCE_COST_CYCLES: u64 = 100_000_000;
const GET_UTXOS_COST_CYCLES: u64 = 10_000_000_000;
const GET_CURRENT_FEE_PERCENTILES_CYCLES: u64 = 100_000_000;
const SEND_TRANSACTION_BASE_CYCLES: u64 = 5_000_000_000;
const SEND_TRANSACTION_PER_BYTE_CYCLES: u64 = 20_000_000;

/// Returns the balance of the given bitcoin address.
///
/// Relies on the `bitcoin_get_balance` endpoint.
/// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_balance
pub async fn get_balance(network: BitcoinNetwork, address: String) -> u64 {
let balance_res: Result<(Satoshi,), _> = call_with_payment(
Principal::management_canister(),
"bitcoin_get_balance",
(GetBalanceRequest {
address,
network: network.into(),
min_confirmations: None,
},),
GET_BALANCE_COST_CYCLES,
)
let min_confirmations = None;
let balance_res = bitcoin_get_balance(GetBalanceRequest {
address,
network,
min_confirmations,
})
.await;

balance_res.unwrap().0
Expand All @@ -37,16 +25,12 @@ pub async fn get_balance(network: BitcoinNetwork, address: String) -> u64 {
/// NOTE: Relies on the `bitcoin_get_utxos` endpoint.
/// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_utxos
pub async fn get_utxos(network: BitcoinNetwork, address: String) -> GetUtxosResponse {
let utxos_res: Result<(GetUtxosResponse,), _> = call_with_payment(
Principal::management_canister(),
"bitcoin_get_utxos",
(GetUtxosRequest {
address,
network: network.into(),
filter: None,
},),
GET_UTXOS_COST_CYCLES,
)
let filter = None;
let utxos_res = bitcoin_get_utxos(GetUtxosRequest {
address,
network,
filter,
})
.await;

utxos_res.unwrap().0
Expand All @@ -58,15 +42,8 @@ pub async fn get_utxos(network: BitcoinNetwork, address: String) -> GetUtxosResp
/// Relies on the `bitcoin_get_current_fee_percentiles` endpoint.
/// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles
pub async fn get_current_fee_percentiles(network: BitcoinNetwork) -> Vec<MillisatoshiPerByte> {
let res: Result<(Vec<MillisatoshiPerByte>,), _> = call_with_payment(
Principal::management_canister(),
"bitcoin_get_current_fee_percentiles",
(GetCurrentFeePercentilesRequest {
network: network.into(),
},),
GET_CURRENT_FEE_PERCENTILES_CYCLES,
)
.await;
let res =
bitcoin_get_current_fee_percentiles(GetCurrentFeePercentilesRequest { network }).await;

res.unwrap().0
}
Expand All @@ -76,18 +53,10 @@ pub async fn get_current_fee_percentiles(network: BitcoinNetwork) -> Vec<Millisa
/// Relies on the `bitcoin_send_transaction` endpoint.
/// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction
pub async fn send_transaction(network: BitcoinNetwork, transaction: Vec<u8>) {
let transaction_fee = SEND_TRANSACTION_BASE_CYCLES
+ (transaction.len() as u64) * SEND_TRANSACTION_PER_BYTE_CYCLES;

let res: Result<(), _> = call_with_payment(
Principal::management_canister(),
"bitcoin_send_transaction",
(SendTransactionRequest {
network: network.into(),
transaction,
},),
transaction_fee,
)
let res = bitcoin_send_transaction(SendTransactionRequest {
network,
transaction,
})
.await;

res.unwrap();
Expand Down
16 changes: 9 additions & 7 deletions rust/basic_bitcoin/src/basic_bitcoin/src/bitcoin_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use bitcoin::{
hashes::Hash,
Address, AddressType, EcdsaSighashType, OutPoint, Script, Transaction, TxIn, TxOut, Txid,
};
use ic_cdk::api::management_canister::bitcoin::{MillisatoshiPerByte, BitcoinNetwork, Satoshi, Utxo};
use ic_cdk::api::management_canister::bitcoin::{
BitcoinNetwork, MillisatoshiPerByte, Satoshi, Utxo,
};
use ic_cdk::print;
use sha2::Digest;
use std::str::FromStr;
Expand All @@ -28,7 +30,7 @@ pub async fn get_p2pkh_address(
derivation_path: Vec<Vec<u8>>,
) -> String {
// Fetch the public key of the given derivation path.
let public_key = ecdsa_api::ecdsa_public_key(key_name, derivation_path).await;
let public_key = ecdsa_api::get_ecdsa_public_key(key_name, derivation_path).await;

// Compute the address.
public_key_to_p2pkh_address(network, &public_key)
Expand Down Expand Up @@ -59,7 +61,7 @@ pub async fn send(

// Fetch our public key, P2PKH address, and UTXOs.
let own_public_key =
ecdsa_api::ecdsa_public_key(key_name.clone(), derivation_path.clone()).await;
ecdsa_api::get_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...");
Expand All @@ -85,7 +87,7 @@ pub async fn send(
.await;

let tx_bytes = transaction.serialize();
print(&format!("Transaction to sign: {}", hex::encode(tx_bytes)));
print(format!("Transaction to sign: {}", hex::encode(tx_bytes)));

// Sign the transaction.
let signed_transaction = sign_transaction(
Expand All @@ -94,12 +96,12 @@ pub async fn send(
transaction,
key_name,
derivation_path,
ecdsa_api::sign_with_ecdsa,
ecdsa_api::get_ecdsa_signature,
)
.await;

let signed_transaction_bytes = signed_transaction.serialize();
print(&format!(
print(format!(
"Signed transaction: {}",
hex::encode(&signed_transaction_bytes)
));
Expand Down Expand Up @@ -151,7 +153,7 @@ async fn build_transaction(
let signed_tx_bytes_len = signed_transaction.serialize().len() as u64;

if (signed_tx_bytes_len * fee_per_byte) / 1000 == total_fee {
print(&format!("Transaction built with fee {}.", total_fee));
print(format!("Transaction built with fee {}.", total_fee));
return transaction;
} else {
total_fee = (signed_tx_bytes_len * fee_per_byte) / 1000;
Expand Down
60 changes: 27 additions & 33 deletions rust/basic_bitcoin/src/basic_bitcoin/src/ecdsa_api.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,43 @@
use crate::types::*;
use candid::Principal;
use ic_cdk::{api::call::call_with_payment, call};

// The fee for the `sign_with_ecdsa` endpoint using the test key.
const SIGN_WITH_ECDSA_COST_CYCLES: u64 = 10_000_000_000;
use ic_cdk::api::management_canister::ecdsa::{
ecdsa_public_key, sign_with_ecdsa, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgument,
SignWithEcdsaArgument,
};

/// Returns the ECDSA public key of this canister at the given derivation path.
pub async fn ecdsa_public_key(key_name: String, derivation_path: Vec<Vec<u8>>) -> Vec<u8> {
pub async fn get_ecdsa_public_key(key_name: String, derivation_path: Vec<Vec<u8>>) -> Vec<u8> {
// Retrieve the public key of this canister at the given derivation path
// from the ECDSA API.
let res: Result<(ECDSAPublicKeyReply,), _> = call(
Principal::management_canister(),
"ecdsa_public_key",
(ECDSAPublicKey {
canister_id: None,
derivation_path,
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: key_name,
},
},),
)
let canister_id = None;
let key_id = EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: key_name,
};

let res = ecdsa_public_key(EcdsaPublicKeyArgument {
canister_id,
derivation_path,
key_id,
})
.await;

res.unwrap().0.public_key
}

pub async fn sign_with_ecdsa(
pub async fn get_ecdsa_signature(
key_name: String,
derivation_path: Vec<Vec<u8>>,
message_hash: Vec<u8>,
) -> Vec<u8> {
let res: Result<(SignWithECDSAReply,), _> = call_with_payment(
Principal::management_canister(),
"sign_with_ecdsa",
(SignWithECDSA {
message_hash,
derivation_path,
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: key_name,
},
},),
SIGN_WITH_ECDSA_COST_CYCLES,
)
let key_id = EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: key_name,
};

let res = sign_with_ecdsa(SignWithEcdsaArgument {
message_hash,
derivation_path,
key_id,
})
.await;

res.unwrap().0.signature
Expand Down

0 comments on commit 6a51c37

Please sign in to comment.