Smart contracts in Rust
Rust is a modern programming language known for its performance, reliability, and memory safety without a garbage collector, empowering developers to write high-performance code that's safe and maintainable.
Rust is especially popular in blockchain systems due to its strong type system, powerful macros, and diverse ecosystem, making it a top choice for building secure, scalable software.
Compiling Rust to Wasm
Rust has first-class support for compiling to WebAssembly (Wasm). Wasm is a binary instruction format that runs in a secure, sandboxed environment.
When you write application code for ICP in Rust or another language, it is compiled into Wasm before being deployed as an ICP smart contract. The compiled Wasm module is deployed to the ICP blockchain and executed deterministically across the network's nodes.
Rust on ICP
Rust canisters on ICP are developed using the Canister Development Kit (CDK) for Rust. The Rust IC CDK offers a set of libraries and macros that simplify building ICP smart contracts:
Key features include:
#[ic_cdk::init]
,#[ic_cdk::query]
, and#[ic_cdk::update]
macros for lifecycle and method definitions.- Asynchronous support via Rust's
async/await
model. - Inter-canister calls and state management tools.
- Integration with features like HTTP outcalls, cycles, and threshold signatures.
You can write your logic in idiomatic Rust, and the CDK handles the low-level interfacing with the ICP runtime. The build process compiles your code to Wasm, which is then deployed to the network.
Why Rust is a great choice for ICP development
Rust is a reliable and efficient language for building smart contracts on ICP. Beyond performance and tooling, it offers key advantages that make it a strong fit for secure, maintainable onchain applications:
- Memory safety without garbage collection: Rust prevents common bugs like null pointer dereferencing and data races at compile time through its ownership model. This reduces the risk of security issues in deployed contracts.
- Strong static typing and clear structure: Rust's type system and traits help developers model contract logic in a predictable and maintainable way. Enums and pattern matching make it easy to represent complex state transitions safely.
- Good fit for low-level cryptographic work: Rust's control over memory and its mature ecosystem of crypto libraries make it ideal for tasks like key management, signature generation, and working with raw Bitcoin data formats.
Together, these traits give developers the tools to write smart contracts that are both powerful and safe.
Using the Bitcoin API from the Rust CDK
ICP supports direct integration with the Bitcoin network, and the Rust CDK provides methods to interact with the Bitcoin API, allowing ICP smart contracts to:
- Generate Bitcoin addresses (ECDSA or Schnorr)
- Read UTXOs and balances
- Construct and sign Bitcoin transactions
- Submit transactions to the Bitcoin mainnet, testnet4, or regtest
For example, you can use the Rust CDK and Bitcoin API to create a P2TR address:
use bitcoin::{key::Secp256k1, Address, PublicKey, XOnlyPublicKey};
use ic_cdk::update;
use crate::{common::DerivationPath, schnorr::get_schnorr_public_key, BTC_CONTEXT};
/// Returns a Taproot (P2TR) address of this smart contract that supports **key path spending only**.
///
/// This address does not commit to a script path (it commits to an unspendable path per BIP-341).
/// It allows spending using a single Schnorr signature corresponding to the internal key.
#[update]
pub async fn get_p2tr_key_path_only_address() -> String {
let ctx = BTC_CONTEXT.with(|ctx| ctx.get());
// Derivation path strategy:
// We assign fixed address indexes for key roles within Taproot:
// - Index 0: key-path-only Taproot (no script tree committed)
// - Index 1: internal key for a Taproot output that includes a script tree
// - Index 2: script leaf key committed to in the Merkle tree
let internal_key_path = DerivationPath::p2tr(0, 0);
// Derive the public key used as the internal key (untweaked key path base).
// This key is used for key path spending only, without any committed script tree.
let internal_key = get_schnorr_public_key(&ctx, internal_key_path.to_vec_u8_path()).await;
// Convert the internal key to an x-only public key, as required by Taproot (BIP-341).
let internal_key = XOnlyPublicKey::from(PublicKey::from_slice(&internal_key).unwrap());
// Create a Taproot address using the internal key only.
// We pass `None` as the Merkle root, which per BIP-341 means the address commits
// to an unspendable script path, enabling only key path spending.
let secp256k1_engine = Secp256k1::new();
Address::p2tr(&secp256k1_engine, internal_key, None, ctx.bitcoin_network).to_string()
}
Learn more about generating Bitcoin addresses from ICP smart contracts.