Skip to content

Commit

Permalink
feat: taproot keypair to prevent to depend on rand
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Sep 5, 2024
1 parent 661c064 commit 4689a95
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 66 deletions.
15 changes: 11 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@
name = "ord-rs"
categories = ["cryptography::cryptocurrencies"]
license = "MIT"
version = "0.2.0"
version = "0.2.1"
authors = ["Finity Technologies"]
description = "A library for working with Ordinal inscriptions."
repository = "https://github.com/bitfinity-network/ord-rs"
documentation = "https://docs.rs/ord-rs"
edition = "2021"

[features]
default = []
default = ["rand"]
rand = ["bitcoin/rand", "dep:rand"]
rune = ["ordinals"]

[dependencies]
async-trait = "0.1"
bitcoin = { version = "0.31", features = ["rand", "serde"] }
bitcoin = { version = "0.31", features = ["serde"] }
ciborium = "0.2"
hex = "0.4"
log = "0.4"
ordinals = { version = "0.0.9", optional = true }
rand = { version = "0.8" }
rand = { version = "0.8", optional = true }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_with = { version = "3", default-features = false, features = ["macros"] }
Expand All @@ -44,26 +45,32 @@ tokio = { version = "1", features = ["full"] }
[[example]]
name = "generate-address"
path = "examples/generate_address.rs"
required-features = ["rand"]

[[example]]
name = "transfer"
path = "examples/transfer.rs"
required-features = ["rand"]

[[example]]
name = "mint"
path = "examples/mint.rs"
required-features = ["rand"]

[[example]]
name = "deploy"
path = "examples/deploy.rs"
required-features = ["rand"]

[[example]]
name = "send-inscription"
path = "examples/send_inscription.rs"
required-features = ["rand"]

[[example]]
name = "print-script"
path = "examples/print_script.rs"
required-features = ["rand"]

[[example]]
name = "edict"
Expand Down
3 changes: 2 additions & 1 deletion examples/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs,
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs, TaprootKeypair,
};
use ord_rs::{Brc20, OrdTransactionBuilder};
use utils::rpc_client;
Expand Down Expand Up @@ -94,6 +94,7 @@ async fn main() -> anyhow::Result<()> {
leftovers_recipient: sender_address.clone(),
commit_fee,
reveal_fee,
taproot_keypair: Some(TaprootKeypair::Random),
},
)?;
let signed_commit_tx = builder
Expand Down
2 changes: 2 additions & 0 deletions examples/etch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{
CreateCommitTransactionArgsV2, EtchingTransactionArgs, Runestone, SignCommitTransactionArgs,
TaprootKeypair,
};
use ord_rs::{Nft, OrdTransactionBuilder};
use ordinals::{Etching, Rune, Terms};
Expand Down Expand Up @@ -123,6 +124,7 @@ async fn main() -> anyhow::Result<()> {
leftovers_recipient: sender_address.clone(),
commit_fee,
reveal_fee,
taproot_keypair: Some(TaprootKeypair::Random),
},
)?;
let signed_commit_tx = builder
Expand Down
3 changes: 2 additions & 1 deletion examples/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs,
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs, TaprootKeypair,
};
use ord_rs::{Brc20, OrdTransactionBuilder};

Expand Down Expand Up @@ -89,6 +89,7 @@ async fn main() -> anyhow::Result<()> {
leftovers_recipient: sender_address.clone(),
commit_fee,
reveal_fee,
taproot_keypair: Some(TaprootKeypair::Random),
},
)?;

Expand Down
3 changes: 2 additions & 1 deletion examples/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network, PrivateKey};
use log::{debug, info};
use ord_rs::wallet::{
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs,
CreateCommitTransactionArgsV2, RevealTransactionArgs, SignCommitTransactionArgs, TaprootKeypair,
};
use ord_rs::{Brc20, OrdTransactionBuilder};

Expand Down Expand Up @@ -88,6 +88,7 @@ async fn main() -> anyhow::Result<()> {
leftovers_recipient: sender_address.clone(),
commit_fee,
reveal_fee,
taproot_keypair: Some(TaprootKeypair::Random),
},
)?;
debug!("commit transaction: {commit_tx:?}");
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use thiserror::Error;
/// Ordinal transaction handling error types
#[derive(Error, Debug)]
pub enum OrdError {
#[error("when using P2TR, the taproot keypair option must be provided")]
TaprootKeypairNotProvided,
#[error("Hex codec error: {0}")]
HexCodec(#[from] hex::FromHexError),
#[error("Ord codec error: {0}")]
Expand Down
2 changes: 1 addition & 1 deletion src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub(crate) use builder::RUNE_POSTAGE;
pub use builder::{
CreateCommitTransaction, CreateCommitTransactionArgs, CreateCommitTransactionArgsV2,
OrdTransactionBuilder, RedeemScriptPubkey, RevealTransactionArgs, ScriptType,
SignCommitTransactionArgs, TaprootPayload, TxInputInfo, Utxo,
SignCommitTransactionArgs, TaprootKeypair, TaprootPayload, TxInputInfo, Utxo,
};
#[cfg(feature = "rune")]
pub use builder::{CreateEdictTxArgs, EtchingTransactionArgs, Runestone};
Expand Down
119 changes: 68 additions & 51 deletions src/wallet/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
pub mod signer;
mod taproot;

use bitcoin::absolute::LockTime;
use bitcoin::bip32::DerivationPath;
use bitcoin::script::{Builder as ScriptBuilder, PushBytesBuf};
Expand All @@ -6,10 +9,9 @@ use bitcoin::{
secp256k1, Address, Amount, FeeRate, Network, OutPoint, PublicKey, ScriptBuf, Sequence,
Transaction, TxIn, TxOut, Txid, Witness, XOnlyPublicKey,
};
use signer::Wallet;

use self::taproot::generate_keypair;
pub use self::taproot::TaprootPayload;
use self::signer::Wallet;
pub use self::taproot::{TaprootKeypair, TaprootPayload};
use crate::inscription::Inscription;
use crate::utils::constants::POSTAGE;
use crate::utils::fees::{estimate_commit_fee, estimate_reveal_fee, MultisigConfig};
Expand All @@ -23,9 +25,6 @@ pub use rune::{CreateEdictTxArgs, EtchingTransactionArgs, Runestone, RUNE_POSTAG

use crate::wallet::builder::signer::LocalSigner;

pub mod signer;
mod taproot;

/// Ordinal-aware transaction builder for arbitrary (`Nft`)
/// and `Brc20` inscriptions.
pub struct OrdTransactionBuilder {
Expand All @@ -36,6 +35,29 @@ pub struct OrdTransactionBuilder {
signer: Wallet,
}

/// Unspent transaction output to be used as input of a transaction
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Utxo {
pub id: Txid,
pub index: u32,
pub amount: Amount,
}

/// Output of a previous transaction to be used as an input.
///
/// This struct contains signature script in contrast to [Utxo] so it can be used to sign inputs
/// from different addresses.
#[derive(Debug, Clone)]
pub struct TxInputInfo {
/// ID of the output.
pub outpoint: OutPoint,

/// Contents of the output.
pub tx_out: TxOut,

pub derivation_path: DerivationPath,
}

#[derive(Debug)]
/// Arguments for creating a commit transaction
pub struct CreateCommitTransactionArgs<T>
Expand All @@ -54,6 +76,30 @@ where
pub fee_rate: FeeRate,
/// Multisig configuration, if applicable
pub multisig_config: Option<MultisigConfig>,
/// Optional taproot keypair
pub taproot_keypair: Option<TaprootKeypair>,
}

#[derive(Debug)]
/// Arguments for creating a commit transaction with fixed fees
pub struct CreateCommitTransactionArgsV2<T>
where
T: Inscription,
{
/// UTXOs to be used as inputs of the transaction
pub inputs: Vec<Utxo>,
/// Inscription to write
pub inscription: T,
/// Address to send the leftovers BTC of the trasnsaction
pub leftovers_recipient: Address,
/// Fee to pay for the commit transaction
pub commit_fee: Amount,
/// Fee to pay for the reveal transaction
pub reveal_fee: Amount,
/// Script pubkey of the inputs
pub txin_script_pubkey: ScriptBuf,
/// Optional taproot keypair
pub taproot_keypair: Option<TaprootKeypair>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -161,7 +207,12 @@ impl OrdTransactionBuilder {
// generate P2TR keyts
let p2tr_keys = match self.script_type {
ScriptType::P2WSH => None,
ScriptType::P2TR => Some(generate_keypair(&secp_ctx)),
ScriptType::P2TR => {
let taproot_keypair = args
.taproot_keypair
.ok_or(OrdError::TaprootKeypairNotProvided)?;
Some(taproot_keypair.generate_keypair(&secp_ctx))
}
};

// generate redeem script pubkey based on the current script type
Expand Down Expand Up @@ -403,7 +454,12 @@ impl OrdTransactionBuilder {
// generate P2TR keyts
let p2tr_keys = match self.script_type {
ScriptType::P2WSH => None,
ScriptType::P2TR => Some(generate_keypair(&secp_ctx)),
ScriptType::P2TR => {
let taproot_keypair = args
.taproot_keypair
.ok_or(OrdError::TaprootKeypairNotProvided)?;
Some(taproot_keypair.generate_keypair(&secp_ctx))
}
};

// generate redeem script pubkey based on the current script type
Expand Down Expand Up @@ -499,49 +555,6 @@ impl OrdTransactionBuilder {
}
}

#[derive(Debug)]
/// Arguments for creating a commit transaction
pub struct CreateCommitTransactionArgsV2<T>
where
T: Inscription,
{
/// UTXOs to be used as inputs of the transaction
pub inputs: Vec<Utxo>,
/// Inscription to write
pub inscription: T,
/// Address to send the leftovers BTC of the trasnsaction
pub leftovers_recipient: Address,
/// Fee to pay for the commit transaction
pub commit_fee: Amount,
/// Fee to pay for the reveal transaction
pub reveal_fee: Amount,
/// Script pubkey of the inputs
pub txin_script_pubkey: ScriptBuf,
}

/// Unspent transaction output to be used as input of a transaction
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Utxo {
pub id: Txid,
pub index: u32,
pub amount: Amount,
}

/// Output of a previous transaction to be used as an input.
///
/// This struct contains signature script in contrast to [Utxo] so it can be used to sign inputs
/// from different addresses.
#[derive(Debug, Clone)]
pub struct TxInputInfo {
/// ID of the output.
pub outpoint: OutPoint,

/// Contents of the output.
pub tx_out: TxOut,

pub derivation_path: DerivationPath,
}

#[cfg(test)]
mod test {
use std::str::FromStr;
Expand All @@ -557,6 +570,7 @@ mod test {
const WIF: &str = "cVkWbHmoCx6jS8AyPNQqvFr8V9r2qzDHJLaxGDQgDJfxT73w6fuU";

#[tokio::test]
#[cfg(feature = "rand")]
async fn test_should_build_transfer_for_brc20_transactions_from_existing_data_with_p2wsh() {
// this test refers to these testnet transactions, commit and reveal:
// <https://mempool.space/testnet/tx/4472899344bce1a6c83c6ec45859f79ab622b55b3faf67e555e3e03cee5139e6>
Expand All @@ -581,6 +595,7 @@ mod test {
leftovers_recipient: address.clone(),
commit_fee: Amount::from_sat(2_500),
reveal_fee: Amount::from_sat(4_700),
taproot_keypair: Some(TaprootKeypair::Random),
};
let tx_result = builder
.build_commit_transaction_with_fixed_fees(Network::Testnet, commit_transaction_args)
Expand Down Expand Up @@ -666,6 +681,7 @@ mod test {
}

#[tokio::test]
#[cfg(feature = "rand")]
async fn test_should_build_transfer_for_brc20_transactions_from_existing_data_with_p2tr() {
// this test refers to these testnet transactions, commit and reveal:
// <https://mempool.space/testnet/tx/973f78eb7b3cc666dc4133ff6381c363fd29edda0560d36ea3cfd31f1e85d9f9>
Expand All @@ -690,6 +706,7 @@ mod test {
leftovers_recipient: address.clone(),
commit_fee: Amount::from_sat(2_500),
reveal_fee: Amount::from_sat(4_700),
taproot_keypair: Some(TaprootKeypair::Random),
};
let tx_result = builder
.build_commit_transaction_with_fixed_fees(Network::Testnet, commit_transaction_args)
Expand Down
4 changes: 3 additions & 1 deletion src/wallet/builder/rune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ mod tests {
use hex_literal::hex;

use super::*;
use crate::wallet::{CreateCommitTransactionArgsV2, LocalSigner};
use crate::wallet::{CreateCommitTransactionArgsV2, LocalSigner, TaprootKeypair};
use crate::{Nft, SignCommitTransactionArgs, Wallet};

// <https://mempool.space/testnet/address/tb1qzc8dhpkg5e4t6xyn4zmexxljc4nkje59dg3ark>
Expand Down Expand Up @@ -383,6 +383,7 @@ mod tests {
}

#[tokio::test]
#[cfg(feature = "rand")]
async fn test_should_append_runestone() {
// this test refers to these testnet transactions, commit and reveal:
// <https://mempool.space/testnet/tx/973f78eb7b3cc666dc4133ff6381c363fd29edda0560d36ea3cfd31f1e85d9f9>
Expand Down Expand Up @@ -412,6 +413,7 @@ mod tests {
leftovers_recipient: address.clone(),
commit_fee: Amount::from_sat(2_500),
reveal_fee: Amount::from_sat(4_700),
taproot_keypair: Some(TaprootKeypair::Random),
};
let tx_result = builder
.build_commit_transaction_with_fixed_fees(Network::Testnet, commit_transaction_args)
Expand Down
9 changes: 3 additions & 6 deletions src/wallet/builder/taproot.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
mod taproot_keypair;

use bitcoin::key::UntweakedKeypair;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::taproot::{ControlBlock, LeafVersion, TaprootBuilder};
use bitcoin::{Address, Amount, Network, ScriptBuf, TxOut, XOnlyPublicKey};

pub use self::taproot_keypair::TaprootKeypair;
use crate::{OrdError, OrdResult};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -45,9 +48,3 @@ impl TaprootPayload {
})
}
}

pub fn generate_keypair(secp: &Secp256k1<All>) -> (UntweakedKeypair, XOnlyPublicKey) {
let keypair = UntweakedKeypair::new(secp, &mut rand::thread_rng());
let x_public_key = XOnlyPublicKey::from_keypair(&keypair).0;
(keypair, x_public_key)
}
Loading

0 comments on commit 4689a95

Please sign in to comment.