diff --git a/src/main.rs b/src/main.rs index 0c72eb9..33bc843 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,6 +100,15 @@ enum Subcommands { // #[clap(about = "Generate shell completions script")] Completions(Completions), + // + // Experimental + // + #[clap( + about = "Experimental commands for fun and profit", + long_about = "Experimental new commands that are shipped with no stability guarantee. \ + They might break or be removed anytime." + )] + Lab(Lab), } #[tokio::main] @@ -138,5 +147,6 @@ async fn run_command(cli: Cli) -> Result<()> { Subcommands::Declare(cmd) => cmd.run().await, Subcommands::Deploy(cmd) => cmd.run().await, Subcommands::Completions(cmd) => cmd.run(), + Subcommands::Lab(cmd) => cmd.run(), } } diff --git a/src/subcommands/lab/mine_udc_salt.rs b/src/subcommands/lab/mine_udc_salt.rs new file mode 100644 index 0000000..d1f63e5 --- /dev/null +++ b/src/subcommands/lab/mine_udc_salt.rs @@ -0,0 +1,215 @@ +use std::time::SystemTime; + +use anyhow::Result; +use clap::Parser; +use colored::Colorize; +use starknet::core::{ + crypto::{compute_hash_on_elements, pedersen_hash}, + types::FieldElement, + utils::{normalize_address, UdcUniqueSettings, UdcUniqueness}, +}; + +/// The default UDC address: 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf. +const DEFAULT_UDC_ADDRESS: FieldElement = FieldElement::from_mont([ + 15144800532519055890, + 15685625669053253235, + 9333317513348225193, + 121672436446604875, +]); + +// Cairo string of "STARKNET_CONTRACT_ADDRESS" +const CONTRACT_ADDRESS_PREFIX: FieldElement = FieldElement::from_mont([ + 3829237882463328880, + 17289941567720117366, + 8635008616843941496, + 533439743893157637, +]); + +#[derive(Debug, Parser)] +pub struct MineUdcSalt { + #[clap( + long, + help = "Prefix bits in BINARY representation, ASSUMING 252-BIT ADDRESSES" + )] + prefix: String, + #[clap(long, help = "Suffix bits in BINARY representation")] + suffix: String, + #[clap(long, help = "Do not derive contract address from deployer address")] + not_unique: bool, + #[clap( + long, + help = "Deployer address. Needed if and only if not using --no-unique" + )] + deployer_address: Option, + #[clap(help = "Class hash")] + class_hash: FieldElement, + #[clap(help = "Raw constructor arguments (argument resolution not supported yet)")] + ctor_args: Vec, +} + +impl MineUdcSalt { + pub fn run(self) -> Result<()> { + let udc_uniqueness = match (self.not_unique, self.deployer_address) { + (true, Some(_)) => { + anyhow::bail!("--deployer-address must not be used when --not-unique is on"); + } + (false, None) => { + anyhow::bail!("--deployer-address must be used when --not-unique is off"); + } + (true, None) => UdcUniqueness::NotUnique, + (false, Some(deployer_address)) => { + eprintln!( + "{}", + "WARNING: mining without --not-unique is slower. \ + Try using --no-unique instead \ + (you need to also use this option for the deploy command)." + .bright_magenta() + ); + + UdcUniqueness::Unique(UdcUniqueSettings { + deployer_address, + udc_contract_address: DEFAULT_UDC_ADDRESS, + }) + } + }; + + if self.prefix.len() > 252 { + anyhow::bail!("invalid prefix length"); + } + if self.suffix.len() > 252 { + anyhow::bail!("invalid suffix length"); + } + + let prefix_bits = self + .suffix + .chars() + .rev() + .map(|bit| match bit { + '1' => Ok(true), + '0' => Ok(false), + _ => anyhow::bail!("invalid bit: {}", bit), + }) + .collect::>>()?; + let suffix_bits = self + .prefix + .chars() + .rev() + .map(|bit| match bit { + '1' => Ok(true), + '0' => Ok(false), + _ => anyhow::bail!("invalid bit: {}", bit), + }) + .collect::>>()?; + + let prefix_len = prefix_bits.len(); + let suffix_len = suffix_bits.len(); + + let mut bloom = [false; 252]; + bloom[..prefix_len].copy_from_slice(&prefix_bits); + bloom[(252 - suffix_len)..].copy_from_slice(&suffix_bits); + + if Self::validate_bloom(&bloom).is_err() { + anyhow::bail!("prefix/suffix out of range and impossible to mine"); + } + + let ctor_hash = compute_hash_on_elements(&self.ctor_args); + + let mut nonce = FieldElement::ZERO; + + let start_time = SystemTime::now(); + + // TODO: parallelize with rayon + let resulting_address = loop { + let (effective_salt, effective_deployer) = match &udc_uniqueness { + UdcUniqueness::NotUnique => (nonce, FieldElement::ZERO), + UdcUniqueness::Unique(settings) => ( + pedersen_hash(&settings.deployer_address, &nonce), + settings.udc_contract_address, + ), + }; + + let deployed_address = normalize_address(compute_hash_on_elements(&[ + CONTRACT_ADDRESS_PREFIX, + effective_deployer, + effective_salt, + self.class_hash, + ctor_hash, + ])); + + let address_bits = deployed_address.to_bits_le(); + + if Self::validate_address(&address_bits[..252], &bloom, prefix_len, suffix_len) { + break deployed_address; + } + + nonce += FieldElement::ONE; + }; + + let end_time = SystemTime::now(); + + let duration = end_time.duration_since(start_time)?; + + println!( + "Time spent: {}", + format!("{}s", duration.as_secs()).bright_yellow() + ); + + println!("Salt: {}", format!("{:#064x}", nonce).bright_yellow()); + println!( + "Address: {}", + format!("{:#064x}", resulting_address).bright_yellow() + ); + + Ok(()) + } + + fn validate_bloom(bloom: &[bool]) -> Result<()> { + let mut bloom_256 = [false; 256]; + bloom_256[..252].copy_from_slice(bloom); + bloom_256.reverse(); + + let bytes = bloom_256 + .chunks_exact(8) + .map(|bits| { + (if bits[0] { 128u8 } else { 0 }) + + (if bits[1] { 64u8 } else { 0 }) + + (if bits[2] { 32u8 } else { 0 }) + + (if bits[3] { 16u8 } else { 0 }) + + (if bits[4] { 8u8 } else { 0 }) + + (if bits[5] { 4u8 } else { 0 }) + + (if bits[6] { 2u8 } else { 0 }) + + (if bits[7] { 1u8 } else { 0 }) + }) + .collect::>(); + + FieldElement::from_byte_slice_be(&bytes)?; + + Ok(()) + } + + #[inline(always)] + fn validate_address( + address: &[bool], + bloom: &[bool], + prefix_len: usize, + suffix_len: usize, + ) -> bool { + for ind in 0..prefix_len { + unsafe { + if address.get_unchecked(ind) != bloom.get_unchecked(ind) { + return false; + } + } + } + + for ind in (252 - suffix_len)..252 { + unsafe { + if address.get_unchecked(ind) != bloom.get_unchecked(ind) { + return false; + } + } + } + + true + } +} diff --git a/src/subcommands/lab/mod.rs b/src/subcommands/lab/mod.rs new file mode 100644 index 0000000..612e4d9 --- /dev/null +++ b/src/subcommands/lab/mod.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +mod mine_udc_salt; +use mine_udc_salt::MineUdcSalt; + +#[derive(Debug, Parser)] +pub struct Lab { + #[clap(subcommand)] + command: Subcommands, +} + +#[derive(Debug, Subcommand)] +enum Subcommands { + #[clap(about = "Mine UDC contract deployment salt for specific address prefix and/or suffix")] + MineUdcSalt(MineUdcSalt), +} + +impl Lab { + pub fn run(self) -> Result<()> { + match self.command { + Subcommands::MineUdcSalt(cmd) => cmd.run(), + } + } +} diff --git a/src/subcommands/mod.rs b/src/subcommands/mod.rs index c4e93a9..6f3bf95 100644 --- a/src/subcommands/mod.rs +++ b/src/subcommands/mod.rs @@ -75,3 +75,6 @@ pub use call::Call; mod invoke; pub use invoke::Invoke; + +mod lab; +pub use lab::Lab;