diff --git a/charms-spell-checker/src/app.rs b/charms-spell-checker/src/app.rs index bbf8f2a..66cc812 100644 --- a/charms-spell-checker/src/app.rs +++ b/charms-spell-checker/src/app.rs @@ -14,12 +14,12 @@ fn to_public_values(t: &T) -> SP1PublicValues { } #[derive(Serialize, Deserialize)] -pub struct AppContractVK { +pub(crate) struct AppContractVK { pub vk: Option<[u32; 8]>, } impl AppContractVK { - pub fn verify(&self, app: &App, tx: &Transaction, x: &Data) -> bool { + pub(crate) fn verify(&self, app: &App, tx: &Transaction, x: &Data) -> bool { match &self.vk { Some(vk) => { let Ok(pv) = to_public_values(&(app, tx, x)).hash().try_into() else { diff --git a/charms-spell-checker/src/bin.rs b/charms-spell-checker/src/bin.rs index e28cf57..23923de 100644 --- a/charms-spell-checker/src/bin.rs +++ b/charms-spell-checker/src/bin.rs @@ -11,10 +11,11 @@ pub fn main() { eprintln!("about to commit"); // Commit to the public values of the program. - sp1_zkvm::io::commit_slice(util::write(&output).unwrap().as_slice()); + let output_vec = util::write(&output).unwrap(); + sp1_zkvm::io::commit_slice(output_vec.as_slice()); } -pub fn run(input: SpellProverInput) -> (String, NormalizedSpell) { +pub(crate) fn run(input: SpellProverInput) -> (String, NormalizedSpell) { let SpellProverInput { self_spell_vk, prev_txs, diff --git a/charms-spell-checker/src/lib.rs b/charms-spell-checker/src/lib.rs index c111e90..cfdafce 100644 --- a/charms-spell-checker/src/lib.rs +++ b/charms-spell-checker/src/lib.rs @@ -8,10 +8,14 @@ use charms_data::{App, Charms, Data, Transaction, TxId, UtxoId}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; +/// Version `0` of the protocol. pub const V0: u32 = 0u32; +/// Verification key for version `0` of the `charms-spell-checker` binary. pub const V0_SPELL_VK: &str = "0x00e9398ac819e6dd281f81db3ada3fe5159c3cc40222b5ddb0e7584ed2327c5d"; +/// Version `1` of the protocol. pub const V1: u32 = 1u32; +/// Current version of the protocol. pub const CURRENT_VERSION: u32 = V1; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -23,8 +27,11 @@ pub struct SpellProverInput { pub app_contract_proofs: BTreeSet, // proofs are provided in input stream data } +/// Maps the index of the charm's app (in [`NormalizedSpell`].`app_public_inputs`) to the charm's +/// data. pub type NormalizedCharms = BTreeMap; +/// Normalized representation of a Charms transaction. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct NormalizedTransaction { /// (Optional) input UTXO list. Is None when serialized in the transaction: the transaction @@ -42,6 +49,7 @@ pub struct NormalizedTransaction { } impl NormalizedTransaction { + /// Return a sorted set of transaction IDs of the inputs. pub fn prev_txids(&self) -> Option> { self.ins .as_ref() @@ -49,18 +57,23 @@ impl NormalizedTransaction { } } +/// Proof of correctness of a spell. pub type Proof = Box<[u8]>; +/// Normalized representation of a spell. /// Can be committed as public input. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct NormalizedSpell { + /// Protocol version. pub version: u32, + /// Transaction data. pub tx: NormalizedTransaction, - /// Maps all `App`s in the transaction to (potentially empty) data. + /// Maps all `App`s in the transaction to (potentially empty) public input data. pub app_public_inputs: BTreeMap, } impl NormalizedSpell { + /// Check if the spell is well-formed. pub fn well_formed( &self, prev_spells: &BTreeMap, usize)>, @@ -102,10 +115,12 @@ impl NormalizedSpell { true } + /// Return the list of apps in the spell. pub fn apps(&self) -> Vec { self.app_public_inputs.keys().cloned().collect() } + /// Convert normalized spell to [`charms_data::Transaction`]. pub fn to_tx( &self, prev_spells: &BTreeMap, usize)>, @@ -138,7 +153,8 @@ impl NormalizedSpell { } } - pub fn is_correct( + /// Check if the spell is correct. + pub(crate) fn is_correct( &self, prev_txs: &Vec, app_contract_vks: &Vec<(App, AppContractVK)>, @@ -177,7 +193,8 @@ impl NormalizedSpell { true } - fn charms(&self, n_charms: &NormalizedCharms) -> Charms { + /// Return [`charms_data::Charms`] for the given [`NormalizedCharms`]. + pub fn charms(&self, n_charms: &NormalizedCharms) -> Charms { let apps = self.apps(); n_charms .iter() @@ -186,6 +203,7 @@ impl NormalizedSpell { } } +/// Extract spells from previous transactions. pub fn prev_spells( prev_txs: &Vec, spell_vk: &str, diff --git a/charms-spell-checker/src/tx.rs b/charms-spell-checker/src/tx.rs index 73fb9d4..789b73f 100644 --- a/charms-spell-checker/src/tx.rs +++ b/charms-spell-checker/src/tx.rs @@ -10,6 +10,8 @@ use serde::Serialize; use sp1_primitives::io::SP1PublicValues; use sp1_verifier::Groth16Verifier; +/// Extract a [`NormalizedSpell`] from a transaction and verify it. +/// Incorrect spells are rejected. pub fn extract_spell( tx: &bitcoin::Transaction, spell_vk: &str, @@ -78,7 +80,7 @@ pub fn extract_spell( Groth16Verifier::verify( &proof, - to_sp1_pv(&(spell_vk, &spell)).as_slice(), + to_sp1_pv(spell.version, &(spell_vk, &spell)).as_slice(), spell_vk, groth16_vk, ) @@ -97,8 +99,19 @@ fn vks(spell_version: u32, spell_vk: &str) -> anyhow::Result<(&str, &[u8])> { const V0_GROTH16_VK_BYTES: &'static [u8] = include_bytes!("../vk/v0/groth16_vk.bin"); -fn to_sp1_pv(t: &T) -> SP1PublicValues { +fn to_sp1_pv(spell_version: u32, t: &T) -> SP1PublicValues { let mut pv = SP1PublicValues::new(); - pv.write_slice(util::write(t).unwrap().as_slice()); + match spell_version { + CURRENT_VERSION => { + // we commit to CBOR-encoded tuple `(spell_vk, n_spell)` + pv.write_slice(util::write(t).unwrap().as_slice()); + } + V0 => { + // we used to commit to the tuple `(spell_vk, n_spell)`, which was serialized internally + // by SP1 + pv.write(t); + } + _ => unreachable!(), + } pv } diff --git a/src/app.rs b/src/app.rs index b59d5f4..dcd4c52 100644 --- a/src/app.rs +++ b/src/app.rs @@ -29,7 +29,7 @@ impl Prover { } } - pub fn prove( + pub(crate) fn prove( &self, app_binaries: &BTreeMap>, tx: Transaction, diff --git a/src/bin/charms-spell-checker b/src/bin/charms-spell-checker index e1d07e7..0b73587 100755 Binary files a/src/bin/charms-spell-checker and b/src/bin/charms-spell-checker differ diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 19c9797..c925255 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -5,6 +5,7 @@ pub mod tx; pub mod wallet; use clap::{Args, Parser, Subcommand}; +use serde::Serialize; use std::{net::IpAddr, path::PathBuf}; #[derive(Parser)] @@ -16,11 +17,11 @@ pub struct Cli { #[derive(Args)] pub struct ServerConfig { - /// IP address to listen on, defaults to 0.0.0.0 (all) + /// IP address to listen on, defaults to 0.0.0.0 (all). #[arg(long, default_value = "0.0.0.0")] ip_addr: IpAddr, - /// Port to listen on, defaults to 17784 + /// Port to listen on, defaults to 17784. #[arg(long, default_value = "17784")] port: u16, @@ -34,35 +35,35 @@ pub struct ServerConfig { /// bitcoind RPC password. Recommended to set via RPC_PASSWORD env var. /// Use the .cookie file in the bitcoind data directory to look up the password: - /// the format is `__cookie__:password` + /// the format is `__cookie__:password`. #[arg(long, env)] rpc_password: String, } #[derive(Subcommand)] pub enum Commands { - /// Charms API Server + /// Charms API Server. Server(#[command(flatten)] ServerConfig), - /// Work with spells + /// Work with spells. Spell { #[command(subcommand)] command: SpellCommands, }, - /// Low level transaction-related cli + /// Work with underlying blockchain transactions. Tx { #[command(subcommand)] command: TxCommands, }, - /// Manage apps + /// Manage apps. App { #[command(subcommand)] command: AppCommands, }, - /// Wallet commands + /// Wallet commands. Wallet { #[command(subcommand)] command: WalletCommands, @@ -71,124 +72,120 @@ pub enum Commands { #[derive(Args)] pub struct SpellProveParams { + /// Spell source file (YAML/JSON). #[arg(long, default_value = "/dev/stdin")] spell: PathBuf, + /// Bitcoin transaction (hex-encoded). #[arg(long)] tx: String, + /// Pre-requisite transactions (hex-encoded) separated by commas (`,`). + /// These are the transactions that create the UTXOs that the `tx` (and the spell) spends. + /// If the spell has any reference UTXOs, the transactions creating them must also be included. #[arg(long, value_delimiter = ',')] prev_txs: Vec, + /// Path to the app binaries (RISC-V ELF files) referenced by the spell. #[arg(long, value_delimiter = ',')] app_bins: Vec, + /// UTXO ID of the funding transaction output (txid:vout). + /// This UTXO will be spent to pay the fees (at the `fee-rate` per vB) for the commit and spell + /// transactions. The rest of the value will be returned to the `change-address`. #[arg(long)] funding_utxo_id: String, + /// Value of the funding UTXO in sats. #[arg(long)] funding_utxo_value: u64, - #[arg(long)] - change_address: String, - #[arg(long)] - fee_rate: f64, -} - -#[derive(Args)] -pub struct SpellRenderParams { - #[arg(long, default_value = "/dev/stdin")] - formula: PathBuf, + /// Address to send the change to. #[arg(long)] - tx: String, - - #[arg(long, value_delimiter = ',')] - prev_txs: Vec, + change_address: String, - #[arg(long, value_delimiter = ',')] - app_vks: Vec, + /// Fee rate in sats/vB. + #[arg(long, default_value = "2.0")] + fee_rate: f64, } #[derive(Subcommand)] pub enum SpellCommands { + /// Prove a spell. Prove(#[command(flatten)] SpellProveParams), - Render(#[command(flatten)] SpellRenderParams), -} - -#[derive(Args)] -pub struct TxAddSpellParams { - #[arg(long)] - tx: String, - #[arg(long, value_delimiter = ',')] - prev_txs: Vec, - #[arg(long)] - funding_utxo_id: String, - #[arg(long)] - funding_utxo_value: u64, - #[arg(long)] - change_address: String, - #[arg(long)] - fee_rate: f64, } #[derive(Subcommand)] pub enum TxCommands { - AddSpell(#[command(flatten)] TxAddSpellParams), + /// Show the spell in a transaction. If the transaction has a spell and its valid proof, it + /// will be printed to stdout. ShowSpell { + /// Hex-encoded transaction. #[arg(long)] tx: String, + /// Output in JSON format (default is YAML). + #[arg(long)] + json: bool, }, } #[derive(Subcommand)] pub enum AppCommands { - /// Create a new app + /// Create a new app. New { /// Name of the app. Directory will be created. name: String, }, - /// Build the app + /// Build the app. Build, - /// Show verification key for an app + /// Show verification key for an app. Vk { - /// Path to the app's RISC-V binary + /// Path to the app's RISC-V binary. path: Option, }, /// Test the app for a spell. Run { - /// Path to spell source file (YAML/JSON) + /// Path to spell source file (YAML/JSON). #[arg(long, default_value = "/dev/stdin")] spell: PathBuf, - /// Path to the app's RISC-V binary + /// Path to the app's RISC-V binary. path: Option, }, } #[derive(Subcommand)] pub enum WalletCommands { - /// List outputs with charms + /// List outputs with charms in the user's wallet. List(#[command(flatten)] WalletListParams), + /// Cast a spell. + /// Creates a spell, creates the underlying Bitcoin transaction, proves the spell, creates the + /// commit transaction. Signs both the commit and spell transactions with the user's wallet. + /// Returns the hex-encoded signed commit and spell transactions. Cast(#[command(flatten)] WalletCastParams), } #[derive(Args)] pub struct WalletListParams { + /// Output in JSON format (default is YAML) #[arg(long)] json: bool, } #[derive(Args)] pub struct WalletCastParams { - /// Path to spell source file (YAML/JSON) + /// Path to spell source file (YAML/JSON). #[arg(long, default_value = "/dev/stdin")] spell: PathBuf, + /// Path to the apps' RISC-V binaries. #[arg(long, value_delimiter = ',')] app_bins: Vec, + /// Funding UTXO ID (`txid:vout`). #[arg(long)] funding_utxo_id: String, + /// Fee rate in sats/vB. #[arg(long, default_value = "2.0")] fee_rate: f64, } @@ -200,11 +197,9 @@ pub async fn run() -> anyhow::Result<()> { Commands::Server(server_config) => server::server(server_config).await, Commands::Spell { command } => match command { SpellCommands::Prove(params) => spell::prove(params), - SpellCommands::Render(params) => spell::render(params), }, Commands::Tx { command } => match command { - TxCommands::AddSpell(params) => tx::tx_add_spell(params), - TxCommands::ShowSpell { tx } => tx::tx_show_spell(tx), + TxCommands::ShowSpell { tx, json } => tx::tx_show_spell(tx, json), }, Commands::App { command } => match command { AppCommands::New { name } => app::new(&name), @@ -219,6 +214,14 @@ pub async fn run() -> anyhow::Result<()> { } } +fn print_output(output: &T, json: bool) -> anyhow::Result<()> { + match json { + true => serde_json::to_writer_pretty(std::io::stdout(), &output)?, + false => serde_yaml::to_writer(std::io::stdout(), &output)?, + }; + Ok(()) +} + #[cfg(test)] mod test { #[test] diff --git a/src/cli/spell.rs b/src/cli/spell.rs index fbcd3bc..132c372 100644 --- a/src/cli/spell.rs +++ b/src/cli/spell.rs @@ -1,10 +1,10 @@ use crate::{ app, - cli::{SpellProveParams, SpellRenderParams}, + cli::SpellProveParams, spell, spell::Spell, tx::{add_spell, txs_by_txid}, - utils, SPELL_VK, + utils, }; use anyhow::{anyhow, ensure, Result}; use bitcoin::{ @@ -93,7 +93,6 @@ pub fn do_prove( &binaries, app_private_inputs, prev_txs.values().cloned().collect(), - SPELL_VK, )?; // Serialize spell into CBOR @@ -162,7 +161,3 @@ fn align_spell_to_tx(norm_spell: &mut NormalizedSpell, tx: &Transaction) -> Resu Ok(()) } - -pub(crate) fn render(_params: SpellRenderParams) -> Result<()> { - todo!() -} diff --git a/src/cli/tx.rs b/src/cli/tx.rs index f1486c8..d6f88fd 100644 --- a/src/cli/tx.rs +++ b/src/cli/tx.rs @@ -1,16 +1,6 @@ -use crate::{ - cli::TxAddSpellParams, - tx, - tx::{add_spell, txs_by_txid}, -}; -use anyhow::{anyhow, ensure, Result}; -use bitcoin::{ - consensus::encode::{deserialize_hex, serialize_hex}, - Amount, FeeRate, OutPoint, Transaction, -}; -use charms_data::util; -use charms_spell_checker::{NormalizedSpell, Proof}; -use std::str::FromStr; +use crate::{cli, tx}; +use anyhow::{anyhow, Result}; +use bitcoin::{consensus::encode::deserialize_hex, OutPoint, Transaction}; pub(crate) fn parse_outpoint(s: &str) -> Result { let parts: Vec<&str> = s.split(':').collect(); @@ -21,69 +11,11 @@ pub(crate) fn parse_outpoint(s: &str) -> Result { Ok(OutPoint::new(parts[0].parse()?, parts[1].parse()?)) } -pub fn tx_add_spell( - TxAddSpellParams { - tx, - prev_txs, - funding_utxo_id, - funding_utxo_value, - change_address, - fee_rate, - }: TxAddSpellParams, -) -> Result<()> { - // Read spell data from stdin - let spell_and_proof: (NormalizedSpell, Proof) = util::read(std::io::stdin())?; - - // Serialize spell into CBOR - let spell_data = util::write(&spell_and_proof)?; - - // Parse transaction from hex - let tx = deserialize_hex::(&tx)?; - - // Parse funding UTXO - let funding_utxo = parse_outpoint(&funding_utxo_id)?; - - // Parse amount - let funding_utxo_value = Amount::from_sat(funding_utxo_value); - - // Parse change address into ScriptPubkey - let change_script_pubkey = bitcoin::Address::from_str(&change_address)? - .assume_checked() - .script_pubkey(); - - // Parse fee rate - let fee_rate = FeeRate::from_sat_per_kwu((fee_rate * 1000.0 / 4.0) as u64); - - let prev_txs = txs_by_txid(prev_txs)?; - ensure!(tx - .input - .iter() - .all(|input| prev_txs.contains_key(&input.previous_output.txid))); - - // Call the add_spell function - let transactions = add_spell( - tx, - &spell_data, - funding_utxo, - funding_utxo_value, - change_script_pubkey, - fee_rate, - &prev_txs, - ); - - // Convert transactions to hex and create JSON array - let hex_txs: Vec = transactions.iter().map(|tx| serialize_hex(tx)).collect(); - - // Print JSON array of transaction hexes - println!("{}", serde_json::to_string(&hex_txs)?); - Ok(()) -} - -pub fn tx_show_spell(tx: String) -> Result<()> { +pub fn tx_show_spell(tx: String, json: bool) -> Result<()> { let tx = deserialize_hex::(&tx)?; match tx::spell(&tx) { - Some(spell) => serde_yaml::to_writer(std::io::stdout(), &spell)?, + Some(spell) => cli::print_output(&spell, json)?, None => eprintln!("No spell found in the transaction"), } diff --git a/src/cli/wallet.rs b/src/cli/wallet.rs index b9a4b30..f1bb031 100644 --- a/src/cli/wallet.rs +++ b/src/cli/wallet.rs @@ -1,9 +1,11 @@ use crate::{ + cli, cli::{spell::do_prove, WalletCastParams, WalletListParams}, - spell::{str_index, Input, KeyedCharms, Output, Spell}, + spell::{Input, KeyedCharms, Output, Spell}, tx, tx::txs_by_txid, utils, + utils::str_index, }; use anyhow::{ensure, Result}; use bitcoin::{ @@ -51,16 +53,8 @@ pub fn list(params: WalletListParams) -> Result<()> { let unspent_charms_outputs = outputs_with_charms(b_list_unspent)?; - match params.json { - true => Ok(serde_json::to_writer_pretty( - std::io::stdout(), - &unspent_charms_outputs, - )?), - false => Ok(serde_yaml::to_writer( - std::io::stdout(), - &unspent_charms_outputs, - )?), - } + cli::print_output(&unspent_charms_outputs, params.json)?; + Ok(()) } fn outputs_with_charms(b_list_unspent: Vec) -> Result { diff --git a/src/lib.rs b/src/lib.rs index ab2a7cd..d92bc8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ pub mod utils; pub const SPELL_CHECKER_BINARY: &[u8] = include_bytes!("./bin/charms-spell-checker"); /// Verification key for the `charms-spell-checker` binary. -pub const SPELL_VK: &str = "0x00d42ff3f423b00461bac02ac0071b33f9fb2b81a8239b948a1f7c414763ec2c"; +pub const SPELL_VK: &str = "0x009f38f590ebca4c08c1e97b4064f39e4cd336eea4069669c5f5170a38a1ff97"; #[cfg(test)] mod test { diff --git a/src/spell.rs b/src/spell.rs index 3192655..955ecd6 100644 --- a/src/spell.rs +++ b/src/spell.rs @@ -1,4 +1,4 @@ -use crate::{app, SPELL_CHECKER_BINARY}; +use crate::{app, utils, SPELL_CHECKER_BINARY, SPELL_VK}; use anyhow::{anyhow, ensure, Error}; use bitcoin::{address::NetworkUnchecked, Address}; use charms_data::{util, App, Charms, Data, Transaction, UtxoId, B32}; @@ -33,27 +33,36 @@ pub struct Output { pub charms: Option, } -/// Defines how spells are represented on the wire, +/// Defines how spells are represented in their source form and in CLI outputs, /// in both human-friendly (JSON/YAML) and machine-friendly (CBOR) formats. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Spell { + /// Version of the protocol. pub version: u32, + /// Apps used in the spell. Map of `$KEY: App`. + /// Keys are arbitrary strings. They just need to be unique (inside the spell). pub apps: BTreeMap, + /// Public inputs to the apps for this spell. Map of `$KEY: Data`. #[serde(skip_serializing_if = "Option::is_none")] pub public_inputs: Option>, + /// Private inputs to the apps for this spell. Map of `$KEY: Data`. #[serde(skip_serializing_if = "Option::is_none")] pub private_inputs: Option>, + /// Transaction inputs. pub ins: Vec, + /// Reference inputs. #[serde(skip_serializing_if = "Option::is_none")] pub refs: Option>, + /// Transaction outputs. pub outs: Vec, } impl Spell { + /// New empty spell. pub fn new() -> Self { Self { version: CURRENT_VERSION, @@ -66,6 +75,7 @@ impl Spell { } } + /// Get a [`charms_data::Transaction`] for the spell. pub fn to_tx(&self) -> anyhow::Result { let ins = self.strings_of_charms(&self.ins)?; let empty_vec = vec![]; @@ -105,6 +115,7 @@ impl Spell { .collect::>() } + /// Get a [`NormalizedSpell`] and apps' private inputs for the spell. pub fn normalized(&self) -> anyhow::Result<(NormalizedSpell, BTreeMap)> { let empty_map = BTreeMap::new(); let keyed_public_inputs = self.public_inputs.as_ref().unwrap_or(&empty_map); @@ -170,10 +181,11 @@ impl Spell { Ok((norm_spell, app_private_inputs)) } + /// De-normalize a normalized spell. pub fn denormalized(norm_spell: &NormalizedSpell) -> Self { let apps = (0..) .zip(norm_spell.app_public_inputs.keys()) - .map(|(i, app)| (str_index(&i), app.clone())) + .map(|(i, app)| (utils::str_index(&i), app.clone())) .collect(); let public_inputs = match (0..) @@ -181,7 +193,7 @@ impl Spell { .filter_map(|(i, data)| match data { data if data.is_empty() => None, data => Some(( - str_index(&i), + utils::str_index(&i), data.value().ok().expect("Data should be a Value"), )), }) @@ -227,7 +239,7 @@ impl Spell { .iter() .map(|(i, data)| { ( - str_index(i), + utils::str_index(i), data.value().ok().expect("Data should be a Value"), ) }) @@ -251,10 +263,6 @@ impl Spell { } } -pub fn str_index(i: &usize) -> String { - format!("${:04}", i) -} - fn app_inputs( keyed_apps: &BTreeMap, keyed_inputs: &BTreeMap, @@ -270,18 +278,23 @@ fn app_inputs( .collect() } +/// Prove a spell (provided as [`NormalizedSpell`]). +/// Returns the normalized spell and the proof (which is a Groth16 proof of checking if the spell is +/// correct inside a zkVM). +/// +/// Requires the binaries of the apps used in the spell, the private inputs to the apps, and the +/// pre-requisite transactions (`prev_txs`). pub fn prove( norm_spell: NormalizedSpell, app_binaries: &BTreeMap>, app_private_inputs: BTreeMap, prev_txs: Vec, - spell_vk: &str, ) -> anyhow::Result<(NormalizedSpell, Proof)> { let client = ProverClient::from_env(); let (pk, vk) = client.setup(SPELL_CHECKER_BINARY); let mut stdin = SP1Stdin::new(); - let prev_spells = charms_spell_checker::prev_spells(&prev_txs, spell_vk); + let prev_spells = charms_spell_checker::prev_spells(&prev_txs, SPELL_VK); let prover_input = SpellProverInput { self_spell_vk: vk.bytes32(), diff --git a/src/tx.rs b/src/tx.rs index 06f7064..eb973ed 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -21,15 +21,15 @@ use rand::thread_rng; use std::collections::BTreeMap; /// `add_spell` adds `spell` to `tx`: -/// 1. it builds `commit_spell_tx` transaction which creates a *committed spell* Tapscript output +/// 1. it builds `commit_tx` transaction which creates a *committed spell* Tapscript output /// 2. then appends an input spending the *committed spell* to `tx`, and adds a witness for it. /// /// `fee_rate` is used to compute the amount of sats necessary to fund the commit and spell /// transactions. /// -/// Return `[commit_spell_tx, tx]`. +/// Return `[commit_tx, tx]`. /// -/// Both `commit_spell_tx` and `tx` need to be signed. +/// Both `commit_tx` and `tx` need to be signed. pub fn add_spell( tx: Transaction, spell_data: &[u8], diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cbcf28e..a57c6de 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,6 @@ pub(crate) mod logger; + +/// Create a string representation of the index `i` in the format `$xxxx`. +pub fn str_index(i: &usize) -> String { + format!("${:04}", i) +}