diff --git a/Cargo.lock b/Cargo.lock index 40ff6c02be..4626ef329d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2582,8 +2582,8 @@ dependencies = [ "base64 0.21.7", "bech32", "bitcoin-internals 0.3.0", - "bitcoin-io", - "bitcoin-units", + "bitcoin-io 0.1.3", + "bitcoin-units 0.1.2", "bitcoin_hashes 0.14.0", "hex-conservative 0.2.1", "hex_lit", @@ -2620,12 +2620,40 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b854212e29b96c8f0fe04cab11d57586c8f3257de0d146c76cb3b42b3eb9118" + [[package]] name = "bitcoin-io" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +[[package]] +name = "bitcoin-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26792cd2bf245069a1c5acb06aa7ad7abe1de69b507c90b490bca81e0665d0ee" +dependencies = [ + "bitcoin-internals 0.4.0", +] + +[[package]] +name = "bitcoin-primitives" +version = "0.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5871b575b49663222d29f4435160191ed8e0abc6d97df787ffb45ad28da39b1d" +dependencies = [ + "bitcoin-internals 0.4.0", + "bitcoin-io 0.2.0", + "bitcoin-units 0.2.0", + "bitcoin_hashes 0.15.0", + "hex-conservative 0.3.0", +] + [[package]] name = "bitcoin-units" version = "0.1.2" @@ -2636,6 +2664,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-units" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61aec975b0c91f4d2ee891c6e69e6ec2b94b3791336b7b76f9f4a9cbdeae3f7" +dependencies = [ + "bitcoin-internals 0.4.0", +] + [[package]] name = "bitcoin_hashes" version = "0.13.0" @@ -2652,11 +2689,21 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ - "bitcoin-io", + "bitcoin-io 0.1.3", "hex-conservative 0.2.1", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0982261c82a50d89d1a411602afee0498b3e0debe3d36693f0c661352809639" +dependencies = [ + "bitcoin-io 0.2.0", + "hex-conservative 0.3.0", +] + [[package]] name = "bitcoincore-rpc" version = "0.19.0" @@ -3911,7 +3958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -5457,6 +5504,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex-literal" version = "0.4.1" @@ -13072,7 +13128,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.14.0", "rand 0.8.5", "secp256k1-sys 0.10.1", "serde", @@ -14286,8 +14342,8 @@ dependencies = [ "strata-asm-common", "strata-checkpoint-types", "strata-l1tx", + "strata-ol-chain-types", "strata-ol-chainstate-types", - "strata-state", "strata-test-utils", "thiserror 2.0.16", ] @@ -14303,6 +14359,7 @@ dependencies = [ "strata-asm-proto-checkpoint-txs", "strata-checkpoint-types", "strata-crypto", + "strata-identifiers", "strata-primitives", "thiserror 2.0.16", ] @@ -14346,6 +14403,7 @@ dependencies = [ "strata-asm-proto-bridge-v1", "strata-asm-proto-checkpoint-v0", "strata-l1-txfmt", + "strata-params", "strata-primitives", ] @@ -14418,7 +14476,10 @@ dependencies = [ "rand 0.8.5", "serde", "sha2 0.10.9", + "strata-btc-types", "strata-checkpoint-types", + "strata-identifiers", + "strata-params", "strata-primitives", "strata-test-utils-btc", "thiserror 2.0.16", @@ -14436,6 +14497,8 @@ dependencies = [ "strata-asm-spec", "strata-asm-stf", "strata-asm-types", + "strata-identifiers", + "strata-params", "strata-primitives", "strata-service", "strata-state", @@ -14445,6 +14508,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "strata-btc-types" +version = "0.1.0" +dependencies = [ + "arbitrary", + "bitcoin", + "bitcoin-bosd", + "borsh", + "hex", + "serde", + "sha2 0.10.9", + "strata-checkpoint-types", + "strata-identifiers", + "thiserror 2.0.16", +] + [[package]] name = "strata-btcio" version = "0.3.0-alpha.1" @@ -14491,6 +14570,7 @@ dependencies = [ "strata-eectl", "strata-ol-chain-types", "strata-ol-chainstate-types", + "strata-params", "strata-primitives", "strata-service", "strata-status", @@ -14508,6 +14588,7 @@ dependencies = [ "strata-chaintsn", "strata-ol-chain-types", "strata-ol-chainstate-types", + "strata-params", "strata-primitives", "thiserror 2.0.16", ] @@ -14520,10 +14601,13 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "strata-asm-types", + "strata-btc-types", "strata-checkpoint-types", "strata-crypto", + "strata-identifiers", "strata-ol-chain-types", "strata-ol-chainstate-types", + "strata-params", "strata-primitives", "strata-state", "strata-test-utils", @@ -14540,7 +14624,8 @@ dependencies = [ "arbitrary", "borsh", "serde", - "strata-primitives", + "strata-crypto", + "strata-identifiers", "zkaleido", ] @@ -14638,6 +14723,7 @@ dependencies = [ "rand 0.8.5", "strata-asm-types", "strata-asm-worker", + "strata-btc-types", "strata-chain-worker", "strata-chainexec", "strata-chaintsn", @@ -14646,8 +14732,10 @@ dependencies = [ "strata-db", "strata-db-store-sled", "strata-eectl", + "strata-identifiers", "strata-ol-chain-types", "strata-ol-chainstate-types", + "strata-params", "strata-primitives", "strata-state", "strata-status", @@ -14666,15 +14754,21 @@ name = "strata-crypto" version = "0.3.0-alpha.1" dependencies = [ "arbitrary", + "bincode", "bitcoin", "bitvec", "borsh", + "k256", "musig2", "rand 0.8.5", - "strata-primitives", + "secp256k1 0.29.1", + "serde", + "strata-identifiers", "strata-test-utils", "thiserror 2.0.16", "zkaleido", + "zkaleido-risc0-groth16-verifier", + "zkaleido-sp1-groth16-verifier", ] [[package]] @@ -14720,7 +14814,10 @@ dependencies = [ "serde", "serde_json", "strata-asm-types", + "strata-btc-types", "strata-checkpoint-types", + "strata-crypto", + "strata-identifiers", "strata-ol-chain-types", "strata-ol-chainstate-types", "strata-primitives", @@ -14854,6 +14951,7 @@ dependencies = [ "anyhow", "strata-common", "strata-db", + "strata-identifiers", "strata-ol-chain-types", "strata-primitives", "strata-state", @@ -14897,6 +14995,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "strata-identifiers" +version = "0.1.0" +dependencies = [ + "arbitrary", + "bincode", + "bitcoin", + "bitcoin-primitives", + "bitcoin_hashes 0.15.0", + "borsh", + "const-hex", + "hex", + "rand 0.8.5", + "serde", + "serde_json", + "sha2 0.10.9", + "thiserror 2.0.16", + "zeroize", +] + [[package]] name = "strata-key-derivation" version = "0.3.0-alpha.1" @@ -14925,12 +15043,14 @@ dependencies = [ "bitcoin", "borsh", "secp256k1 0.29.1", - "strata-asm-types", + "strata-btc-types", "strata-btcio", "strata-checkpoint-types", "strata-crypto", + "strata-identifiers", "strata-l1-txfmt", "strata-ol-chainstate-types", + "strata-params", "strata-primitives", "strata-state", "strata-test-utils", @@ -14978,12 +15098,14 @@ name = "strata-ol-chain-types" version = "0.3.0-alpha.1" dependencies = [ "arbitrary", + "bitcoin-bosd", "borsh", + "num_enum 0.7.4", "serde", - "strata-asm-types", + "strata-btc-types", "strata-crypto", - "strata-primitives", - "strata-state", + "strata-identifiers", + "strata-params", "strata-test-utils", "thiserror 2.0.16", "tracing", @@ -14997,11 +15119,28 @@ dependencies = [ "bitcoin", "borsh", "strata-asm-types", + "strata-btc-types", + "strata-ol-chain-types", "strata-primitives", "strata-state", "tracing", ] +[[package]] +name = "strata-params" +version = "0.1.0" +dependencies = [ + "bincode", + "bitcoin", + "borsh", + "serde", + "strata-btc-types", + "strata-crypto", + "strata-identifiers", + "strata-l1-txfmt", + "thiserror 2.0.16", +] + [[package]] name = "strata-primitives" version = "0.3.0-alpha.1" @@ -15012,8 +15151,6 @@ dependencies = [ "bitcoin-bosd", "borsh", "const-hex", - "digest 0.10.7", - "hex", "k256", "musig2", "num_enum 0.7.4", @@ -15022,12 +15159,14 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", + "strata-btc-types", + "strata-crypto", + "strata-identifiers", "strata-l1-txfmt", + "strata-ol-chain-types", "strata-test-utils", "thiserror 2.0.16", "zeroize", - "zkaleido-risc0-groth16-verifier", - "zkaleido-sp1-groth16-verifier", ] [[package]] @@ -15351,8 +15490,10 @@ dependencies = [ "serde", "strata-asm-common", "strata-asm-stf", - "strata-asm-types", + "strata-btc-types", "strata-checkpoint-types", + "strata-identifiers", + "strata-ol-chain-types", "strata-primitives", ] @@ -15379,8 +15520,10 @@ dependencies = [ "lru 0.12.5", "paste", "strata-asm-types", + "strata-btc-types", "strata-checkpoint-types", "strata-db", + "strata-identifiers", "strata-ol-chain-types", "strata-ol-chainstate-types", "strata-primitives", diff --git a/Cargo.toml b/Cargo.toml index 7a00cefe11..93a8f5c637 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "crates/asm/txs/bridge-v1", "crates/asm-types", "crates/asm/worker", + "crates/btc-types", "crates/btcio", "crates/chainexec", "crates/chaintsn", @@ -37,11 +38,13 @@ members = [ "crates/ee-chain-types", "crates/eectl", "crates/evmexec", + "crates/identifiers", "crates/key-derivation", "crates/l1tx", "crates/mpt", "crates/ol-chain-types", "crates/ol-chainstate-types", + "crates/params", "crates/primitives", "crates/proof-impl/checkpoint", "crates/proof-impl/cl-stf", @@ -141,6 +144,7 @@ strata-asm-txs-admin = { path = "crates/asm/txs/admin" } strata-asm-txs-bridge-v1 = { path = "crates/asm/txs/bridge-v1" } strata-asm-types = { path = "crates/asm-types" } strata-asm-worker = { path = "crates/asm/worker" } +strata-btc-types = { path = "crates/btc-types" } strata-btcio = { path = "crates/btcio" } strata-chain-worker = { path = "crates/chain-worker" } strata-chainexec = { path = "crates/chainexec" } @@ -160,12 +164,14 @@ strata-ee-acct-types = { path = "crates/ee-acct-types" } strata-ee-chain-types = { path = "crates/ee-chain-types" } strata-eectl = { path = "crates/eectl" } strata-evmexec = { path = "crates/evmexec" } +strata-identifiers = { path = "crates/identifiers" } strata-key-derivation = { path = "crates/key-derivation" } strata-l1tx = { path = "crates/l1tx" } strata-mmr = { path = "crates/util/mmr" } strata-mpt = { path = "crates/mpt" } strata-ol-chain-types = { path = "crates/ol-chain-types" } strata-ol-chainstate-types = { path = "crates/ol-chainstate-types" } +strata-params = { path = "crates/params" } strata-primitives = { path = "crates/primitives" } strata-proofimpl-checkpoint = { path = "crates/proof-impl/checkpoint" } strata-proofimpl-cl-stf = { path = "crates/proof-impl/cl-stf" } @@ -322,14 +328,17 @@ bdk_esplora = { version = "0.20.1", features = [ ], default-features = false } bdk_wallet = "1.0.0" bincode = "1.3" -bitcoin = { version = "0.32.6", features = ["serde"] } +bitcoin = { version = "0.32.7", features = ["serde"] } bitcoin-bosd = { version = "0.4.0", default-features = false } +bitcoin_hashes = "0.15" +bitcoin-primitives = "0.101" bitcoind-async-client = "0.1.1" bitvec = "1.0.1" borsh = { version = "1.5.0", features = ["derive"] } bytes = "1.6.0" cfg-if = "1.0.0" chrono = "0.4.38" +const-hex = "1.14" clap = { version = "4.5.39", default-features = false, features = ["derive"] } criterion = { version = "0.7.0", features = ["html_reports"] } deadpool = "0.12.1" diff --git a/bin/alpen-cli/src/cmd/deposit.rs b/bin/alpen-cli/src/cmd/deposit.rs index 5518ac6f50..0d982a92db 100644 --- a/bin/alpen-cli/src/cmd/deposit.rs +++ b/bin/alpen-cli/src/cmd/deposit.rs @@ -16,7 +16,7 @@ use indicatif::ProgressBar; use rand_core::OsRng; use shrex::encode; use strata_cli_common::errors::{DisplayableError, DisplayedError}; -use strata_primitives::crypto::even_kp; +use strata_crypto::schnorr::even_kp; use crate::{ alpen::AlpenWallet, diff --git a/bin/prover-client/src/errors.rs b/bin/prover-client/src/errors.rs index 9cc11b8783..1bd149f49f 100644 --- a/bin/prover-client/src/errors.rs +++ b/bin/prover-client/src/errors.rs @@ -1,5 +1,5 @@ use strata_db::DbError; -use strata_primitives::proof::ProofKey; +use strata_crypto::proof_vk::ProofKey; use thiserror::Error; use zkaleido::ZkVmError; diff --git a/bin/prover-client/src/operators/mod.rs b/bin/prover-client/src/operators/mod.rs index bbc6213d63..1256ffeed8 100644 --- a/bin/prover-client/src/operators/mod.rs +++ b/bin/prover-client/src/operators/mod.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use strata_db::traits::ProofDatabase; use strata_db_store_sled::prover::ProofDBSled; -use strata_primitives::proof::{ProofContext, ProofKey}; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use tokio::sync::Mutex; use tracing::{error, info, instrument}; use zkaleido::{ZkVmHost, ZkVmProgram}; diff --git a/bin/prover-client/src/prover_manager.rs b/bin/prover-client/src/prover_manager.rs index 4eb13216bb..dfbc73cb25 100644 --- a/bin/prover-client/src/prover_manager.rs +++ b/bin/prover-client/src/prover_manager.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use strata_db::traits::ProofDatabase; use strata_db_store_sled::prover::ProofDBSled; -use strata_primitives::proof::{ProofContext, ProofKey, ProofZkVm}; +use strata_crypto::proof_vk::{ProofContext, ProofKey, ProofZkVm}; use tokio::{spawn, sync::Mutex, time::sleep}; use tracing::{debug, error, info, warn}; @@ -260,7 +260,7 @@ fn handle_checkpoint_error(chkpt_err: CheckpointError) -> ProvingTaskError { mod tests { use std::collections::HashMap; - use strata_primitives::proof::{ProofContext, ProofZkVm}; + use strata_crypto::proof_vk::{ProofContext, ProofZkVm}; use strata_rpc_types::ProofKey; use super::{handle_task_error, ProverManagerConfig, ProvingTaskError, ProvingTaskStatus}; diff --git a/bin/prover-client/src/task_tracker.rs b/bin/prover-client/src/task_tracker.rs index 98b8ba773d..98884a425f 100644 --- a/bin/prover-client/src/task_tracker.rs +++ b/bin/prover-client/src/task_tracker.rs @@ -2,7 +2,7 @@ use std::collections::{hash_map::Entry, HashMap, HashSet}; use strata_db::traits::ProofDatabase; use strata_db_store_sled::prover::ProofDBSled; -use strata_primitives::proof::{ProofContext, ProofKey, ProofZkVm}; +use strata_crypto::proof_vk::{ProofContext, ProofKey, ProofZkVm}; use tracing::{info, warn}; use crate::{errors::ProvingTaskError, status::ProvingTaskStatus}; diff --git a/bin/strata-client/src/errors.rs b/bin/strata-client/src/errors.rs index 9865c9ceb3..e87faef645 100644 --- a/bin/strata-client/src/errors.rs +++ b/bin/strata-client/src/errors.rs @@ -2,7 +2,7 @@ use std::io; use alloy_rpc_types::engine::JwtError; use format_serde_error::SerdeError; -use strata_primitives::params::ParamsError; +use strata_params::ParamsError; use thiserror::Error; #[derive(Debug, Error)] diff --git a/bin/strata-client/src/main.rs b/bin/strata-client/src/main.rs index 1b5cb64357..0d88f2adcd 100644 --- a/bin/strata-client/src/main.rs +++ b/bin/strata-client/src/main.rs @@ -39,7 +39,7 @@ use strata_db::{ }; use strata_eectl::engine::{ExecEngineCtl, L2BlockRef}; use strata_evmexec::{engine::RpcExecEngineCtl, EngineRpcClient}; -use strata_primitives::params::{Params, ProofPublishMode}; +use strata_params::{Params, ProofPublishMode}; use strata_rpc_api::{ StrataAdminApiServer, StrataApiServer, StrataDebugApiServer, StrataSequencerApiServer, }; diff --git a/bin/strata-client/src/network.rs b/bin/strata-client/src/network.rs index 7132925813..aedc61bc20 100644 --- a/bin/strata-client/src/network.rs +++ b/bin/strata-client/src/network.rs @@ -5,7 +5,7 @@ use std::{ fs, }; -use strata_primitives::params::RollupParams; +use strata_params::RollupParams; use tracing::warn; /// Rollup params we initialize with if not overridden. Optionally set at compile time. @@ -49,7 +49,7 @@ pub(crate) fn get_envvar_params() -> anyhow::Result> { #[cfg(test)] mod tests { - use strata_primitives::params::RollupParams; + use strata_params::RollupParams; use super::DEFAULT_NETWORK_ROLLUP_PARAMS; diff --git a/bin/strata-dbtool/src/cmd/chainstate.rs b/bin/strata-dbtool/src/cmd/chainstate.rs index 42a1874355..961851b4ba 100644 --- a/bin/strata-dbtool/src/cmd/chainstate.rs +++ b/bin/strata-dbtool/src/cmd/chainstate.rs @@ -11,7 +11,7 @@ use strata_db::{ types::IntentStatus, }; use strata_ol_chainstate_types::WriteBatch; -use strata_primitives::l2::L2BlockId; +use strata_identifiers::L2BlockId; use super::{ checkpoint::get_latest_checkpoint_entry, diff --git a/bin/strata-dbtool/src/cmd/l1.rs b/bin/strata-dbtool/src/cmd/l1.rs index 5a19778fc7..d675481871 100644 --- a/bin/strata-dbtool/src/cmd/l1.rs +++ b/bin/strata-dbtool/src/cmd/l1.rs @@ -1,7 +1,7 @@ use argh::FromArgs; use strata_cli_common::errors::{DisplayableError, DisplayedError}; use strata_db::traits::{DatabaseBackend, L1Database}; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use crate::{ cli::OutputFormat, diff --git a/bin/strata-dbtool/src/cmd/writer.rs b/bin/strata-dbtool/src/cmd/writer.rs index 3cc10314fc..3304f86e42 100644 --- a/bin/strata-dbtool/src/cmd/writer.rs +++ b/bin/strata-dbtool/src/cmd/writer.rs @@ -1,7 +1,7 @@ use argh::FromArgs; use strata_cli_common::errors::{DisplayableError, DisplayedError}; use strata_db::traits::L1WriterDatabase; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::checkpoint::{get_checkpoint_at_index, get_checkpoint_index_range}; use crate::{ diff --git a/bin/strata-dbtool/src/output/broadcaster.rs b/bin/strata-dbtool/src/output/broadcaster.rs index 4e00d50f66..6bc129b76b 100644 --- a/bin/strata-dbtool/src/output/broadcaster.rs +++ b/bin/strata-dbtool/src/output/broadcaster.rs @@ -3,7 +3,7 @@ use std::fmt; use hex::encode_to_slice; use serde::Serialize; use strata_db::types::L1TxStatus; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::{helpers::porcelain_field, traits::Formattable}; diff --git a/bin/strata-dbtool/src/output/l1.rs b/bin/strata-dbtool/src/output/l1.rs index 85d5cc8afc..2f90f3e3bf 100644 --- a/bin/strata-dbtool/src/output/l1.rs +++ b/bin/strata-dbtool/src/output/l1.rs @@ -1,7 +1,7 @@ //! L1 block formatting implementations use strata_asm_types::{L1Tx, ProtocolOperation}; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use super::{checkpoint::format_signed_checkpoint, helpers::porcelain_field, traits::Formattable}; diff --git a/crates/asm-types/Cargo.toml b/crates/asm-types/Cargo.toml index 65bf671170..8a5d9f5edf 100644 --- a/crates/asm-types/Cargo.toml +++ b/crates/asm-types/Cargo.toml @@ -7,7 +7,10 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true +strata-identifiers.workspace = true +strata-params.workspace = true strata-primitives.workspace = true arbitrary.workspace = true diff --git a/crates/asm-types/src/block.rs b/crates/asm-types/src/block.rs index 678aeb8d97..0f229be912 100644 --- a/crates/asm-types/src/block.rs +++ b/crates/asm-types/src/block.rs @@ -1,12 +1,8 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - buf::Buf32, - l1::{L1BlockCommitment, L1BlockId}, -}; - -use super::{L1HeaderRecord, L1Tx}; +use strata_identifiers::{Buf32, L1BlockCommitment, L1BlockId}; +use strata_btc_types::{L1HeaderRecord, L1Tx}; /// Reference to a Bitcoin transaction by block ID and transaction index. #[derive( @@ -45,89 +41,3 @@ impl From<(&L1BlockId, u32)> for L1TxRef { Self::new(*val.0, val.1) } } - -/// Bitcoin-anchored block manifest containing header record and transactions. -#[derive( - Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Deserialize, Serialize, -)] -pub struct L1BlockManifest { - /// The actual l1 record - record: L1HeaderRecord, - - /// List of interesting transactions we took out. - txs: Vec, - - /// Epoch, which was used to generate this manifest. - epoch: u64, - - /// Block height. - height: u64, -} - -impl L1BlockManifest { - pub fn new(record: L1HeaderRecord, txs: Vec, epoch: u64, height: u64) -> Self { - Self { - record, - txs, - epoch, - height, - } - } - - pub fn record(&self) -> &L1HeaderRecord { - &self.record - } - - pub fn txs(&self) -> &[L1Tx] { - &self.txs - } - - pub fn txs_vec(&self) -> &Vec { - &self.txs - } - - pub fn epoch(&self) -> u64 { - self.epoch - } - - pub fn blkid(&self) -> &L1BlockId { - &self.record.blkid - } - - #[deprecated(note = "use .blkid()")] - pub fn block_hash(&self) -> L1BlockId { - *self.record.blkid() - } - - pub fn height(&self) -> u64 { - self.height - } - - pub fn header(&self) -> &[u8] { - self.record.buf() - } - - pub fn txs_root(&self) -> Buf32 { - *self.record.wtxs_root() - } - - pub fn get_prev_blockid(&self) -> L1BlockId { - self.record().parent_blkid() - } - - pub fn into_record(self) -> L1HeaderRecord { - self.record - } -} - -impl From for L1BlockCommitment { - fn from(value: L1BlockManifest) -> Self { - Self::from_height_u64(value.height(), *value.blkid()).expect("height should be valid") - } -} - -impl From<&L1BlockManifest> for L1BlockCommitment { - fn from(value: &L1BlockManifest) -> Self { - Self::from_height_u64(value.height(), *value.blkid()).expect("height should be valid") - } -} diff --git a/crates/asm-types/src/header.rs b/crates/asm-types/src/header.rs index 6e8fc65dac..8dcc394079 100644 --- a/crates/asm-types/src/header.rs +++ b/crates/asm-types/src/header.rs @@ -1,7 +1,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{buf::Buf32, hash, l1::L1BlockId}; +use strata_identifiers::{Buf32, L1BlockId, hash}; /// Header and the wtxs root. /// diff --git a/crates/asm-types/src/header_verification.rs b/crates/asm-types/src/header_verification.rs index a947100bbf..ea39aa7343 100644 --- a/crates/asm-types/src/header_verification.rs +++ b/crates/asm-types/src/header_verification.rs @@ -4,12 +4,9 @@ use bitcoin::{ }; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - buf::Buf32, - hash::compute_borsh_hash, - l1::{BtcParams, L1BlockCommitment, L1BlockId}, - params::GenesisL1View, -}; +use strata_identifiers::{Buf32, L1BlockCommitment, L1BlockId, hash::compute_borsh_hash}; +use strata_params::GenesisL1View; +use strata_primitives::l1::BtcParams; use thiserror::Error; use super::{timestamp_store::TimestampStore, utils::compute_block_hash, BtcWork}; @@ -216,7 +213,7 @@ impl HeaderVerificationState { self.last_verified_block.height().to_consensus_u32() + 1, ) .expect("height + 1 should be valid"); - self.last_verified_block = L1BlockCommitment::new(next_height, block_hash_raw.into()); + self.last_verified_block = L1BlockCommitment::new_btc(next_height, block_hash_raw.into()); // Update the timestamps self.update_timestamps(header.time); diff --git a/crates/asm-types/src/inclusion_proof.rs b/crates/asm-types/src/inclusion_proof.rs index 68f124ecba..8b13789179 100644 --- a/crates/asm-types/src/inclusion_proof.rs +++ b/crates/asm-types/src/inclusion_proof.rs @@ -1,149 +1 @@ -use std::marker::PhantomData; -use arbitrary::Arbitrary; -use bitcoin::Transaction; -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; -use strata_primitives::{buf::Buf32, hash::sha256d, utils::get_cohashes}; - -use super::{TxIdComputable, TxIdMarker, WtxIdMarker}; - -/// A generic proof structure that can handle any kind of transaction ID (e.g., -/// [`Txid`](bitcoin::Txid) or [`Wtxid`](bitcoin::Wtxid)) by delegating the ID computation to the -/// provided type `T` that implements [`TxIdComputable`]. -#[derive( - Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct L1TxInclusionProof { - /// The 0-based position (index) of the transaction within the block's transaction list - /// for which this proof is generated. - position: u32, - - /// The intermediate hashes (sometimes called "siblings") needed to reconstruct the Merkle root - /// when combined with the target transaction's own ID. These are the Merkle tree nodes at - /// each step that pair with the current hash (either on the left or the right) to produce - /// the next level of the tree. - cohashes: Vec, - - /// A marker that preserves the association with type `T`, which implements - /// [`TxIdComputable`]. This ensures the proof logic depends on the correct - /// transaction ID computation ([`Txid`](bitcoin::Txid) vs.[`Wtxid`](bitcoin::Wtxid)) for the - /// lifetime of the proof. - _marker: PhantomData, -} - -impl L1TxInclusionProof { - pub fn new(position: u32, cohashes: Vec) -> Self { - Self { - position, - cohashes, - _marker: PhantomData, - } - } - - pub fn cohashes(&self) -> &[Buf32] { - &self.cohashes - } - - pub fn position(&self) -> u32 { - self.position - } -} - -impl L1TxInclusionProof { - /// Generates the proof for a transaction at the specified index in the list of - /// transactions, using `T` to compute the transaction IDs. - pub fn generate(transactions: &[Transaction], idx: u32) -> Self { - let txids = transactions - .iter() - .enumerate() - .map(|(idx, tx)| T::compute_id(tx, idx)) - .collect::>(); - let (cohashes, _txroot) = get_cohashes(&txids, idx); - L1TxInclusionProof::new(idx, cohashes) - } - - /// Computes the merkle root for the given `transaction` using the proof's cohashes. - pub fn compute_root(&self, transaction: &Transaction) -> Buf32 { - // `cur_hash` represents the intermediate hash at each step. After all cohashes are - // processed `cur_hash` becomes the root hash - let mut cur_hash = T::compute_id(transaction, self.position as usize).0; - - let mut pos = self.position(); - for cohash in self.cohashes() { - let mut buf = [0u8; 64]; - if pos & 1 == 0 { - buf[0..32].copy_from_slice(&cur_hash); - buf[32..64].copy_from_slice(cohash.as_ref()); - } else { - buf[0..32].copy_from_slice(cohash.as_ref()); - buf[32..64].copy_from_slice(&cur_hash); - } - cur_hash = sha256d(&buf).0; - pos >>= 1; - } - Buf32::from(cur_hash) - } - - /// Verifies the inclusion proof of the given `transaction` against the provided merkle `root`. - pub fn verify(&self, transaction: &Transaction, root: Buf32) -> bool { - self.compute_root(transaction) == root - } -} - -/// Convenience type alias for the [`Txid`](bitcoin::Txid)-based proof. -pub type L1TxProof = L1TxInclusionProof; - -/// Convenience type alias for the [`Wtxid`](bitcoin::Wtxid)-based proof. -pub type L1WtxProof = L1TxInclusionProof; - -#[cfg(test)] -mod tests { - use bitcoin::hashes::Hash; - use rand::{thread_rng, Rng}; - use strata_primitives::buf::Buf32; - use strata_test_utils_btc::segment::BtcChainSegment; - - use super::*; - - #[test] - fn test_l1_tx_proof() { - let btc_chain = BtcChainSegment::load(); - let block = btc_chain.get_block_at(40_321).unwrap(); - let merkle_root: Buf32 = block.header.merkle_root.to_byte_array().into(); - let txs = &block.txdata; - - for (idx, tx) in txs.iter().enumerate() { - let proof = L1TxProof::generate(txs, idx as u32); - assert!(proof.verify(tx, merkle_root)); - } - } - - #[test] - fn test_l1_tx_proof_2() { - let block = BtcChainSegment::load_full_block(); - let merkle_root: Buf32 = block.header.merkle_root.to_byte_array().into(); - let txs = &block.txdata; - - let mut rng = thread_rng(); - let idx = rng.gen_range(0..=txs.len()); - let proof = L1TxProof::generate(txs, idx as u32); - assert!(proof.verify(&txs[idx], merkle_root)); - } - - #[test] - fn test_l1_wtx_proof() { - let block = BtcChainSegment::load_full_block(); - let txs = &block.txdata; - let wtx_root = block.witness_root().unwrap().to_byte_array().into(); - - let idx = 0; - let proof = L1WtxProof::generate(txs, idx as u32); - assert!(proof.verify(&txs[idx], wtx_root)); - - let mut rng = thread_rng(); - let idx = rng.gen_range(1..=txs.len()); - let proof = L1WtxProof::generate(txs, idx as u32); - assert!(proof.verify(&txs[idx], wtx_root)); - } -} diff --git a/crates/asm-types/src/ops.rs b/crates/asm-types/src/ops.rs index b71914af13..640c44f77f 100644 --- a/crates/asm-types/src/ops.rs +++ b/crates/asm-types/src/ops.rs @@ -6,181 +6,5 @@ use digest::Digest; use serde::{Deserialize, Serialize}; use sha2::Sha256; use strata_checkpoint_types::SignedCheckpoint; -use strata_primitives::{ - buf::Buf32, - l1::{BitcoinAmount, OutputRef}, -}; - -/// Commits to a DA blob. This is just the hash of the DA blob. -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - BorshSerialize, - BorshDeserialize, - Arbitrary, - Serialize, - Deserialize, -)] -pub struct DaCommitment(Buf32); - -impl DaCommitment { - /// Creates a commitment from a DA payload buf. - pub fn from_buf(buf: &[u8]) -> Self { - Self::from_chunk_iter([buf].into_iter()) - } - - /// Creates a commitment from a series of contiguous chunks of a single DA - /// paylod buf. - /// - /// This is meant to be used when constructing a commitment from an in-situ - /// payload from a transaction, which has to be in 520-byte chunks. - pub fn from_chunk_iter<'a>(chunks: impl Iterator) -> Self { - // TODO maybe abstract this further? - let mut hasher = Sha256::new(); - for chunk in chunks { - hasher.update(chunk); - } - - let hash: [u8; 32] = hasher.finalize().into(); - Self(Buf32(hash)) - } - - pub fn as_hash(&self) -> &Buf32 { - &self.0 - } - - pub fn to_hash(&self) -> Buf32 { - self.0 - } -} - -/// Consensus level protocol operations extracted from a bitcoin transaction. -/// -/// These are submitted to the OL STF and impact state. -#[expect(clippy::large_enum_variant, reason = "used for protocol operations")] -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -pub enum ProtocolOperation { - /// Deposit Transaction - Deposit(DepositInfo), - - /// Checkpoint data - Checkpoint(SignedCheckpoint), - - /// DA blob - DaCommitment(DaCommitment), - - /// Deposit request. - /// - /// This is being removed soon as it's not really a consensus change. - DepositRequest(DepositRequestInfo), - - /// Withdrawal fulfilled by bridge operator front-payment. - WithdrawalFulfillment(WithdrawalFulfillmentInfo), - - /// Deposit utxo is spent. - DepositSpent(DepositSpendInfo), -} - -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -pub struct DepositInfo { - /// Deposit from tag output, as assigned by operators. - pub deposit_idx: u32, - - /// Bitcoin amount. - pub amt: BitcoinAmount, - - /// Output for deposit funds at rest. - pub outpoint: OutputRef, - - /// Destination address payload. - pub address: Vec, -} - -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -pub struct DepositRequestInfo { - /// amount in satoshis - pub amt: u64, - - /// tapscript control block hash for timelock script - pub take_back_leaf_hash: [u8; 32], - - /// EE address - pub address: Vec, -} - -#[derive( - Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -pub struct WithdrawalFulfillmentInfo { - /// index of deposit this fulfillment is for - pub deposit_idx: u32, - - /// assigned operator - /// TODO: maybe this is not needed - pub operator_idx: u32, - - /// amount that was actually sent on bitcoin. - /// should equal withdrawal_amount - operator fee - pub amt: BitcoinAmount, - - /// corresponding bitcoin transaction id. - pub txid: Buf32, -} - -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, -)] -pub struct DepositSpendInfo { - /// index of the deposit whose utxo is spent. - pub deposit_idx: u32, -} - -// Custom debug implementation to print txid in little endian -impl fmt::Debug for WithdrawalFulfillmentInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let txid_le = { - let mut bytes = self.txid.0; - bytes.reverse(); - bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::() - }; - - f.debug_struct("WithdrawalFulfillmentInfo") - .field("deposit_idx", &self.deposit_idx) - .field("operator_idx", &self.operator_idx) - .field("amt", &self.amt) - .field("txid", &txid_le) - .finish() - } -} - -// Custom display implementation to print txid in little endian -impl fmt::Display for WithdrawalFulfillmentInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let txid_le = { - let mut bytes = self.txid.0; - bytes.reverse(); - bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::() - }; - - write!( - f, - "WithdrawalFulfillmentInfo {{ deposit_idx: {}, operator_idx: {}, amt: {:?}, txid: {} }}", - self.deposit_idx, self.operator_idx, self.amt, txid_le - ) - } -} +use strata_identifiers::{BitcoinAmount, Buf32}; +use strata_primitives::l1::OutputRef; diff --git a/crates/asm-types/src/proof.rs b/crates/asm-types/src/proof.rs index 70c83b4647..8b13789179 100644 --- a/crates/asm-types/src/proof.rs +++ b/crates/asm-types/src/proof.rs @@ -1,49 +1 @@ -use arbitrary::Arbitrary; -use bitcoin::Transaction; -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; -use strata_primitives::buf::Buf32; -/// A trait for computing some kind of transaction ID (e.g., [`Txid`](bitcoin::Txid) or -/// [`Wtxid`](bitcoin::Wtxid)) from a [`Transaction`]. -/// -/// This trait is designed to be implemented by "marker" types that define how a transaction ID -/// should be computed. For example, [`TxIdMarker`] invokes [`Transaction::compute_txid`], and -/// [`WtxIdMarker`] invokes [`Transaction::compute_wtxid`]. This approach avoids duplicating -/// inclusion-proof or serialization logic across multiple ID computations. -pub trait TxIdComputable { - /// Computes the transaction ID for the given transaction. - /// - /// The `idx` parameter allows marker types to handle special cases such as the coinbase - /// transaction (which has a zero [`Wtxid`](bitcoin::Wtxid)) by looking up the transaction - /// index. - fn compute_id(tx: &Transaction, idx: usize) -> Buf32; -} - -/// Marker type for computing the [`Txid`](bitcoin::Txid). -#[derive( - Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct TxIdMarker; - -/// Marker type for computing the [`Wtxid`](bitcoin::Wtxid). -#[derive( - Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct WtxIdMarker; - -impl TxIdComputable for TxIdMarker { - fn compute_id(tx: &Transaction, _idx: usize) -> Buf32 { - tx.compute_txid().into() - } -} - -impl TxIdComputable for WtxIdMarker { - fn compute_id(tx: &Transaction, idx: usize) -> Buf32 { - // Coinbase transaction wtxid is hash with zeroes - if idx == 0 { - return Buf32::zero(); - } - tx.compute_wtxid().into() - } -} diff --git a/crates/asm-types/src/tx.rs b/crates/asm-types/src/tx.rs index 6b85e189db..80bd037a37 100644 --- a/crates/asm-types/src/tx.rs +++ b/crates/asm-types/src/tx.rs @@ -1,42 +1,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::l1::RawBitcoinTx; - -use super::{L1TxProof, ProtocolOperation}; - -/// Bitcoin-anchored transaction with proof and protocol operations. -#[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Arbitrary, Serialize, Deserialize, -)] -pub struct L1Tx { - // TODO: verify if we need L1TxProof or L1WtxProof - proof: L1TxProof, - tx: RawBitcoinTx, - protocol_ops: Vec, -} - -impl L1Tx { - pub fn new(proof: L1TxProof, tx: RawBitcoinTx, protocol_ops: Vec) -> Self { - Self { - proof, - tx, - protocol_ops, - } - } - - pub fn proof(&self) -> &L1TxProof { - &self.proof - } - - pub fn tx_data(&self) -> &RawBitcoinTx { - &self.tx - } - - pub fn protocol_ops(&self) -> &[ProtocolOperation] { - &self.protocol_ops - } -} +use strata_btc_types::{L1Tx, L1TxProof, ProtocolOperation}; /// Bitcoin-anchored deposit update transaction. #[derive( diff --git a/crates/asm-types/src/utils.rs b/crates/asm-types/src/utils.rs index 7e06e77108..40b45ad26e 100644 --- a/crates/asm-types/src/utils.rs +++ b/crates/asm-types/src/utils.rs @@ -1,7 +1,6 @@ use bitcoin::{block::Header, consensus::Encodable, Block}; -use strata_primitives::{buf::Buf32, hash::sha256d}; - -use super::{L1Tx, L1TxProof, ProtocolOperation}; +use strata_identifiers::{Buf32, hash::sha256d}; +use strata_btc_types::{L1Tx, L1TxProof, ProtocolOperation}; /// Returns the block hash. /// diff --git a/crates/asm/spec/Cargo.toml b/crates/asm/spec/Cargo.toml index 7f720a3a4a..29bad967f0 100644 --- a/crates/asm/spec/Cargo.toml +++ b/crates/asm/spec/Cargo.toml @@ -11,4 +11,5 @@ strata-asm-common.workspace = true strata-asm-proto-bridge-v1.workspace = true strata-asm-proto-checkpoint-v0.workspace = true strata-l1-txfmt.workspace = true +strata-params.workspace = true strata-primitives.workspace = true diff --git a/crates/asm/spec/src/lib.rs b/crates/asm/spec/src/lib.rs index 98c2abcd8b..ee6cfa79cf 100644 --- a/crates/asm/spec/src/lib.rs +++ b/crates/asm/spec/src/lib.rs @@ -10,10 +10,8 @@ use strata_asm_proto_checkpoint_v0::{ CheckpointV0Params, CheckpointV0Subproto, CheckpointV0VerificationParams, }; use strata_l1_txfmt::MagicBytes; -use strata_primitives::{ - l1::BitcoinAmount, - params::{OperatorConfig, RollupParams}, -}; +use strata_params::{OperatorConfig, RollupParams}; +use strata_primitives::l1::BitcoinAmount; /// ASM specification for the Strata protocol. /// diff --git a/crates/asm/subprotocols/bridge-v1/src/subprotocol.rs b/crates/asm/subprotocols/bridge-v1/src/subprotocol.rs index 4f9f9caeeb..330844f392 100644 --- a/crates/asm/subprotocols/bridge-v1/src/subprotocol.rs +++ b/crates/asm/subprotocols/bridge-v1/src/subprotocol.rs @@ -150,7 +150,7 @@ impl Subprotocol for BridgeV1Subproto { match msg { BridgeIncomingMsg::DispatchWithdrawal(withdrawal_cmd) => { // TODO: Pass actual L1BlockId instead of placeholder - let l1blk = L1BlockCommitment::new( + let l1blk = L1BlockCommitment::new_btc( absolute::Height::ZERO, L1BlockId::from(Buf32::zero()), ); diff --git a/crates/asm/subprotocols/checkpoint-v0/Cargo.toml b/crates/asm/subprotocols/checkpoint-v0/Cargo.toml index 4ea4c4dd58..b6effc5f22 100644 --- a/crates/asm/subprotocols/checkpoint-v0/Cargo.toml +++ b/crates/asm/subprotocols/checkpoint-v0/Cargo.toml @@ -15,6 +15,7 @@ strata-asm-proto-bridge-v1.workspace = true strata-asm-proto-checkpoint-txs.workspace = true strata-checkpoint-types.workspace = true strata-crypto.workspace = true +strata-identifiers.workspace = true strata-primitives.workspace = true thiserror.workspace = true diff --git a/crates/asm/subprotocols/checkpoint-v0/src/msgs.rs b/crates/asm/subprotocols/checkpoint-v0/src/msgs.rs index c809f2c0d3..dc92bda1f8 100644 --- a/crates/asm/subprotocols/checkpoint-v0/src/msgs.rs +++ b/crates/asm/subprotocols/checkpoint-v0/src/msgs.rs @@ -1,7 +1,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use strata_asm_common::{InterprotoMsg, SubprotocolId}; use strata_asm_proto_checkpoint_txs::CHECKPOINT_V0_SUBPROTOCOL_ID; -use strata_primitives::{buf::Buf32, proof::RollupVerifyingKey}; +use strata_crypto::proof_vk::RollupVerifyingKey; +use strata_identifiers::Buf32; /// Incoming messages that the checkpoint v0 subprotocol can receive from other subprotocols. /// diff --git a/crates/asm/subprotocols/checkpoint-v0/src/subprotocol.rs b/crates/asm/subprotocols/checkpoint-v0/src/subprotocol.rs index 1f3393ef90..ca53265e44 100644 --- a/crates/asm/subprotocols/checkpoint-v0/src/subprotocol.rs +++ b/crates/asm/subprotocols/checkpoint-v0/src/subprotocol.rs @@ -15,9 +15,9 @@ use strata_asm_proto_checkpoint_txs::{ extract_signed_checkpoint_from_envelope, extract_withdrawal_messages, CHECKPOINT_V0_SUBPROTOCOL_ID, OL_STF_CHECKPOINT_TX_TYPE, }; -use strata_primitives::{ - block_credential::CredRule, buf::Buf32, l1::BitcoinTxid, proof::RollupVerifyingKey, -}; +use strata_crypto::proof_vk::RollupVerifyingKey; +use strata_identifiers::{Buf32, CredRule}; +use strata_primitives::l1::BitcoinTxid; use crate::{ error::{CheckpointV0Error, CheckpointV0Result}, diff --git a/crates/asm/subprotocols/checkpoint-v0/src/types.rs b/crates/asm/subprotocols/checkpoint-v0/src/types.rs index 5f749cd737..5d3c0a364b 100644 --- a/crates/asm/subprotocols/checkpoint-v0/src/types.rs +++ b/crates/asm/subprotocols/checkpoint-v0/src/types.rs @@ -8,9 +8,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use strata_checkpoint_types::Checkpoint; -use strata_primitives::{ - block_credential::CredRule, buf::Buf32, l1::L1BlockCommitment, proof::RollupVerifyingKey, -}; +use strata_crypto::proof_vk::RollupVerifyingKey; +use strata_identifiers::{Buf32, CredRule, L1BlockCommitment}; /// Checkpoint verifier state for checkpoint v0 /// diff --git a/crates/asm/subprotocols/checkpoint-v0/src/verification.rs b/crates/asm/subprotocols/checkpoint-v0/src/verification.rs index 09a9cacda9..5c257d8bb8 100644 --- a/crates/asm/subprotocols/checkpoint-v0/src/verification.rs +++ b/crates/asm/subprotocols/checkpoint-v0/src/verification.rs @@ -12,7 +12,7 @@ use strata_checkpoint_types::{ verify_signed_checkpoint_sig, BatchTransition, Checkpoint, SignedCheckpoint, }; use strata_crypto::groth16_verifier::verify_rollup_groth16_proof_receipt; -use strata_primitives::proof::RollupVerifyingKey; +use strata_crypto::proof_vk::RollupVerifyingKey; use crate::{error::CheckpointV0Error, types::CheckpointV0VerifierState}; diff --git a/crates/asm/txs/admin/src/actions/updates/operator.rs b/crates/asm/txs/admin/src/actions/updates/operator.rs index da68ba370d..59b4dde0d1 100644 --- a/crates/asm/txs/admin/src/actions/updates/operator.rs +++ b/crates/asm/txs/admin/src/actions/updates/operator.rs @@ -1,6 +1,6 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; /// An update to the Bridge Operator Set: /// - removes the specified `remove_members` diff --git a/crates/asm/txs/admin/src/actions/updates/seq.rs b/crates/asm/txs/admin/src/actions/updates/seq.rs index 58ea6ea736..94f65d0b4b 100644 --- a/crates/asm/txs/admin/src/actions/updates/seq.rs +++ b/crates/asm/txs/admin/src/actions/updates/seq.rs @@ -1,6 +1,6 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; /// An update to the public key of the sequencer. #[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize)] diff --git a/crates/asm/txs/admin/src/test_utils/mod.rs b/crates/asm/txs/admin/src/test_utils/mod.rs index 9d0a9233dc..2bbc27e3ee 100644 --- a/crates/asm/txs/admin/src/test_utils/mod.rs +++ b/crates/asm/txs/admin/src/test_utils/mod.rs @@ -19,7 +19,7 @@ use strata_crypto::{ multisig::{schemes::SchnorrScheme, signature::AggregatedSignature}, test_utils::schnorr::create_musig2_signature, }; -use strata_primitives::buf::{Buf32, Buf64}; +use strata_identifiers::{Buf32, Buf64}; pub(crate) const TEST_MAGIC_BYTES: &[u8; 4] = b"ALPN"; diff --git a/crates/asm/txs/checkpoint/Cargo.toml b/crates/asm/txs/checkpoint/Cargo.toml index 3be67fc1a9..357f118f30 100644 --- a/crates/asm/txs/checkpoint/Cargo.toml +++ b/crates/asm/txs/checkpoint/Cargo.toml @@ -10,8 +10,8 @@ workspace = true strata-asm-common.workspace = true strata-checkpoint-types.workspace = true strata-l1tx.workspace = true +strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true -strata-state.workspace = true bitcoin.workspace = true borsh.workspace = true diff --git a/crates/asm/txs/checkpoint/src/parser.rs b/crates/asm/txs/checkpoint/src/parser.rs index 0124947e2d..30912f0c12 100644 --- a/crates/asm/txs/checkpoint/src/parser.rs +++ b/crates/asm/txs/checkpoint/src/parser.rs @@ -10,7 +10,7 @@ use strata_asm_common::TxInputRef; use strata_checkpoint_types::{Checkpoint, SignedCheckpoint}; use strata_l1tx::envelope::parser::enter_envelope; use strata_ol_chainstate_types::Chainstate; -use strata_state::bridge_ops::WithdrawalIntent; +use strata_ol_chain_types::WithdrawalIntent; use crate::errors::{CheckpointTxError, CheckpointTxResult}; diff --git a/crates/asm/worker/Cargo.toml b/crates/asm/worker/Cargo.toml index 7e37747082..dff788ea50 100644 --- a/crates/asm/worker/Cargo.toml +++ b/crates/asm/worker/Cargo.toml @@ -11,6 +11,8 @@ strata-asm-common.workspace = true strata-asm-spec.workspace = true strata-asm-stf.workspace = true strata-asm-types.workspace = true +strata-identifiers.workspace = true +strata-params.workspace = true strata-primitives.workspace = true strata-service.workspace = true strata-state.workspace = true diff --git a/crates/asm/worker/src/builder.rs b/crates/asm/worker/src/builder.rs index a6e25196ac..9267bd5676 100644 --- a/crates/asm/worker/src/builder.rs +++ b/crates/asm/worker/src/builder.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use strata_primitives::params::Params; +use strata_params::Params; use strata_service::ServiceBuilder; use strata_tasks::TaskExecutor; use tokio::runtime::Handle; diff --git a/crates/asm/worker/src/service.rs b/crates/asm/worker/src/service.rs index 61a9464386..324f9f4b51 100644 --- a/crates/asm/worker/src/service.rs +++ b/crates/asm/worker/src/service.rs @@ -42,7 +42,7 @@ impl SyncService for AsmWorkerService< // Handle pre-genesis: if the block is before genesis we don't care about it. let genesis_height = state.params.rollup().genesis_l1_view.height(); - let height = incoming_block.height(); + let height = incoming_block.height().to_consensus_u32(); if height < genesis_height { warn!(%height, "ignoring unexpected L1 block before genesis"); return Ok(Response::Continue); @@ -54,7 +54,7 @@ impl SyncService for AsmWorkerService< let mut pivot_block = *incoming_block; let mut pivot_anchor = ctx.get_anchor_state(&pivot_block); - while pivot_anchor.is_err() && pivot_block.height() >= genesis_height { + while pivot_anchor.is_err() && pivot_block.height().to_consensus_u32() >= genesis_height { let block = ctx.get_l1_block(pivot_block.blkid())?; let parent_height = pivot_block.height().to_consensus_u32() - 1; let parent_block_id = L1BlockCommitment::from_height_u64( @@ -72,7 +72,7 @@ impl SyncService for AsmWorkerService< } // We reached the height before genesis (while traversing), but didn't find genesis state. - if pivot_block.height() < genesis_height { + if pivot_block.height().to_consensus_u32() < genesis_height { warn!("ASM hasn't found pivot anchor state at genesis."); return Ok(Response::ShouldExit); } diff --git a/crates/asm/worker/src/state.rs b/crates/asm/worker/src/state.rs index 2049479f8a..c8a9091a6c 100644 --- a/crates/asm/worker/src/state.rs +++ b/crates/asm/worker/src/state.rs @@ -5,7 +5,8 @@ use strata_asm_common::{AnchorState, AuxPayload, ChainViewState}; use strata_asm_spec::StrataAsmSpec; use strata_asm_stf::{AsmStfInput, AsmStfOutput}; use strata_asm_types::HeaderVerificationState; -use strata_primitives::{l1::L1BlockCommitment, params::Params}; +use strata_identifiers::L1BlockCommitment; +use strata_params::Params; use strata_service::ServiceState; use strata_state::asm_state::AsmState; diff --git a/crates/btc-types/Cargo.toml b/crates/btc-types/Cargo.toml new file mode 100644 index 0000000000..0505347953 --- /dev/null +++ b/crates/btc-types/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "strata-btc-types" +version = "0.1.0" +edition = "2021" + +[dependencies] +strata-checkpoint-types.workspace = true +strata-identifiers.workspace = true + +arbitrary.workspace = true +bitcoin = { workspace = true, features = ["serde", "rand-std"] } +bitcoin-bosd = { workspace = true, features = [ + "address", + "serde", + "borsh", + "arbitrary", +] } +borsh.workspace = true +hex.workspace = true +serde.workspace = true +sha2.workspace = true +thiserror.workspace = true + +[lints] +workspace = true diff --git a/crates/btc-types/src/address.rs b/crates/btc-types/src/address.rs new file mode 100644 index 0000000000..5d214074e9 --- /dev/null +++ b/crates/btc-types/src/address.rs @@ -0,0 +1,143 @@ +use std::io::{self, Read, Write}; + +use bitcoin::{address::NetworkUnchecked, Address, Network, ScriptBuf}; +use bitcoin_bosd::Descriptor; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{de, Deserialize, Deserializer, Serialize}; + +use crate::ParseError; + +/// A wrapper around the [`bitcoin::Address`] type. +/// +/// It's created in order to implement some useful traits on it such as +/// [`serde::Deserialize`], [`borsh::BorshSerialize`] and [`borsh::BorshDeserialize`]. +// TODO: implement [`arbitrary::Arbitrary`]? +#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct BitcoinAddress { + /// The [`bitcoin::Network`] that this address is valid in. + network: Network, + + /// The actual [`Address`] that this type wraps. + address: Address, +} + +impl BitcoinAddress { + /// Parses a [`BitcoinAddress`] from a string. + pub fn parse(address_str: &str, network: Network) -> Result { + let address = address_str + .parse::>() + .map_err(ParseError::InvalidAddress)?; + + let checked_address = address + .require_network(network) + .map_err(ParseError::InvalidAddress)?; + + Ok(Self { + network, + address: checked_address, + }) + } + + /// Parses a [`BitcoinAddress`] from raw bytes representation of a bitcoin Script. + pub fn from_bytes(bytes: &[u8], network: Network) -> Result { + let script_buf = ScriptBuf::from_bytes(bytes.to_vec()); + let address = Address::from_script(&script_buf, network)?; + Ok(Self { network, address }) + } + + pub fn from_descriptor(descriptor: &Descriptor, network: Network) -> Result { + let address = descriptor + .to_address(network) + .map_err(|err| ParseError::Descriptor(err.to_string()))?; + Ok(Self { network, address }) + } +} + +impl BitcoinAddress { + pub fn address(&self) -> &Address { + &self.address + } + + pub fn network(&self) -> &Network { + &self.network + } +} + +impl<'de> Deserialize<'de> for BitcoinAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct BitcoinAddressShim { + network: Network, + address: String, + } + + let shim = BitcoinAddressShim::deserialize(deserializer)?; + let address = shim + .address + .parse::>() + .map_err(|_| de::Error::custom("invalid bitcoin address"))? + .require_network(shim.network) + .map_err(|_| de::Error::custom("address invalid for given network"))?; + + Ok(BitcoinAddress { + network: shim.network, + address, + }) + } +} + +impl BorshSerialize for BitcoinAddress { + fn serialize(&self, writer: &mut W) -> Result<(), io::Error> { + let address_string = self.address.to_string(); + + BorshSerialize::serialize(address_string.as_str(), writer)?; + + let network_byte = match self.network { + Network::Bitcoin => 0u8, + Network::Testnet => 1u8, + Network::Signet => 2u8, + Network::Regtest => 3u8, + other => unreachable!("should handle new variant: {}", other), + }; + + BorshSerialize::serialize(&network_byte, writer)?; + + Ok(()) + } +} + +impl BorshDeserialize for BitcoinAddress { + fn deserialize_reader(reader: &mut R) -> Result { + let address_str = String::deserialize_reader(reader)?; + + let network_byte = u8::deserialize_reader(reader)?; + let network = match network_byte { + 0u8 => Network::Bitcoin, + 1u8 => Network::Testnet, + 2u8 => Network::Signet, + 3u8 => Network::Regtest, + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Invalid network byte: {network_byte}"), + )); + } + }; + + let address = address_str + .parse::>() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid bitcoin address"))? + .require_network(network) + .map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "address invalid for given network", + ) + })?; + + Ok(BitcoinAddress { address, network }) + } +} diff --git a/crates/btc-types/src/asm_manifest.rs b/crates/btc-types/src/asm_manifest.rs new file mode 100644 index 0000000000..ecb0e3338f --- /dev/null +++ b/crates/btc-types/src/asm_manifest.rs @@ -0,0 +1,39 @@ +//! L1 segment types. + +#[derive(Clone, Debug)] +pub struct AsmManifest { + /// Bitcoin block header. + // TODO figure out what to do here + header_buf: [u8; 80], + + /// Merkle root of the witness structure, so we can make proofs against it. + wtxid_root: [u8; 32], + + /// Logs produced by the ASM as of this block. + logs: Vec, +} + +impl AsmManifest { + pub fn header_buf(&self) -> [u8; 80] { + self.header_buf + } + + pub fn wtxid_root(&self) -> [u8; 32] { + self.wtxid_root + } + + pub fn logs(&self) -> &[AsmLog] { + &self.logs + } +} + +#[derive(Clone, Debug)] +pub struct AsmLog { + payload: Vec, +} + +impl AsmLog { + pub fn payload(&self) -> &[u8] { + &self.payload + } +} diff --git a/crates/btc-types/src/constants.rs b/crates/btc-types/src/constants.rs new file mode 100644 index 0000000000..43ef0aeb0f --- /dev/null +++ b/crates/btc-types/src/constants.rs @@ -0,0 +1,7 @@ +/// The number of timestamps used for calculating the median in Bitcoin header verification. +/// According to Bitcoin consensus rules, we need to check that a block's timestamp +/// is not lower than the median of the last eleven blocks' timestamps. +pub const TIMESTAMPS_FOR_MEDIAN: usize = 11; + +/// The size (in bytes) of a Hash (such as [`Txid`](bitcoin::Txid)). +pub const HASH_SIZE: usize = 32; diff --git a/crates/btc-types/src/errors.rs b/crates/btc-types/src/errors.rs new file mode 100644 index 0000000000..675777a589 --- /dev/null +++ b/crates/btc-types/src/errors.rs @@ -0,0 +1,45 @@ +//! Errors during parsing/handling/conversion of Bitcoin types. + +use bitcoin::{address, secp256k1, AddressType}; +use thiserror::Error; +use strata_identifiers::Buf32; + +/// Parsing errors that can occur with Bitcoin types, +/// such as addresses, pubkeys, and scripts. +#[derive(Debug, Clone, Error)] +pub enum ParseError { + /// The provided pubkey is invalid. + #[error("supplied pubkey is invalid")] + InvalidPubkey(#[from] secp256k1::Error), + + /// The provided address is invalid. + #[error("supplied address is invalid")] + InvalidAddress(#[from] address::ParseError), + + /// The provided script is invalid. + #[error("supplied script is invalid")] + InvalidScript(#[from] address::FromScriptError), + + /// The provided 32-byte buffer is not a valid point on the curve. + #[error("not a valid point on the curve: {0}")] + InvalidPoint(Buf32), + + /// Converting from an unsupported [`Address`](bitcoin::Address) type for a [`Buf32`]. + #[error("only taproot addresses are supported but found {0:?}")] + UnsupportedAddress(Option), + + /// Could not get a network address from descriptor + /// Using String error as [`bitcoin_bosd::DescriptorError`] does not impl Clone + #[error("descriptor: {0}")] + Descriptor(String), +} + +impl From for ParseError { + fn from(value: strata_identifiers::ParseError) -> Self { + match value { + strata_identifiers::ParseError::InvalidPubkey(e) => Self::InvalidPubkey(e), + strata_identifiers::ParseError::InvalidPoint(buf) => Self::InvalidPoint(buf), + strata_identifiers::ParseError::UnsupportedAddress(ty) => Self::UnsupportedAddress(ty), + } + } +} diff --git a/crates/btc-types/src/legacy/block.rs b/crates/btc-types/src/legacy/block.rs new file mode 100644 index 0000000000..d3cabed0db --- /dev/null +++ b/crates/btc-types/src/legacy/block.rs @@ -0,0 +1,164 @@ +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::{Buf32, L1BlockCommitment, L1BlockId, hash}; + +use crate::legacy::L1Tx; + +/// Header and the wtxs root. +/// +/// This is the core data we need to make proof against a L1 block. We could +/// omit the wtxs root, but we'd need to re-prove it every time, and that would +/// waste space. So we treat this like you would an "extended header" or +/// something. +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] +pub struct L1HeaderRecord { + /// L1 block ID here so that we don't have to recompute it too much, which + /// is expensive in proofs. + pub(crate) blkid: L1BlockId, + + /// Serialized header. For Bitcoin this is always 80 bytes. + pub(crate) buf: Vec, + + /// Root of the transaction witnesses tree. + /// + /// This is how we check envelopes, since those are only present in the + /// witness transaction serialization. + pub(crate) wtxs_root: Buf32, +} + +impl L1HeaderRecord { + pub fn new(blkid: L1BlockId, buf: Vec, wtxs_root: Buf32) -> Self { + Self { + blkid, + buf, + wtxs_root, + } + } + + /// Creates a new instance serialized header and the wtxs root. + pub fn create_from_serialized_header(buf: Vec, wtxs_root: Buf32) -> Self { + assert_eq!(buf.len(), 80, "l1: header record not 80 bytes"); + let blkid = hash::sha256d(&buf).into(); + Self::new(blkid, buf, wtxs_root) + } + + pub fn blkid(&self) -> &L1BlockId { + &self.blkid + } + + pub fn buf(&self) -> &[u8] { + &self.buf + } + + pub fn wtxs_root(&self) -> &Buf32 { + &self.wtxs_root + } + + /// Extracts the parent block ID from the header record. + pub fn parent_blkid(&self) -> L1BlockId { + assert_eq!(self.buf.len(), 80, "l1: header record not 80 bytes"); + let mut buf = [0; 32]; + buf.copy_from_slice(&self.buf()[4..36]); // range of parent field bytes + L1BlockId::from(Buf32::from(buf)) + } +} + +impl<'a> Arbitrary<'a> for L1HeaderRecord { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // Bitcoin headers are always 80 bytes, so we generate it like that. + // However, we don't want to hardcode the data structure like that *just + // in case*. + let arr = <[u8; 80]>::arbitrary(u)?; + Ok(Self::create_from_serialized_header( + arr.to_vec(), + Buf32::arbitrary(u)?, + )) + } +} + +/// Bitcoin-anchored block manifest containing header record and transactions. +#[derive( + Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Deserialize, Serialize, +)] +pub struct L1BlockManifest { + /// The actual l1 record + record: L1HeaderRecord, + + /// List of interesting transactions we took out. + txs: Vec, + + /// Epoch, which was used to generate this manifest. + epoch: u64, + + /// Block height. + height: u64, +} + +impl L1BlockManifest { + pub fn new(record: L1HeaderRecord, txs: Vec, epoch: u64, height: u64) -> Self { + Self { + record, + txs, + epoch, + height, + } + } + + pub fn record(&self) -> &L1HeaderRecord { + &self.record + } + + pub fn txs(&self) -> &[L1Tx] { + &self.txs + } + + pub fn txs_vec(&self) -> &Vec { + &self.txs + } + + pub fn epoch(&self) -> u64 { + self.epoch + } + + pub fn blkid(&self) -> &L1BlockId { + &self.record.blkid + } + + #[deprecated(note = "use .blkid()")] + pub fn block_hash(&self) -> L1BlockId { + *self.record.blkid() + } + + pub fn height(&self) -> u64 { + self.height + } + + pub fn header(&self) -> &[u8] { + self.record.buf() + } + + pub fn txs_root(&self) -> Buf32 { + *self.record.wtxs_root() + } + + pub fn get_prev_blockid(&self) -> L1BlockId { + self.record().parent_blkid() + } + + pub fn into_record(self) -> L1HeaderRecord { + self.record + } +} + +impl From for L1BlockCommitment { + fn from(value: L1BlockManifest) -> Self { + Self::from_height_u64(value.height(), *value.blkid()).expect("height should be valid") + } +} + +impl From<&L1BlockManifest> for L1BlockCommitment { + fn from(value: &L1BlockManifest) -> Self { + Self::from_height_u64(value.height(), *value.blkid()).expect("height should be valid") + } +} diff --git a/crates/btc-types/src/legacy/mod.rs b/crates/btc-types/src/legacy/mod.rs new file mode 100644 index 0000000000..d56f1ef06b --- /dev/null +++ b/crates/btc-types/src/legacy/mod.rs @@ -0,0 +1,7 @@ +pub mod block; +pub mod protocol_operation; +pub mod tx; + +pub use block::{L1BlockManifest, L1HeaderRecord}; +pub use protocol_operation::*; +pub use tx::L1Tx; diff --git a/crates/btc-types/src/legacy/protocol_operation.rs b/crates/btc-types/src/legacy/protocol_operation.rs new file mode 100644 index 0000000000..11e6f8cfbd --- /dev/null +++ b/crates/btc-types/src/legacy/protocol_operation.rs @@ -0,0 +1,186 @@ +// TODO remove this as we switch to a log-oriented model + +use std::fmt; + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use sha2::{digest::Digest, Sha256}; +use strata_checkpoint_types::SignedCheckpoint; +use strata_identifiers::{BitcoinAmount, Buf32}; + +use crate::OutputRef; + +/// Consensus level protocol operations extracted from a bitcoin transaction. +/// +/// These are submitted to the OL STF and impact state. +#[expect(clippy::large_enum_variant, reason = "used for protocol operations")] +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] +pub enum ProtocolOperation { + /// Deposit Transaction + Deposit(DepositInfo), + + /// Checkpoint data + Checkpoint(SignedCheckpoint), + + /// DA blob + DaCommitment(DaCommitment), + + /// Deposit request. + /// + /// This is being removed soon as it's not really a consensus change. + DepositRequest(DepositRequestInfo), + + /// Withdrawal fulfilled by bridge operator front-payment. + WithdrawalFulfillment(WithdrawalFulfillmentInfo), + + /// Deposit utxo is spent. + DepositSpent(DepositSpendInfo), +} + +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] +pub struct DepositInfo { + /// Deposit from tag output, as assigned by operators. + pub deposit_idx: u32, + + /// Bitcoin amount. + pub amt: BitcoinAmount, + + /// Output for deposit funds at rest. + pub outpoint: OutputRef, + + /// Destination address payload. + pub address: Vec, +} + +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] +pub struct DepositRequestInfo { + /// amount in satoshis + pub amt: u64, + + /// tapscript control block hash for timelock script + pub take_back_leaf_hash: [u8; 32], + + /// EE address + pub address: Vec, +} + +#[derive( + Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] +pub struct WithdrawalFulfillmentInfo { + /// index of deposit this fulfillment is for + pub deposit_idx: u32, + + /// assigned operator + /// TODO: maybe this is not needed + pub operator_idx: u32, + + /// amount that was actually sent on bitcoin. + /// should equal withdrawal_amount - operator fee + pub amt: BitcoinAmount, + + /// corresponding bitcoin transaction id. + pub txid: Buf32, +} + +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, Arbitrary, Serialize, Deserialize, +)] +pub struct DepositSpendInfo { + /// index of the deposit whose utxo is spent. + pub deposit_idx: u32, +} + +// Custom debug implementation to print txid in little endian +impl fmt::Debug for WithdrawalFulfillmentInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let txid_le = { + let mut bytes = self.txid.0; + bytes.reverse(); + bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + }; + + f.debug_struct("WithdrawalFulfillmentInfo") + .field("deposit_idx", &self.deposit_idx) + .field("operator_idx", &self.operator_idx) + .field("amt", &self.amt) + .field("txid", &txid_le) + .finish() + } +} + +// Custom display implementation to print txid in little endian +impl fmt::Display for WithdrawalFulfillmentInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let txid_le = { + let mut bytes = self.txid.0; + bytes.reverse(); + bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + }; + + write!( + f, + "WithdrawalFulfillmentInfo {{ deposit_idx: {}, operator_idx: {}, amt: {:?}, txid: {} }}", + self.deposit_idx, self.operator_idx, self.amt, txid_le + ) + } +} + +/// Commits to a DA blob. This is just the hash of the DA blob. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + Arbitrary, + Serialize, + Deserialize, +)] +pub struct DaCommitment(Buf32); + +impl DaCommitment { + /// Creates a commitment from a DA payload buf. + pub fn from_buf(buf: &[u8]) -> Self { + Self::from_chunk_iter([buf].into_iter()) + } + + /// Creates a commitment from a series of contiguous chunks of a single DA + /// paylod buf. + /// + /// This is meant to be used when constructing a commitment from an in-situ + /// payload from a transaction, which has to be in 520-byte chunks. + pub fn from_chunk_iter<'a>(chunks: impl Iterator) -> Self { + // TODO maybe abstract this further? + let mut hasher = Sha256::new(); + for chunk in chunks { + hasher.update(chunk); + } + + let hash: [u8; 32] = hasher.finalize().into(); + Self(Buf32(hash)) + } + + pub fn as_hash(&self) -> &Buf32 { + &self.0 + } + + pub fn to_hash(&self) -> Buf32 { + self.0 + } +} diff --git a/crates/btc-types/src/legacy/tx.rs b/crates/btc-types/src/legacy/tx.rs new file mode 100644 index 0000000000..59e20d57c5 --- /dev/null +++ b/crates/btc-types/src/legacy/tx.rs @@ -0,0 +1,39 @@ +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::{L1TxProof, RawBitcoinTx}; +use super::protocol_operation::ProtocolOperation; + +/// Bitcoin-anchored transaction with proof and protocol operations. +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Arbitrary, Serialize, Deserialize, +)] +pub struct L1Tx { + // TODO: verify if we need L1TxProof or L1WtxProof + proof: L1TxProof, + tx: RawBitcoinTx, + protocol_ops: Vec, +} + +impl L1Tx { + pub fn new(proof: L1TxProof, tx: RawBitcoinTx, protocol_ops: Vec) -> Self { + Self { + proof, + tx, + protocol_ops, + } + } + + pub fn proof(&self) -> &L1TxProof { + &self.proof + } + + pub fn tx_data(&self) -> &RawBitcoinTx { + &self.tx + } + + pub fn protocol_ops(&self) -> &[ProtocolOperation] { + &self.protocol_ops + } +} diff --git a/crates/btc-types/src/lib.rs b/crates/btc-types/src/lib.rs new file mode 100644 index 0000000000..affd2d0495 --- /dev/null +++ b/crates/btc-types/src/lib.rs @@ -0,0 +1,34 @@ +mod address; +mod asm_manifest; +pub mod constants; +pub mod errors; +mod legacy; +mod operator; +mod output; +mod params; +mod proof; +mod psbt; +mod pubkey; +mod raw_tx; +mod transaction; +mod txid; + +pub use address::BitcoinAddress; +pub use asm_manifest::{AsmLog, AsmManifest}; +pub use errors::ParseError; +pub use legacy::{ + DaCommitment, DepositInfo, DepositRequestInfo, DepositSpendInfo, + L1BlockManifest, L1HeaderRecord, L1Tx, ProtocolOperation, WithdrawalFulfillmentInfo, +}; +pub use operator::OperatorPubkeys; +pub use output::OutputRef; +pub use params::BtcParams; +pub use proof::{L1TxInclusionProof, L1TxProof, L1WtxProof, TxIdComputable, TxIdMarker, WtxIdMarker}; +pub use psbt::BitcoinPsbt; +pub use pubkey::{BitcoinScriptBuf, XOnlyPk}; +pub use raw_tx::RawBitcoinTx; +pub use transaction::{BitcoinTxOut, Outpoint, TaprootSpendPath}; +pub use txid::BitcoinTxid; + +// re-exports +pub use strata_identifiers::L1BlockId; diff --git a/crates/btc-types/src/operator.rs b/crates/btc-types/src/operator.rs new file mode 100644 index 0000000000..16d21c412c --- /dev/null +++ b/crates/btc-types/src/operator.rs @@ -0,0 +1,31 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::Buf32; + +/// Container for operator pubkeys. +#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)] +pub struct OperatorPubkeys { + signing_pk: Buf32, + wallet_pk: Buf32, +} + +impl OperatorPubkeys { + pub fn new(signing_pk: Buf32, wallet_pk: Buf32) -> Self { + Self { + signing_pk, + wallet_pk, + } + } + + pub fn signing_pk(&self) -> &Buf32 { + &self.signing_pk + } + + pub fn wallet_pk(&self) -> &Buf32 { + &self.wallet_pk + } + + pub fn into_parts(self) -> (Buf32, Buf32) { + (self.signing_pk, self.wallet_pk) + } +} diff --git a/crates/btc-types/src/output.rs b/crates/btc-types/src/output.rs new file mode 100644 index 0000000000..07406a88a4 --- /dev/null +++ b/crates/btc-types/src/output.rs @@ -0,0 +1,72 @@ +use std::io::{self, Read, Write}; + +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{hashes::Hash, OutPoint, Txid}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::constants::HASH_SIZE; + +/// L1 output reference. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct OutputRef(pub OutPoint); + +impl From for OutputRef { + fn from(value: OutPoint) -> Self { + Self(value) + } +} + +impl OutputRef { + pub fn new(txid: Txid, vout: u32) -> Self { + Self(OutPoint::new(txid, vout)) + } + + pub fn outpoint(&self) -> &OutPoint { + &self.0 + } +} + +// Implement BorshSerialize for the OutputRef wrapper. +impl BorshSerialize for OutputRef { + fn serialize(&self, writer: &mut W) -> Result<(), io::Error> { + // Serialize the transaction ID as bytes + writer.write_all(&self.0.txid[..])?; + + // Serialize the output index as a little-endian 4-byte integer + writer.write_all(&self.0.vout.to_le_bytes())?; + Ok(()) + } +} + +// Implement BorshDeserialize for the OutputRef wrapper. +impl BorshDeserialize for OutputRef { + fn deserialize_reader(reader: &mut R) -> Result { + // Read 32 bytes for the transaction ID + let mut txid_bytes = [0u8; HASH_SIZE]; + reader.read_exact(&mut txid_bytes)?; + let txid = bitcoin::Txid::from_slice(&txid_bytes).expect("should be a valid txid (hash)"); + + // Read 4 bytes for the output index + let mut vout_bytes = [0u8; 4]; + reader.read_exact(&mut vout_bytes)?; + let vout = u32::from_le_bytes(vout_bytes); + + Ok(OutputRef(OutPoint { txid, vout })) + } +} + +// Implement Arbitrary for the wrapper +impl<'a> Arbitrary<'a> for OutputRef { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // Generate a random 32-byte array for the transaction ID (txid) + let mut txid_bytes = [0u8; HASH_SIZE]; + u.fill_buffer(&mut txid_bytes)?; + let txid = bitcoin::Txid::from_raw_hash(bitcoin::hashes::Hash::from_byte_array(txid_bytes)); + + // Generate a random 4-byte integer for the output index (vout) + let vout = u.int_in_range(0..=u32::MAX)?; + + Ok(OutputRef(OutPoint { txid, vout })) + } +} diff --git a/crates/btc-types/src/params.rs b/crates/btc-types/src/params.rs new file mode 100644 index 0000000000..8b60ac949c --- /dev/null +++ b/crates/btc-types/src/params.rs @@ -0,0 +1,149 @@ +use bitcoin::params::{MAINNET, Params}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone)] +pub struct BtcParams(Params); + +impl PartialEq for BtcParams { + fn eq(&self, other: &Self) -> bool { + // Just compare the network since all other params derive from it + self.0.network == other.0.network + } +} + +impl Eq for BtcParams {} + +impl Default for BtcParams { + fn default() -> Self { + BtcParams(MAINNET.clone()) + } +} + +impl BorshSerialize for BtcParams { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + // Serialize the network type as an index since Network doesn't implement BorshSerialize + let network_index = match self.0.network { + bitcoin::Network::Bitcoin => 0u8, + bitcoin::Network::Testnet => 1u8, + bitcoin::Network::Signet => 2u8, + bitcoin::Network::Regtest => 3u8, + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unsupported network type", + )); + } + }; + BorshSerialize::serialize(&network_index, writer) + } +} + +impl BorshDeserialize for BtcParams { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let network_index = u8::deserialize_reader(reader)?; + let network = match network_index { + 0 => bitcoin::Network::Bitcoin, + 1 => bitcoin::Network::Testnet, + 2 => bitcoin::Network::Signet, + 3 => bitcoin::Network::Regtest, + _ => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid network index", + )); + } + }; + Ok(BtcParams::from(Params::from(network))) + } +} + +impl Serialize for BtcParams { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Just serialize the network - the rest can be derived from it + self.0.network.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for BtcParams { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let network = bitcoin::Network::deserialize(deserializer)?; + Ok(BtcParams::from(Params::from(network))) + } +} + +impl<'a> arbitrary::Arbitrary<'a> for BtcParams { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let networks = [ + bitcoin::Network::Bitcoin, + bitcoin::Network::Testnet, + bitcoin::Network::Signet, + bitcoin::Network::Regtest, + ]; + let network = u.choose(&networks)?; + Ok(BtcParams::from(Params::from(*network))) + } +} + +impl From for BtcParams { + fn from(params: Params) -> Self { + BtcParams(params) + } +} + +impl BtcParams { + pub fn into_inner(self) -> Params { + self.0 + } + + pub fn inner(&self) -> &Params { + &self.0 + } + + pub fn difficulty_adjustment_interval(&self) -> u64 { + self.0.difficulty_adjustment_interval() + } +} + +impl AsRef for BtcParams { + fn as_ref(&self) -> &Params { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use bitcoin::Network; + + use super::*; + + #[test] + fn test_all_networks_serialization() { + let networks = [ + Network::Bitcoin, + Network::Testnet, + Network::Signet, + Network::Regtest, + ]; + + for network in networks { + let params = BtcParams::from(Params::from(network)); + + // Test Borsh + let borsh_data = borsh::to_vec(¶ms).unwrap(); + let borsh_result = borsh::from_slice::(&borsh_data).unwrap(); + assert_eq!(params, borsh_result); + + // Test Serde + let json_data = serde_json::to_string(¶ms).unwrap(); + let serde_result: BtcParams = serde_json::from_str(&json_data).unwrap(); + assert_eq!(params, serde_result); + } + } +} diff --git a/crates/btc-types/src/proof.rs b/crates/btc-types/src/proof.rs new file mode 100644 index 0000000000..1002e5cb8e --- /dev/null +++ b/crates/btc-types/src/proof.rs @@ -0,0 +1,192 @@ +use std::marker::PhantomData; + +use arbitrary::Arbitrary; +use bitcoin::Transaction; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::{Buf32, hash::sha256d, utils::get_cohashes}; + +/// A generic proof structure that can handle any kind of transaction ID (e.g., +/// [`Txid`](bitcoin::Txid) or [`Wtxid`](bitcoin::Wtxid)) by delegating the ID computation to the +/// provided type `T` that implements [`TxIdComputable`]. +#[derive( + Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct L1TxInclusionProof { + /// The 0-based position (index) of the transaction within the block's transaction list + /// for which this proof is generated. + position: u32, + + /// The intermediate hashes (sometimes called "siblings") needed to reconstruct the Merkle root + /// when combined with the target transaction's own ID. These are the Merkle tree nodes at + /// each step that pair with the current hash (either on the left or the right) to produce + /// the next level of the tree. + cohashes: Vec, + + /// A marker that preserves the association with type `T`, which implements + /// [`TxIdComputable`]. This ensures the proof logic depends on the correct + /// transaction ID computation ([`Txid`](bitcoin::Txid) vs.[`Wtxid`](bitcoin::Wtxid)) for the + /// lifetime of the proof. + _marker: PhantomData, +} + +impl L1TxInclusionProof { + pub fn new(position: u32, cohashes: Vec) -> Self { + Self { + position, + cohashes, + _marker: PhantomData, + } + } + + pub fn cohashes(&self) -> &[Buf32] { + &self.cohashes + } + + pub fn position(&self) -> u32 { + self.position + } +} + +impl L1TxInclusionProof { + /// Generates the proof for a transaction at the specified index in the list of + /// transactions, using `T` to compute the transaction IDs. + pub fn generate(transactions: &[Transaction], idx: u32) -> Self { + let txids = transactions + .iter() + .enumerate() + .map(|(idx, tx)| T::compute_id(tx, idx)) + .collect::>(); + let (cohashes, _txroot) = get_cohashes(&txids, idx); + L1TxInclusionProof::new(idx, cohashes) + } + + /// Computes the merkle root for the given `transaction` using the proof's cohashes. + pub fn compute_root(&self, transaction: &Transaction) -> Buf32 { + // `cur_hash` represents the intermediate hash at each step. After all cohashes are + // processed `cur_hash` becomes the root hash + let mut cur_hash = T::compute_id(transaction, self.position as usize).0; + + let mut pos = self.position(); + for cohash in self.cohashes() { + let mut buf = [0u8; 64]; + if pos & 1 == 0 { + buf[0..32].copy_from_slice(&cur_hash); + buf[32..64].copy_from_slice(cohash.as_ref()); + } else { + buf[0..32].copy_from_slice(cohash.as_ref()); + buf[32..64].copy_from_slice(&cur_hash); + } + cur_hash = sha256d(&buf).0; + pos >>= 1; + } + Buf32::from(cur_hash) + } + + /// Verifies the inclusion proof of the given `transaction` against the provided merkle `root`. + pub fn verify(&self, transaction: &Transaction, root: Buf32) -> bool { + self.compute_root(transaction) == root + } +} + +/// Convenience type alias for the [`Txid`](bitcoin::Txid)-based proof. +pub type L1TxProof = L1TxInclusionProof; + +/// Convenience type alias for the [`Wtxid`](bitcoin::Wtxid)-based proof. +pub type L1WtxProof = L1TxInclusionProof; +// FIXME these aren't "marker" types, only traits can be markers + +/// A trait for computing some kind of transaction ID (e.g., [`Txid`](bitcoin::Txid) or +/// [`Wtxid`](bitcoin::Wtxid)) from a [`Transaction`]. +/// +/// This trait is designed to be implemented by "marker" types that define how a transaction ID +/// should be computed. For example, [`TxIdMarker`] invokes [`Transaction::compute_txid`], and +/// [`WtxIdMarker`] invokes [`Transaction::compute_wtxid`]. This approach avoids duplicating +/// inclusion-proof or serialization logic across multiple ID computations. +pub trait TxIdComputable { + /// Computes the transaction ID for the given transaction. + /// + /// The `idx` parameter allows marker types to handle special cases such as the coinbase + /// transaction (which has a zero [`Wtxid`](bitcoin::Wtxid)) by looking up the transaction + /// index. + fn compute_id(tx: &Transaction, idx: usize) -> Buf32; +} + +/// Marker type for computing the [`Txid`](bitcoin::Txid). +#[derive( + Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct TxIdMarker; + +/// Marker type for computing the [`Wtxid`](bitcoin::Wtxid). +#[derive( + Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct WtxIdMarker; + +impl TxIdComputable for TxIdMarker { + fn compute_id(tx: &Transaction, _idx: usize) -> Buf32 { + tx.compute_txid().into() + } +} + +impl TxIdComputable for WtxIdMarker { + fn compute_id(tx: &Transaction, idx: usize) -> Buf32 { + // Coinbase transaction wtxid is hash with zeroes + if idx == 0 { + return Buf32::zero(); + } + tx.compute_wtxid().into() + } +} + +#[cfg(test)] +mod tests { + use bitcoin::hashes::Hash; + use rand::{thread_rng, Rng}; + use strata_identifiers::Buf32; + use strata_test_utils_btc::segment::BtcChainSegment; + + use super::*; + + #[test] + fn test_l1_tx_proof() { + let btc_chain = BtcChainSegment::load(); + let block = btc_chain.get_block_at(40_321).unwrap(); + let merkle_root: Buf32 = block.header.merkle_root.to_byte_array().into(); + let txs = &block.txdata; + + for (idx, tx) in txs.iter().enumerate() { + let proof = L1TxProof::generate(txs, idx as u32); + assert!(proof.verify(tx, merkle_root)); + } + } + + #[test] + fn test_l1_tx_proof_2() { + let block = BtcChainSegment::load_full_block(); + let merkle_root: Buf32 = block.header.merkle_root.to_byte_array().into(); + let txs = &block.txdata; + + let mut rng = thread_rng(); + let idx = rng.gen_range(0..=txs.len()); + let proof = L1TxProof::generate(txs, idx as u32); + assert!(proof.verify(&txs[idx], merkle_root)); + } + + #[test] + fn test_l1_wtx_proof() { + let block = BtcChainSegment::load_full_block(); + let txs = &block.txdata; + let wtx_root = block.witness_root().unwrap().to_byte_array().into(); + + let idx = 0; + let proof = L1WtxProof::generate(txs, idx as u32); + assert!(proof.verify(&txs[idx], wtx_root)); + + let mut rng = thread_rng(); + let idx = rng.gen_range(1..=txs.len()); + let proof = L1WtxProof::generate(txs, idx as u32); + assert!(proof.verify(&txs[idx], wtx_root)); + } +} diff --git a/crates/btc-types/src/psbt.rs b/crates/btc-types/src/psbt.rs new file mode 100644 index 0000000000..a5feb9616b --- /dev/null +++ b/crates/btc-types/src/psbt.rs @@ -0,0 +1,95 @@ +use std::io::{Read, Write}; + +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{ + absolute::LockTime, transaction::Version, OutPoint, Psbt, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Txid, Witness, +}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::BitcoinTxOut; + +/// [Borsh](borsh)-friendly Bitcoin [`Psbt`]. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BitcoinPsbt(Psbt); + +impl BitcoinPsbt { + pub fn inner(&self) -> &Psbt { + &self.0 + } + + pub fn compute_txid(&self) -> Txid { + self.0.unsigned_tx.compute_txid() + } +} + +impl From for BitcoinPsbt { + fn from(value: Psbt) -> Self { + Self(value) + } +} + +impl From for Psbt { + fn from(value: BitcoinPsbt) -> Self { + value.0 + } +} + +impl BorshSerialize for BitcoinPsbt { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + // Serialize the PSBT using bitcoin's built-in serialization + let psbt_bytes = self.0.serialize(); + // First, write the length of the serialized PSBT (as u32) + BorshSerialize::serialize(&(psbt_bytes.len() as u32), writer)?; + // Then, write the actual serialized PSBT bytes + writer.write_all(&psbt_bytes)?; + Ok(()) + } +} + +impl BorshDeserialize for BitcoinPsbt { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + // First, read the length of the PSBT (as u32) + let len = u32::deserialize_reader(reader)? as usize; + // Then, create a buffer to hold the PSBT bytes and read them + let mut psbt_bytes = vec![0u8; len]; + reader.read_exact(&mut psbt_bytes)?; + // Use the bitcoin crate's deserialize method to create a Psbt from the bytes + let psbt = Psbt::deserialize(&psbt_bytes).map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid PSBT data") + })?; + Ok(BitcoinPsbt(psbt)) + } +} + +impl<'a> Arbitrary<'a> for BitcoinPsbt { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let num_outputs = u.arbitrary_len::<[u8; 32]>()? % 5; + let mut output: Vec = vec![]; + + for _ in 0..num_outputs { + let txout = BitcoinTxOut::arbitrary(u)?; + let txout = TxOut::from(txout); + + output.push(txout); + } + + let tx = Transaction { + version: Version(1), + lock_time: LockTime::from_consensus(0), + input: vec![TxIn { + previous_output: OutPoint::null(), + witness: Witness::new(), + sequence: Sequence(0), + script_sig: ScriptBuf::new(), + }], + output, + }; + + let psbt = Psbt::from_unsigned_tx(tx).map_err(|_e| arbitrary::Error::IncorrectFormat)?; + let psbt = BitcoinPsbt::from(psbt); + + Ok(psbt) + } +} diff --git a/crates/btc-types/src/pubkey.rs b/crates/btc-types/src/pubkey.rs new file mode 100644 index 0000000000..5f1bffa6bd --- /dev/null +++ b/crates/btc-types/src/pubkey.rs @@ -0,0 +1,139 @@ +use std::io::{Read, Write}; + +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{key::TapTweak, secp256k1::XOnlyPublicKey, Address, AddressType, Network, ScriptBuf}; +use bitcoin_bosd::Descriptor; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::Buf32; + +use crate::{BitcoinAddress, ParseError}; + +/// A wrapper around [`Buf32`] for XOnly Schnorr taproot pubkeys. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct XOnlyPk(Buf32); + +impl XOnlyPk { + /// Construct a new [`XOnlyPk`] directly from a [`Buf32`]. + pub fn new(val: Buf32) -> Result { + if Self::is_valid_xonly_public_key(&val) { + Ok(Self(val)) + } else { + Err(ParseError::InvalidPoint(val)) + } + } + + /// Get the underlying [`Buf32`]. + pub fn inner(&self) -> &Buf32 { + &self.0 + } + + /// Convert a [`BitcoinAddress`] into a [`XOnlyPk`]. + pub fn from_address(checked_addr: &BitcoinAddress) -> Result { + let checked_addr = checked_addr.address(); + + if let Some(AddressType::P2tr) = checked_addr.address_type() { + let script_pubkey = checked_addr.script_pubkey(); + + // skip the version and length bytes + let pubkey_bytes = &script_pubkey.as_bytes()[2..34]; + let output_key: XOnlyPublicKey = XOnlyPublicKey::from_slice(pubkey_bytes)?; + + Ok(Self(Buf32(output_key.serialize()))) + } else { + Err(ParseError::UnsupportedAddress(checked_addr.address_type())) + } + } + + /// Convert the [`XOnlyPk`] to a `rust-bitcoin`'s [`XOnlyPublicKey`]. + pub fn to_xonly_public_key(&self) -> XOnlyPublicKey { + XOnlyPublicKey::from_slice(self.0.as_bytes()).expect("XOnlyPk is valid") + } + + /// Convert the [`XOnlyPk`] to an [`Address`]. + pub fn to_p2tr_address(&self, network: Network) -> Result { + let buf: [u8; 32] = self.0.0; + let pubkey = XOnlyPublicKey::from_slice(&buf)?; + + Ok(Address::p2tr_tweaked( + pubkey.dangerous_assume_tweaked(), + network, + )) + } + + /// Converts [`XOnlyPk`] to [`Descriptor`]. + pub fn to_descriptor(&self) -> Result { + Descriptor::new_p2tr(&self.to_xonly_public_key().serialize()) + .map_err(|_| ParseError::InvalidPoint(self.0)) + } + + /// Checks if the [`Buf32`] is a valid [`XOnlyPublicKey`]. + fn is_valid_xonly_public_key(buf: &Buf32) -> bool { + XOnlyPublicKey::from_slice(buf.as_bytes()).is_ok() + } +} + +impl From for XOnlyPk { + fn from(value: XOnlyPublicKey) -> Self { + Self(Buf32(value.serialize())) + } +} + +impl TryFrom for Descriptor { + type Error = ParseError; + + fn try_from(value: XOnlyPk) -> Result { + let inner_xonly_pk = XOnlyPublicKey::try_from(value.0)?; + Ok(inner_xonly_pk.into()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct BitcoinScriptBuf(ScriptBuf); + +impl BitcoinScriptBuf { + pub fn inner(&self) -> &ScriptBuf { + &self.0 + } +} + +impl From for BitcoinScriptBuf { + fn from(value: ScriptBuf) -> Self { + Self(value) + } +} + +// Implement BorshSerialize for BitcoinScriptBuf +impl BorshSerialize for BitcoinScriptBuf { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let script_bytes = self.0.to_bytes(); + BorshSerialize::serialize(&(script_bytes.len() as u32), writer)?; + writer.write_all(&script_bytes)?; + Ok(()) + } +} + +// Implement BorshDeserialize for BitcoinScriptBuf +impl BorshDeserialize for BitcoinScriptBuf { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let script_len = u32::deserialize_reader(reader)? as usize; + let mut script_bytes = vec![0u8; script_len]; + reader.read_exact(&mut script_bytes)?; + let script_pubkey = ScriptBuf::from(script_bytes); + + Ok(BitcoinScriptBuf(script_pubkey)) + } +} + +impl<'a> Arbitrary<'a> for BitcoinScriptBuf { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // Generate arbitrary script + let script_len = usize::arbitrary(u)? % 100; // Limit script length + let script_bytes = u.bytes(script_len)?; + let script = ScriptBuf::from(script_bytes.to_vec()); + + Ok(Self(script)) + } +} diff --git a/crates/btc-types/src/raw_tx.rs b/crates/btc-types/src/raw_tx.rs new file mode 100644 index 0000000000..deae52c8f1 --- /dev/null +++ b/crates/btc-types/src/raw_tx.rs @@ -0,0 +1,101 @@ +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{ + absolute::LockTime, + consensus::{deserialize, encode, serialize}, + hashes::Hash, + transaction::Version, + Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, +}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +/// Represents a raw, byte-encoded Bitcoin transaction with custom [`Arbitrary`] support. +/// Provides conversions (via [`TryFrom`]) to and from [`Transaction`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct RawBitcoinTx(Vec); + +impl RawBitcoinTx { + /// Creates a new `RawBitcoinTx` from a raw byte vector. + pub fn from_raw_bytes(bytes: Vec) -> Self { + RawBitcoinTx(bytes) + } +} + +impl From for RawBitcoinTx { + fn from(value: Transaction) -> Self { + Self(serialize(&value)) + } +} + +impl TryFrom for Transaction { + type Error = encode::Error; + fn try_from(value: RawBitcoinTx) -> Result { + deserialize(&value.0) + } +} + +impl TryFrom<&RawBitcoinTx> for Transaction { + type Error = encode::Error; + fn try_from(value: &RawBitcoinTx) -> Result { + deserialize(&value.0) + } +} + +impl<'a> Arbitrary<'a> for RawBitcoinTx { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // Random number of inputs and outputs (bounded for simplicity) + let input_count = u.int_in_range::(0..=4)?; + let output_count = u.int_in_range::(0..=4)?; + + // Build random inputs + let mut inputs = Vec::with_capacity(input_count); + for _ in 0..input_count { + // Random 32-byte TXID + let mut txid_bytes = [0u8; 32]; + u.fill_buffer(&mut txid_bytes)?; + let txid = Txid::from_raw_hash(bitcoin::hashes::Hash::from_byte_array(txid_bytes)); + + // Random vout + let vout = u32::arbitrary(u)?; + + // Random scriptSig (bounded size) + let script_sig_size = u.int_in_range::(0..=50)?; + let script_sig_bytes = u.bytes(script_sig_size)?; + let script_sig = ScriptBuf::from_bytes(script_sig_bytes.to_vec()); + + inputs.push(TxIn { + previous_output: OutPoint { txid, vout }, + script_sig, + sequence: Sequence::MAX, + witness: Witness::default(), // or generate random witness if desired + }); + } + + // Build random outputs + let mut outputs = Vec::with_capacity(output_count); + for _ in 0..output_count { + // Random value (in satoshis) + let value = Amount::from_sat(u64::arbitrary(u)?); + + // Random scriptPubKey (bounded size) + let script_pubkey_size = u.int_in_range::(0..=50)?; + let script_pubkey_bytes = u.bytes(script_pubkey_size)?; + let script_pubkey = ScriptBuf::from(script_pubkey_bytes.to_vec()); + + outputs.push(TxOut { + value, + script_pubkey, + }); + } + + // Construct the transaction + let tx = Transaction { + version: Version::ONE, + lock_time: LockTime::ZERO, + input: inputs, + output: outputs, + }; + + Ok(tx.into()) + } +} diff --git a/crates/btc-types/src/transaction.rs b/crates/btc-types/src/transaction.rs new file mode 100644 index 0000000000..aa64089a91 --- /dev/null +++ b/crates/btc-types/src/transaction.rs @@ -0,0 +1,284 @@ +use std::{ + fmt::{self, Debug, Display}, + io::{Read, Write}, + str, +}; + +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{ + hashes::Hash, + key::{rand, Keypair, Parity}, + secp256k1::{SecretKey, XOnlyPublicKey, SECP256K1}, + taproot::{ControlBlock, LeafVersion, TaprootMerkleBranch}, + Amount, ScriptBuf, TapNodeHash, TxOut, +}; +use borsh::{BorshDeserialize, BorshSerialize}; +use hex::encode_to_slice; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use strata_identifiers::Buf32; + +/// A wrapper around [`bitcoin::TxOut`] that implements some additional traits. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BitcoinTxOut(TxOut); + +impl BitcoinTxOut { + pub fn inner(&self) -> &TxOut { + &self.0 + } +} + +impl From for BitcoinTxOut { + fn from(value: TxOut) -> Self { + Self(value) + } +} + +impl From for TxOut { + fn from(value: BitcoinTxOut) -> Self { + value.0 + } +} + +// Implement BorshSerialize for BitcoinTxOut +impl BorshSerialize for BitcoinTxOut { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + // Serialize the value (u64) + BorshSerialize::serialize(&self.0.value.to_sat(), writer)?; + + // Serialize the script_pubkey (ScriptBuf) + let script_bytes = self.0.script_pubkey.to_bytes(); + BorshSerialize::serialize(&(script_bytes.len() as u64), writer)?; + writer.write_all(&script_bytes)?; + + Ok(()) + } +} + +// Implement BorshDeserialize for BitcoinTxOut +impl BorshDeserialize for BitcoinTxOut { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + // Deserialize the value (u64) + let value = u64::deserialize_reader(reader)?; + + // Deserialize the script_pubkey (ScriptBuf) + let script_len = u64::deserialize_reader(reader)? as usize; + let mut script_bytes = vec![0u8; script_len]; + reader.read_exact(&mut script_bytes)?; + let script_pubkey = ScriptBuf::from(script_bytes); + + Ok(BitcoinTxOut(TxOut { + value: Amount::from_sat(value), + script_pubkey, + })) + } +} + +/// Implement Arbitrary for ArbitraryTxOut +impl<'a> Arbitrary<'a> for BitcoinTxOut { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // Generate arbitrary value and script for the TxOut + let value = u64::arbitrary(u)?; + let script_len = usize::arbitrary(u)? % 100; // Limit script length + let script_bytes = u.bytes(script_len)?; + let script_pubkey = ScriptBuf::from(script_bytes.to_vec()); + + Ok(Self(TxOut { + value: Amount::from_sat(value), + script_pubkey, + })) + } +} + +/// The components required in the witness stack to spend a taproot output. +/// +/// If a script-path path is being used, the witness stack needs the script being spent and the +/// control block in addition to the signature. +/// See [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TaprootSpendPath { + /// Use the keypath spend. + /// + /// This only requires the signature for the tweaked internal key and nothing else. + Key, + + /// Use the script path spend. + /// + /// This requires the script being spent from as well as the [`ControlBlock`] in addition to + /// the elements that fulfill the spending condition in the script. + Script { + script_buf: ScriptBuf, + control_block: ControlBlock, + }, +} + +impl BorshSerialize for TaprootSpendPath { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + match self { + TaprootSpendPath::Key => { + // Variant index for Keypath is 0 + BorshSerialize::serialize(&0u32, writer)?; + } + TaprootSpendPath::Script { + script_buf, + control_block, + } => { + // Variant index for ScriptPath is 1 + BorshSerialize::serialize(&1u32, writer)?; + + // Serialize the ScriptBuf + let script_bytes = script_buf.to_bytes(); + BorshSerialize::serialize(&(script_bytes.len() as u64), writer)?; + writer.write_all(&script_bytes)?; + + // Serialize the ControlBlock using bitcoin's serialize method + let control_block_bytes = control_block.serialize(); + BorshSerialize::serialize(&(control_block_bytes.len() as u64), writer)?; + writer.write_all(&control_block_bytes)?; + } + } + Ok(()) + } +} + +// Implement BorshDeserialize for TaprootSpendInfo +impl BorshDeserialize for TaprootSpendPath { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + // Deserialize the variant index + let variant: u32 = BorshDeserialize::deserialize_reader(reader)?; + match variant { + 0 => Ok(TaprootSpendPath::Key), + 1 => { + // Deserialize the ScriptBuf + let script_len = u64::deserialize_reader(reader)? as usize; + let mut script_bytes = vec![0u8; script_len]; + reader.read_exact(&mut script_bytes)?; + let script_buf = ScriptBuf::from(script_bytes); + + // Deserialize the ControlBlock + let control_block_len = u64::deserialize_reader(reader)? as usize; + let mut control_block_bytes = vec![0u8; control_block_len]; + reader.read_exact(&mut control_block_bytes)?; + let control_block: ControlBlock = ControlBlock::decode(&control_block_bytes[..]) + .map_err(|_| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid ControlBlock") + })?; + + Ok(TaprootSpendPath::Script { + script_buf, + control_block, + }) + } + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unknown variant for TaprootSpendInfo", + )), + } + } +} + +// Implement Arbitrary for TaprootSpendInfo +impl<'a> Arbitrary<'a> for TaprootSpendPath { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // Randomly decide which variant to generate + let variant = u.int_in_range(0..=1)?; + match variant { + 0 => Ok(TaprootSpendPath::Key), + 1 => { + // Arbitrary ScriptBuf (the script part of SpendInfo) + let script_len = usize::arbitrary(u)? % 100; // Limit the length of the script for practicality + let script_bytes = u.bytes(script_len)?; // Generate random bytes for the script + let script_buf = ScriptBuf::from(script_bytes.to_vec()); + + // Now we will manually generate the fields of the ControlBlock struct + + // Leaf version + let leaf_version = LeafVersion::TapScript; + + // Output key parity (Even or Odd) + let output_key_parity = if bool::arbitrary(u)? { + Parity::Even + } else { + Parity::Odd + }; + + // Generate a random secret key and derive the internal key + let secret_key = SecretKey::new(&mut OsRng); + let keypair = Keypair::from_secret_key(SECP256K1, &secret_key); + let (internal_key, _) = XOnlyPublicKey::from_keypair(&keypair); + + // Arbitrary Taproot merkle branch (vector of 32-byte hashes) + const BRANCH_LENGTH: usize = 10; + let mut tapnode_hashes: Vec = Vec::with_capacity(BRANCH_LENGTH); + for _ in 0..BRANCH_LENGTH { + let hash = TapNodeHash::from_byte_array(<[u8; 32]>::arbitrary(u)?); + tapnode_hashes.push(hash); + } + + let tapnode_hashes: &[TapNodeHash; BRANCH_LENGTH] = + &tapnode_hashes[..BRANCH_LENGTH].try_into().unwrap(); + + let merkle_branch = TaprootMerkleBranch::from(*tapnode_hashes); + + // Construct the ControlBlock manually + let control_block = ControlBlock { + leaf_version, + output_key_parity, + internal_key, + merkle_branch, + }; + + // Construct the ScriptPath variant + Ok(TaprootSpendPath::Script { + script_buf, + control_block, + }) + } + _ => unreachable!(), + } + } +} + +/// Outpoint of a bitcoin tx +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, BorshSerialize, BorshDeserialize)] +pub struct Outpoint { + pub txid: Buf32, + pub vout: u32, +} + +// Custom debug implementation to print txid in little endian +impl Debug for Outpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut txid_buf = [0u8; 64]; + { + let mut bytes = self.txid.0; + bytes.reverse(); + encode_to_slice(bytes, &mut txid_buf).expect("buf: enc hex"); + } + + f.debug_struct("Outpoint") + .field("txid", &unsafe { std::str::from_utf8_unchecked(&txid_buf) }) + .field("vout", &self.vout) + .finish() + } +} + +// Custom display implementation to print txid in little endian +impl Display for Outpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut txid_buf = [0u8; 64]; + { + let mut bytes = self.txid.0; + bytes.reverse(); + encode_to_slice(bytes, &mut txid_buf).expect("buf: enc hex"); + } + + write!( + f, + "Outpoint {{ txid: {}, vout: {} }}", + // SAFETY: hex encoding always produces valid UTF-8 + unsafe { str::from_utf8_unchecked(&txid_buf) }, + self.vout + ) + } +} diff --git a/crates/btc-types/src/txid.rs b/crates/btc-types/src/txid.rs new file mode 100644 index 0000000000..acea4d5aee --- /dev/null +++ b/crates/btc-types/src/txid.rs @@ -0,0 +1,86 @@ +use std::io::{Read, Write}; + +use arbitrary::{Arbitrary, Unstructured}; +use bitcoin::{hashes::Hash, Txid}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::Buf32; + +use crate::constants::HASH_SIZE; + +/// [Borsh](borsh)-friendly Bitcoin [`Txid`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BitcoinTxid(Txid); + +impl From for BitcoinTxid { + fn from(value: Txid) -> Self { + Self(value) + } +} + +impl From for Txid { + fn from(value: BitcoinTxid) -> Self { + value.0 + } +} + +impl BitcoinTxid { + /// Creates a new [`BitcoinTxid`] from a [`Txid`]. + /// + /// # Notes + /// + /// [`Txid`] is [`Copy`]. + pub fn new(txid: &Txid) -> Self { + BitcoinTxid(*txid) + } + + /// Gets the inner Bitcoin [`Txid`] + pub fn inner(&self) -> Txid { + self.0 + } + + /// Gets the inner Bitcoin [`Txid`] as raw bytes [`Buf32`]. + pub fn inner_raw(&self) -> Buf32 { + self.0.to_byte_array().into() + } +} + +impl BorshSerialize for BitcoinTxid { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + // Serialize the txid using bitcoin's built-in serialization + let txid_bytes = self.0.to_byte_array(); + // First, write the length of the serialized txid (as u32) + BorshSerialize::serialize(&(32_u32), writer)?; + // Then, write the actual serialized PSBT bytes + writer.write_all(&txid_bytes)?; + Ok(()) + } +} + +impl BorshDeserialize for BitcoinTxid { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + // First, read the length tag + let len = u32::deserialize_reader(reader)? as usize; + + if len != HASH_SIZE { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Invalid Txid size, expected: {HASH_SIZE}, got: {len}"), + )); + } + + // First, create a buffer to hold the txid bytes and read them + let mut txid_bytes = [0u8; HASH_SIZE]; + reader.read_exact(&mut txid_bytes)?; + // Use the bitcoin crate's deserialize method to create a Psbt from the bytes + let txid = Txid::from_byte_array(txid_bytes); + Ok(BitcoinTxid(txid)) + } +} + +impl<'a> Arbitrary<'a> for BitcoinTxid { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let value = Buf32::arbitrary(u)?; + Ok(Self(Txid::from_byte_array(value.into()))) + } +} diff --git a/crates/btcio/src/broadcaster/task.rs b/crates/btcio/src/broadcaster/task.rs index 8ecf5f854b..236a740de8 100644 --- a/crates/btcio/src/broadcaster/task.rs +++ b/crates/btcio/src/broadcaster/task.rs @@ -3,7 +3,7 @@ use std::{sync::Arc, time::Duration}; use bitcoin::{hashes::Hash, Txid}; use bitcoind_async_client::traits::{Broadcaster, Wallet}; use strata_db::types::{L1TxEntry, L1TxStatus}; -use strata_primitives::params::Params; +use strata_params::Params; use strata_storage::{ops::l1tx_broadcast, BroadcastDbOps}; use tokio::sync::mpsc::Receiver; use tracing::*; diff --git a/crates/btcio/src/reader/handler.rs b/crates/btcio/src/reader/handler.rs index da2bc7a6f8..71d4766d4b 100644 --- a/crates/btcio/src/reader/handler.rs +++ b/crates/btcio/src/reader/handler.rs @@ -1,7 +1,8 @@ use bitcoin::{consensus::serialize, hashes::Hash, Block}; use bitcoind_async_client::traits::Reader; -use strata_asm_types::{generate_l1_tx, L1BlockManifest, L1HeaderRecord, L1Tx}; -use strata_primitives::{buf::Buf32, l1::L1BlockCommitment}; +use strata_btc_types::legacy::{generate_l1_tx, L1BlockManifest, L1HeaderRecord, L1Tx}; +use strata_identifiers::Buf32; +use strata_primitives::l1::L1BlockCommitment; use strata_state::BlockSubmitter; use tracing::*; diff --git a/crates/btcio/src/writer/context.rs b/crates/btcio/src/writer/context.rs index 75c79246dd..d37d45cae9 100644 --- a/crates/btcio/src/writer/context.rs +++ b/crates/btcio/src/writer/context.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use bitcoin::Address; use bitcoind_async_client::traits::{Reader, Signer, Wallet}; use strata_config::btcio::WriterConfig; -use strata_primitives::params::Params; +use strata_params::Params; use strata_status::StatusChannel; /// All the items that writer tasks need as context. diff --git a/crates/btcio/src/writer/signer.rs b/crates/btcio/src/writer/signer.rs index 4e5d7698d3..adf9b9677f 100644 --- a/crates/btcio/src/writer/signer.rs +++ b/crates/btcio/src/writer/signer.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use bitcoin::{consensus, Transaction}; use bitcoind_async_client::traits::{Reader, Signer, Wallet}; use strata_db::types::{BundledPayloadEntry, L1TxEntry}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use tracing::*; use super::{ diff --git a/crates/chain-worker/Cargo.toml b/crates/chain-worker/Cargo.toml index 09337ba7f5..f75b6f9a33 100644 --- a/crates/chain-worker/Cargo.toml +++ b/crates/chain-worker/Cargo.toml @@ -13,6 +13,7 @@ strata-checkpoint-types.workspace = true strata-eectl.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true +strata-params.workspace = true strata-primitives.workspace = true strata-service.workspace = true strata-status.workspace = true diff --git a/crates/chain-worker/src/builder.rs b/crates/chain-worker/src/builder.rs index 3b21172300..dfd7f65f49 100644 --- a/crates/chain-worker/src/builder.rs +++ b/crates/chain-worker/src/builder.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use strata_eectl::handle::ExecCtlHandle; -use strata_primitives::params::Params; +use strata_params::Params; use strata_service::ServiceBuilder; use strata_status::StatusChannel; use strata_tasks::TaskExecutor; diff --git a/crates/chain-worker/src/service.rs b/crates/chain-worker/src/service.rs index 6a57c98c99..f972a57417 100644 --- a/crates/chain-worker/src/service.rs +++ b/crates/chain-worker/src/service.rs @@ -7,7 +7,8 @@ use strata_chainexec::{BlockExecutionOutput, ChainExecutor}; use strata_checkpoint_types::EpochSummary; use strata_eectl::handle::ExecCtlHandle; use strata_ol_chain_types::{L2Block, L2Header}; -use strata_primitives::{params::Params, prelude::*}; +use strata_params::Params; +use strata_primitives::prelude::*; use strata_service::{Response, Service, ServiceState, SyncService}; use strata_status::StatusChannel; use tokio::{runtime::Handle, sync::Mutex}; diff --git a/crates/chainexec/Cargo.toml b/crates/chainexec/Cargo.toml index f2da59f210..9d84534235 100644 --- a/crates/chainexec/Cargo.toml +++ b/crates/chainexec/Cargo.toml @@ -10,6 +10,7 @@ workspace = true strata-chaintsn.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true +strata-params.workspace = true strata-primitives.workspace = true thiserror.workspace = true diff --git a/crates/chainexec/src/executor.rs b/crates/chainexec/src/executor.rs index 70ee7ad983..ab5332191e 100644 --- a/crates/chainexec/src/executor.rs +++ b/crates/chainexec/src/executor.rs @@ -5,6 +5,7 @@ use strata_chaintsn::{ transition::process_block, }; use strata_ol_chain_types::{L2BlockBody, L2Header}; +use strata_params::RollupParams; use strata_primitives::prelude::*; use crate::{BlockExecutionOutput, Error, ExecContext, MemStateAccessor}; diff --git a/crates/chainexec/src/validation_util.rs b/crates/chainexec/src/validation_util.rs index 3000af0d46..490a536a62 100644 --- a/crates/chainexec/src/validation_util.rs +++ b/crates/chainexec/src/validation_util.rs @@ -1,6 +1,7 @@ //! Extra utility functions for block proposal correctness checks. use strata_ol_chain_types::{BlockCheckError, L2Block, L2Header, check_block_credential}; +use strata_params::RollupParams; use strata_primitives::prelude::*; /// Checks a block's credential to ensure that it was authentically proposed. diff --git a/crates/chaintsn/Cargo.toml b/crates/chaintsn/Cargo.toml index 60177033b2..a8aa939c37 100644 --- a/crates/chaintsn/Cargo.toml +++ b/crates/chaintsn/Cargo.toml @@ -8,10 +8,13 @@ workspace = true [dependencies] strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true strata-crypto.workspace = true +strata-identifiers.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true +strata-params.workspace = true strata-primitives.workspace = true strata-state.workspace = true diff --git a/crates/chaintsn/src/checkin.rs b/crates/chaintsn/src/checkin.rs index 413827b13a..ef7e382a45 100644 --- a/crates/chaintsn/src/checkin.rs +++ b/crates/chaintsn/src/checkin.rs @@ -1,14 +1,14 @@ //! L1 check-in logic. use bitcoin::{block::Header, consensus}; -use strata_asm_types::{ +use strata_btc_types::{ DepositInfo, DepositSpendInfo, L1BlockManifest, ProtocolOperation, WithdrawalFulfillmentInfo, }; +use strata_ol_chain_types::DepositIntent; use strata_checkpoint_types::{verify_signed_checkpoint_sig, SignedCheckpoint}; use strata_crypto::groth16_verifier::verify_rollup_groth16_proof_receipt; use strata_ol_chain_types::L1Segment; -use strata_primitives::params::RollupParams; -use strata_state::bridge_ops::DepositIntent; +use strata_params::RollupParams; use crate::{ context::{AuxProvider, ProviderError, ProviderResult, StateAccessor}, diff --git a/crates/chaintsn/src/context.rs b/crates/chaintsn/src/context.rs index 02915095f3..3a839e49fb 100644 --- a/crates/chaintsn/src/context.rs +++ b/crates/chaintsn/src/context.rs @@ -1,6 +1,6 @@ //! Interfaces to expose the context in which a block is being validated. -use strata_asm_types::L1BlockManifest; +use strata_btc_types::L1BlockManifest; use strata_ol_chain_types::{L2BlockHeader, L2BlockId, L2Header}; use strata_ol_chainstate_types::Chainstate; use strata_primitives::prelude::*; diff --git a/crates/chaintsn/src/legacy.rs b/crates/chaintsn/src/legacy.rs index 0011f6c04c..aa9820f8fb 100644 --- a/crates/chaintsn/src/legacy.rs +++ b/crates/chaintsn/src/legacy.rs @@ -1,14 +1,16 @@ //! Legacy routines extracted from `StateCache`. use bitcoin::block::Header; -use strata_asm_types::{L1VerificationError, WithdrawalFulfillmentInfo}; +use strata_asm_types::L1VerificationError; +use strata_btc_types::WithdrawalFulfillmentInfo; use strata_ol_chainstate_types::Chainstate; use strata_primitives::{ bridge::{BitcoinBlockHeight, OperatorIdx}, l1::*, prelude::*, }; -use strata_state::{bridge_ops::DepositIntent, bridge_state::*}; +use strata_ol_chain_types::DepositIntent; +use strata_state::bridge_state::*; use crate::{context::StateAccessor, macros::*}; diff --git a/crates/chaintsn/src/transition.rs b/crates/chaintsn/src/transition.rs index 00987cc1e9..ccce28df12 100644 --- a/crates/chaintsn/src/transition.rs +++ b/crates/chaintsn/src/transition.rs @@ -2,21 +2,17 @@ //! we'll replace components with real implementations as we go along. use rand_core::{RngCore, SeedableRng}; -use strata_asm_types::{ +use strata_btc_types::{ DepositInfo, DepositSpendInfo, L1BlockManifest, ProtocolOperation, WithdrawalFulfillmentInfo, }; use strata_checkpoint_types::{verify_signed_checkpoint_sig, Checkpoint, SignedCheckpoint}; use strata_crypto::groth16_verifier::verify_rollup_groth16_proof_receipt; use strata_ol_chain_types::{L2BlockBody, L2BlockHeader, L2Header}; use strata_ol_chainstate_types::StateCache; -use strata_primitives::{ - epoch::EpochCommitment, l1::L1BlockId, l2::L2BlockCommitment, params::RollupParams, -}; -use strata_state::{ - bridge_ops::{DepositIntent, WithdrawalIntent}, - bridge_state::{DepositState, DispatchCommand, WithdrawOutput}, - exec_update::{self, Op}, -}; +use strata_params::RollupParams; +use strata_identifiers::{EpochCommitment, L1BlockId, L2BlockCommitment}; +use strata_ol_chain_types::{DepositIntent, ExecUpdate, Op, WithdrawalIntent}; +use strata_state::bridge_state::{DepositState, DispatchCommand, WithdrawOutput}; use tracing::warn; use zkaleido::ZkVmResult; @@ -354,7 +350,7 @@ fn check_chain_integrity( /// This will probably be substantially refactored in the future though. fn process_execution_update<'s, 'u, S: StateAccessor>( state: &mut FauxStateCache<'s, S>, - update: &'u exec_update::ExecUpdate, + update: &'u ExecUpdate, ) -> Result<&'u [WithdrawalIntent], TsnError> { // for all the ops, corresponding to DepositIntent, remove those DepositIntent the ExecEnvState let applied_ops = update.input().applied_ops(); @@ -521,9 +517,10 @@ fn next_rand_op_pos(rng: &mut SlotRng, num: u32) -> u32 { #[cfg(test)] mod tests { use rand_core::SeedableRng; - use strata_asm_types::{L1BlockManifest, L1Tx, ProtocolOperation, WithdrawalFulfillmentInfo}; + use strata_btc_types::legacy::{L1BlockManifest, L1Tx, ProtocolOperation, WithdrawalFulfillmentInfo}; use strata_ol_chainstate_types::{Chainstate, StateCache}; - use strata_primitives::{buf::Buf32, l1::BitcoinAmount}; + use strata_identifiers::Buf32; + use strata_primitives::l1::BitcoinAmount; use strata_state::bridge_state::{ DepositState, DepositsTable, DispatchCommand, DispatchedState, }; diff --git a/crates/checkpoint-types/Cargo.toml b/crates/checkpoint-types/Cargo.toml index d3c545492d..562f9fa63e 100644 --- a/crates/checkpoint-types/Cargo.toml +++ b/crates/checkpoint-types/Cargo.toml @@ -7,7 +7,8 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] -strata-primitives.workspace = true +strata-crypto.workspace = true +strata-identifiers.workspace = true arbitrary.workspace = true borsh.workspace = true diff --git a/crates/checkpoint-types/src/batch.rs b/crates/checkpoint-types/src/batch.rs index 3f515def90..b4f47b66c9 100644 --- a/crates/checkpoint-types/src/batch.rs +++ b/crates/checkpoint-types/src/batch.rs @@ -3,12 +3,7 @@ use std::fmt; use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - buf::Buf32, - epoch::EpochCommitment, - l1::L1BlockCommitment, - l2::{L2BlockCommitment, L2BlockId}, -}; +use strata_identifiers::{Buf32, EpochCommitment, L1BlockCommitment, L2BlockCommitment, L2BlockId}; /// Summary generated when we accept the last block of an epoch. /// diff --git a/crates/checkpoint-types/src/checkpoint.rs b/crates/checkpoint-types/src/checkpoint.rs index 57d9985170..67808932d2 100644 --- a/crates/checkpoint-types/src/checkpoint.rs +++ b/crates/checkpoint-types/src/checkpoint.rs @@ -1,12 +1,8 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - block_credential::CredRule, - buf::{Buf32, Buf64}, - crypto::verify_schnorr_sig, - hash, -}; +use strata_crypto::schnorr::verify_schnorr_sig; +use strata_identifiers::{hash, Buf32, Buf64, CredRule}; use zkaleido::{Proof, ProofReceipt, PublicValues}; use super::{batch::BatchInfo, transition::BatchTransition}; diff --git a/crates/checkpoint-types/src/transition.rs b/crates/checkpoint-types/src/transition.rs index 3dd69cb290..946a0f4746 100644 --- a/crates/checkpoint-types/src/transition.rs +++ b/crates/checkpoint-types/src/transition.rs @@ -1,7 +1,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; /// Contains transition information in a batch checkpoint, verified by the proof #[derive( diff --git a/crates/consensus-logic/Cargo.toml b/crates/consensus-logic/Cargo.toml index 207550ba02..e981dd3821 100644 --- a/crates/consensus-logic/Cargo.toml +++ b/crates/consensus-logic/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] strata-asm-types.workspace = true strata-asm-worker.workspace = true +strata-btc-types.workspace = true strata-chain-worker.workspace = true strata-chainexec.workspace = true strata-chaintsn.workspace = true @@ -16,8 +17,10 @@ strata-checkpoint-types.workspace = true strata-common = { workspace = true, default-features = true } strata-db.workspace = true strata-eectl.workspace = true +strata-identifiers.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true +strata-params.workspace = true strata-primitives.workspace = true strata-state.workspace = true strata-status.workspace = true diff --git a/crates/consensus-logic/src/checkpoint_verification.rs b/crates/consensus-logic/src/checkpoint_verification.rs index e8ffb1f054..6af0c6dd1f 100644 --- a/crates/consensus-logic/src/checkpoint_verification.rs +++ b/crates/consensus-logic/src/checkpoint_verification.rs @@ -2,7 +2,7 @@ use strata_chaintsn::transition::verify_checkpoint_proof; use strata_checkpoint_types::{BatchTransition, Checkpoint}; -use strata_primitives::params::*; +use strata_params::*; use strata_state::client_state::L1Checkpoint; use tracing::*; use zkaleido::{ProofReceipt, ZkVmError, ZkVmResult}; diff --git a/crates/consensus-logic/src/csm/client_transition.rs b/crates/consensus-logic/src/csm/client_transition.rs index 78b7d7d732..92f88631be 100644 --- a/crates/consensus-logic/src/csm/client_transition.rs +++ b/crates/consensus-logic/src/csm/client_transition.rs @@ -1,12 +1,11 @@ //! Core state transition function. use bitcoin::Transaction; -use strata_asm_types::{L1BlockManifest, L1Tx, ProtocolOperation}; +use strata_btc_types::{L1BlockManifest, L1Tx, ProtocolOperation}; use strata_checkpoint_types::verify_signed_checkpoint_sig; -use strata_primitives::{ - l1::{L1BlockCommitment, L1BlockId}, - prelude::*, -}; +use strata_identifiers::{L1BlockCommitment, L1BlockId}; +use strata_params::{Params, RollupParams}; +use strata_primitives::prelude::*; use strata_state::{client_state::*, operation::*}; use strata_storage::NodeStorage; use tracing::*; diff --git a/crates/consensus-logic/src/csm/worker.rs b/crates/consensus-logic/src/csm/worker.rs index 1dcc2b0c32..7abde2eb63 100644 --- a/crates/consensus-logic/src/csm/worker.rs +++ b/crates/consensus-logic/src/csm/worker.rs @@ -2,8 +2,9 @@ use std::{sync::Arc, thread}; -use strata_asm_types::L1BlockManifest; +use strata_btc_types::L1BlockManifest; use strata_db::types::{CheckpointConfStatus, CheckpointEntry, CheckpointProvingStatus}; +use strata_params::Params; use strata_primitives::prelude::*; use strata_state::{ client_state::ClientState, @@ -146,7 +147,7 @@ fn process_block_with_retries( // Handle pre-genesis: if the block is before genesis we don't care about it. let genesis_trigger = state.params.rollup().genesis_l1_view.height(); let height = incoming_block.height(); - if incoming_block.height() < genesis_trigger { + if incoming_block.height().to_consensus_u32() < genesis_trigger { #[cfg(test)] eprintln!( "early L1 block at h={height} (gt={genesis_trigger}) you may have set up the test env wrong" @@ -164,7 +165,7 @@ fn process_block_with_retries( let mut cur_state = ctx.get_client_state(&cur_block); while cur_state.is_err() - && cur_block.height() >= state.params.rollup().genesis_l1_view.height() + && cur_block.height().to_consensus_u32() >= state.params.rollup().genesis_l1_view.height() { let cur_block_mf = ctx.get_l1_block_manifest(cur_block.blkid())?; let prev_block_id = cur_block_mf.get_prev_blockid(); @@ -179,7 +180,7 @@ fn process_block_with_retries( cur_state = ctx.get_client_state(&cur_block); } - if cur_block.height() < state.params.rollup().genesis_l1_view.height() { + if cur_block.height().to_consensus_u32() < state.params.rollup().genesis_l1_view.height() { // we reached the height before genesis (while traversing the tree of ClientStates), // for such a case there shouldn't be any ClientState besides the default one. (Default::default(), Default::default()) diff --git a/crates/consensus-logic/src/fork_choice_manager.rs b/crates/consensus-logic/src/fork_choice_manager.rs index b87c43988b..d7c961fb2c 100644 --- a/crates/consensus-logic/src/fork_choice_manager.rs +++ b/crates/consensus-logic/src/fork_choice_manager.rs @@ -8,7 +8,8 @@ use strata_db::{errors::DbError, traits::BlockStatus, types::CheckpointConfStatu use strata_eectl::errors::EngineError; use strata_ol_chain_types::{L2BlockBundle, L2BlockId, L2Header}; use strata_ol_chainstate_types::Chainstate; -use strata_primitives::{epoch::EpochCommitment, l2::L2BlockCommitment, params::Params}; +use strata_identifiers::{EpochCommitment, L2BlockCommitment}; +use strata_params::Params; use strata_state::client_state::{CheckpointState, ClientState}; use strata_status::*; use strata_storage::{L2BlockManager, NodeStorage}; diff --git a/crates/consensus-logic/src/genesis.rs b/crates/consensus-logic/src/genesis.rs index 88971587f9..306a062d63 100644 --- a/crates/consensus-logic/src/genesis.rs +++ b/crates/consensus-logic/src/genesis.rs @@ -5,17 +5,15 @@ use strata_ol_chain_types::{ L2Header, SignedL2BlockHeader, }; use strata_ol_chainstate_types::{Chainstate, GenesisStateData, L1ViewState, WriteBatch}; -use strata_primitives::{ - buf::{Buf32, Buf64}, - constants::TIMESTAMPS_FOR_MEDIAN, - evm_exec::create_evm_extra_payload, - params::{OperatorConfig, Params}, -}; +use strata_btc_types::constants::TIMESTAMPS_FOR_MEDIAN; +use strata_identifiers::{Buf32, Buf64}; +use strata_params::{OperatorConfig, Params}; +use strata_primitives::evm_exec::create_evm_extra_payload; +use strata_ol_chain_types::{ExecUpdate, UpdateInput, UpdateOutput}; use strata_state::{ bridge_state::OperatorTable, client_state::ClientState, exec_env::ExecEnvState, - exec_update::{ExecUpdate, UpdateInput, UpdateOutput}, operation::ClientUpdateOutput, prelude::*, }; diff --git a/crates/consensus-logic/src/sync_manager.rs b/crates/consensus-logic/src/sync_manager.rs index 0b0d508b68..d6c8d3e250 100644 --- a/crates/consensus-logic/src/sync_manager.rs +++ b/crates/consensus-logic/src/sync_manager.rs @@ -8,7 +8,8 @@ use bitcoind_async_client::Client; use strata_asm_worker::AsmWorkerHandle; use strata_chain_worker::ChainWorkerHandle; use strata_eectl::{engine::ExecEngineCtl, handle::ExecCtlHandle}; -use strata_primitives::{l1::L1BlockCommitment, params::Params}; +use strata_identifiers::L1BlockCommitment; +use strata_params::Params; use strata_status::StatusChannel; use strata_storage::NodeStorage; use strata_tasks::TaskExecutor; diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 7f9c3d5599..0ee548ec53 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -7,20 +7,32 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] -strata-primitives.workspace = true +strata-identifiers.workspace = true arbitrary.workspace = true +bincode.workspace = true bitcoin.workspace = true bitvec.workspace = true borsh.workspace = true musig2.workspace = true rand = { workspace = true, optional = true } +serde.workspace = true thiserror.workspace = true zkaleido.workspace = true +zkaleido-risc0-groth16-verifier.workspace = true +zkaleido-sp1-groth16-verifier.workspace = true + +[target.'cfg(target_os = "zkvm")'.dependencies] +k256 = { version = "0.13.4", features = ["schnorr"] } + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +secp256k1.workspace = true [dev-dependencies] strata-test-utils.workspace = true [features] -default = [] -test-utils = ["dep:rand"] +default = ["std", "rand"] +rand = ["std", "dep:rand"] +std = [] +test-utils = ["rand"] diff --git a/crates/crypto/src/groth16_verifier.rs b/crates/crypto/src/groth16_verifier.rs index eaee1dee52..c6c3db4937 100644 --- a/crates/crypto/src/groth16_verifier.rs +++ b/crates/crypto/src/groth16_verifier.rs @@ -1,6 +1,7 @@ -use strata_primitives::proof::RollupVerifyingKey; use zkaleido::{ProofReceipt, ZkVmResult, ZkVmVerifier}; +use crate::proof_vk::RollupVerifyingKey; + pub fn verify_rollup_groth16_proof_receipt( proof_receipt: &ProofReceipt, rollup_vk: &RollupVerifyingKey, diff --git a/crates/crypto/src/lib.rs b/crates/crypto/src/lib.rs index b20aef42ce..c75e6cab3a 100644 --- a/crates/crypto/src/lib.rs +++ b/crates/crypto/src/lib.rs @@ -1,8 +1,16 @@ //! Cryptographic primitives. +#![expect( + unused_crate_dependencies, + reason = "I think clippy is wrong and I don't want to break dep hierarchy figuring it out" +)] + // FIXME this stub had to be moved to make a refactor work -pub use strata_primitives::crypto::*; pub mod groth16_verifier; pub mod multisig; +pub mod proof_vk; +pub mod schnorr; + +#[rustfmt::skip] #[cfg(feature = "test-utils")] pub mod test_utils; diff --git a/crates/crypto/src/multisig/config.rs b/crates/crypto/src/multisig/config.rs index af92ebf51d..a8acb75063 100644 --- a/crates/crypto/src/multisig/config.rs +++ b/crates/crypto/src/multisig/config.rs @@ -255,7 +255,7 @@ impl MultisigConfig { #[cfg(test)] mod tests { - use strata_primitives::buf::Buf32; + use strata_identifiers::Buf32; use strata_test_utils::ArbitraryGenerator; use super::*; diff --git a/crates/crypto/src/multisig/constants.rs b/crates/crypto/src/multisig/constants.rs new file mode 100644 index 0000000000..6d144efe03 --- /dev/null +++ b/crates/crypto/src/multisig/constants.rs @@ -0,0 +1,11 @@ +/// The size (in bytes) of a [`musig2::PartialSignature`]. +pub const MUSIG2_PARTIAL_SIG_SIZE: usize = 32; + +/// The size (in bytes) of a [`musig2::NonceSeed`]. +pub const NONCE_SEED_SIZE: usize = 32; + +/// The size (in bytes) of a [`musig2::PubNonce`]. +pub const PUB_NONCE_SIZE: usize = 66; + +/// The size (in bytes) of a [`musig2::SecNonce`]. +pub const SEC_NONCE_SIZE: usize = 64; diff --git a/crates/crypto/src/multisig/mod.rs b/crates/crypto/src/multisig/mod.rs index f4acef36a2..fb2e8bd8a9 100644 --- a/crates/crypto/src/multisig/mod.rs +++ b/crates/crypto/src/multisig/mod.rs @@ -1,4 +1,5 @@ pub mod config; +pub mod constants; pub mod errors; pub mod schemes; pub mod signature; diff --git a/crates/crypto/src/multisig/schemes/schnorr.rs b/crates/crypto/src/multisig/schemes/schnorr.rs index 07b050554f..c11045839f 100644 --- a/crates/crypto/src/multisig/schemes/schnorr.rs +++ b/crates/crypto/src/multisig/schemes/schnorr.rs @@ -1,11 +1,11 @@ use bitcoin::{key::Parity, secp256k1::PublicKey, XOnlyPublicKey}; use musig2::KeyAggContext; -use strata_primitives::{ - buf::{Buf32, Buf64}, - crypto::verify_schnorr_sig, -}; +use strata_identifiers::{Buf32, Buf64}; -use crate::multisig::{errors::MultisigError, traits::CryptoScheme}; +use crate::{ + multisig::{errors::MultisigError, traits::CryptoScheme}, + schnorr::verify_schnorr_sig, +}; /// Schnorr signature scheme using MuSig2 key aggregation. #[derive(Clone, Debug, PartialEq, Eq)] @@ -41,14 +41,17 @@ impl CryptoScheme for SchnorrScheme { /// Aggregates a collection of Schnorr public keys using MuSig2 key aggregation. /// /// # Arguments +/// /// * `keys` - An iterator over 32-byte public keys to aggregate /// /// # Returns +/// /// Returns the aggregated public key on success, or an error if: /// - Any key is not a valid x-only public key /// - MuSig2 key aggregation context creation fails /// /// # Errors +/// /// * `MultisigError::InvalidPubKey` - If a key is not a valid x-only public key /// * `MultisigError::AggregationContextFailed` - If MuSig2 context creation fails pub fn aggregate_schnorr_keys<'k>( diff --git a/crates/crypto/src/proof_vk.rs b/crates/crypto/src/proof_vk.rs new file mode 100644 index 0000000000..93bf9ed91a --- /dev/null +++ b/crates/crypto/src/proof_vk.rs @@ -0,0 +1,154 @@ +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use strata_identifiers::{ExecBlockCommitment, L2BlockCommitment}; +use zkaleido_risc0_groth16_verifier::Risc0Groth16Verifier; +use zkaleido_sp1_groth16_verifier::SP1Groth16Verifier; + +pub type Epoch = u64; + +/// Represents the verifying key used for verifying ZK proofs in a rollup context. +/// +/// This enum encapsulates verifying keys for different ZKVMs: +/// - `SP1VerifyingKey`: Used for verifying proofs generated using SP1. +/// - `Risc0VerifyingKey`: Used for verifying proofs generated using Risc0. +/// - `Native`: For functional testing purposes without ZKVM overhead. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RollupVerifyingKey { + /// Verifying Key for proofs generated by SP1. + #[serde(rename = "sp1")] + SP1VerifyingKey(SP1Groth16Verifier), + + /// Verifying Key for proofs generated by Risc0. + #[serde(rename = "risc0")] + Risc0VerifyingKey(Risc0Groth16Verifier), + + /// Placeholder variant for functional testing. + /// + /// This variant allows skipping guest code compilation (e.g., ELFs for SP1 or Risc0) and is + /// used to test the prover-client and proof logic without the overhead of ZKVM + /// compilation. It is strictly for internal testing and must not be used in production + /// deployments. + #[serde(rename = "native")] + NativeVerifyingKey, +} + +impl BorshSerialize for RollupVerifyingKey { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let encoded = bincode::serialize(self).map_err(std::io::Error::other)?; + BorshSerialize::serialize(&encoded, writer) + } +} + +impl BorshDeserialize for RollupVerifyingKey { + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let encoded = Vec::::deserialize_reader(reader)?; + bincode::deserialize(&encoded).map_err(std::io::Error::other) + } +} + +/// Represents a context for different types of proofs. +/// +/// This enum categorizes proofs by their associated context, including the type of proof and its +/// range or scope. Each variant includes relevant metadata required to distinguish and track the +/// proof. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Hash, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub enum ProofContext { + /// Identifier for the EVM Execution Environment (EE) blocks used in generating the State + /// Transition Function (STF) proof. + EvmEeStf(ExecBlockCommitment, ExecBlockCommitment), + + /// Identifier for the Consensus Layer (CL) blocks used in generating the State Transition + /// Function (STF) proof. + ClStf(L2BlockCommitment, L2BlockCommitment), + + /// Identifier for a specific checkpoint being proven. + Checkpoint(u64), +} + +/// Represents the ZkVm host used for proof generation. +/// +/// This enum identifies the ZkVm environment utilized to create a proof. +/// Available hosts: +/// - `SP1`: SP1 ZKVM. +/// - `Risc0`: Risc0 ZKVM. +/// - `Native`: Native ZKVM. +#[non_exhaustive] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum ProofZkVm { + SP1, + Risc0, + Native, +} + +/// Represents a unique key for identifying any type of proof. +/// +/// A `ProofKey` combines a `ProofContext` (which specifies the type of proof and its scope) +/// with a `ProofZkVm` (which specifies the ZKVM host used for proof generation). +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + Hash, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub struct ProofKey { + /// The unique identifier for the proof type and its context. + context: ProofContext, + + /// The ZKVM host used for proof generation. + host: ProofZkVm, +} + +impl ProofKey { + pub fn new(context: ProofContext, host: ProofZkVm) -> Self { + Self { context, host } + } + + pub fn context(&self) -> &ProofContext { + &self.context + } + + pub fn host(&self) -> &ProofZkVm { + &self.host + } +} + +impl Display for ProofKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ProofKey(context = {:?}, host = {:?})", + self.context, self.host + ) + } +} diff --git a/crates/primitives/src/crypto.rs b/crates/crypto/src/schnorr.rs similarity index 84% rename from crates/primitives/src/crypto.rs rename to crates/crypto/src/schnorr.rs index 42109a02a1..0196a11177 100644 --- a/crates/primitives/src/crypto.rs +++ b/crates/crypto/src/schnorr.rs @@ -1,18 +1,19 @@ -//! Logic to check block credentials. use std::ops::Deref; +#[cfg(target_os = "zkvm")] +use k256::Signature; +#[cfg(not(target_os = "zkvm"))] use secp256k1::{ schnorr::Signature, Keypair, Message, Parity, PublicKey, SecretKey, XOnlyPublicKey, SECP256K1, }; +use strata_identifiers::{Buf32, Buf64}; -use crate::buf::{Buf32, Buf64}; - -#[cfg(feature = "rand")] +#[cfg(all(feature = "rand", not(target_os = "zkvm")))] pub fn sign_schnorr_sig(msg: &Buf32, sk: &Buf32) -> Buf64 { - let sk = SecretKey::from_slice(sk.as_ref()).expect("Invalid private key"); + let sk = SecretKey::from_slice(sk.as_ref()).expect("crypto: invalid private key"); let kp = Keypair::from_secret_key(SECP256K1, &sk); - let msg = Message::from_digest_slice(msg.as_ref()).expect("Invalid message hash"); - let sig = SECP256K1.sign_schnorr(&msg, &kp); + let msg = Message::from_digest_slice(msg.as_ref()).expect("crypto: invalid message hash"); + let sig = SECP256K1.sign_schnorr_no_aux_rand(&msg, &kp); Buf64::from(sig.serialize()) } @@ -53,9 +54,11 @@ pub fn verify_schnorr_sig(sig: &Buf64, msg: &Buf32, pk: &Buf32) -> bool { } /// A secret key that is guaranteed to have a even x-only public key +#[cfg(feature = "std")] #[derive(Debug, Clone, Copy)] pub struct EvenSecretKey(SecretKey); +#[cfg(feature = "std")] impl Deref for EvenSecretKey { type Target = SecretKey; @@ -64,12 +67,14 @@ impl Deref for EvenSecretKey { } } +#[cfg(feature = "std")] impl AsRef for EvenSecretKey { fn as_ref(&self) -> &SecretKey { &self.0 } } +#[cfg(feature = "std")] impl From for EvenSecretKey { fn from(value: SecretKey) -> Self { match value.x_only_public_key(SECP256K1).1 == Parity::Odd { @@ -79,6 +84,7 @@ impl From for EvenSecretKey { } } +#[cfg(feature = "std")] impl From for SecretKey { fn from(value: EvenSecretKey) -> Self { value.0 @@ -86,9 +92,11 @@ impl From for SecretKey { } /// A public key with guaranteed even parity +#[cfg(feature = "std")] #[derive(Debug)] pub struct EvenPublicKey(PublicKey); +#[cfg(feature = "std")] impl Deref for EvenPublicKey { type Target = PublicKey; @@ -97,12 +105,14 @@ impl Deref for EvenPublicKey { } } +#[cfg(feature = "std")] impl AsRef for EvenPublicKey { fn as_ref(&self) -> &PublicKey { &self.0 } } +#[cfg(feature = "std")] impl From for EvenPublicKey { fn from(value: PublicKey) -> Self { match value.x_only_public_key().1 == Parity::Odd { @@ -112,6 +122,7 @@ impl From for EvenPublicKey { } } +#[cfg(feature = "std")] impl From for PublicKey { fn from(value: EvenPublicKey) -> Self { value.0 @@ -119,6 +130,7 @@ impl From for PublicKey { } /// Ensures a keypair is even by checking the public key's parity and negating if odd. +#[cfg(feature = "std")] pub fn even_kp((sk, pk): (SecretKey, PublicKey)) -> (EvenSecretKey, EvenPublicKey) { match (sk, pk) { (sk, pk) if pk.x_only_public_key().1 == Parity::Odd => ( @@ -133,9 +145,9 @@ pub fn even_kp((sk, pk): (SecretKey, PublicKey)) -> (EvenSecretKey, EvenPublicKe mod tests { use rand::{rngs::OsRng, Rng}; use secp256k1::{SecretKey, SECP256K1}; + use strata_identifiers::Buf32; use super::{sign_schnorr_sig, verify_schnorr_sig}; - use crate::buf::Buf32; #[test] fn test_schnorr_signature_pass() { diff --git a/crates/db-store-rocksdb/src/broadcaster/db.rs b/crates/db-store-rocksdb/src/broadcaster/db.rs index d1930269f3..9b46e0a840 100644 --- a/crates/db-store-rocksdb/src/broadcaster/db.rs +++ b/crates/db-store-rocksdb/src/broadcaster/db.rs @@ -4,7 +4,7 @@ use rockbound::{ utils::get_last, OptimisticTransactionDB as DB, SchemaDBOperationsExt, TransactionRetry, }; use strata_db::{errors::DbError, traits::L1BroadcastDatabase, types::L1TxEntry, DbResult}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::schemas::{BcastL1TxIdSchema, BcastL1TxSchema}; use crate::{sequence::get_next_id, DbOpsConfig}; diff --git a/crates/db-store-rocksdb/src/broadcaster/schemas.rs b/crates/db-store-rocksdb/src/broadcaster/schemas.rs index 7eabfd2606..5e3cdaf09a 100644 --- a/crates/db-store-rocksdb/src/broadcaster/schemas.rs +++ b/crates/db-store-rocksdb/src/broadcaster/schemas.rs @@ -1,5 +1,5 @@ use strata_db::types::L1TxEntry; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::{ define_table_with_default_codec, define_table_with_seek_key_codec, define_table_without_codec, diff --git a/crates/db-store-rocksdb/src/chain_state/db.rs b/crates/db-store-rocksdb/src/chain_state/db.rs index 7a169213d9..ec48cea2e5 100644 --- a/crates/db-store-rocksdb/src/chain_state/db.rs +++ b/crates/db-store-rocksdb/src/chain_state/db.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use rockbound::{OptimisticTransactionDB, SchemaDBOperationsExt}; use strata_db::{chainstate::*, DbError, DbResult}; use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::{schemas::*, types::*}; use crate::DbOpsConfig; diff --git a/crates/db-store-rocksdb/src/checkpoint/db.rs b/crates/db-store-rocksdb/src/checkpoint/db.rs index 74d2410723..8150640aba 100644 --- a/crates/db-store-rocksdb/src/checkpoint/db.rs +++ b/crates/db-store-rocksdb/src/checkpoint/db.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use rockbound::{OptimisticTransactionDB, SchemaDBOperationsExt}; use strata_checkpoint_types::EpochSummary; use strata_db::{traits::CheckpointDatabase, types::CheckpointEntry, DbError, DbResult}; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use super::schemas::*; use crate::DbOpsConfig; diff --git a/crates/db-store-rocksdb/src/l1/db.rs b/crates/db-store-rocksdb/src/l1/db.rs index e435e29f05..943fcbe919 100644 --- a/crates/db-store-rocksdb/src/l1/db.rs +++ b/crates/db-store-rocksdb/src/l1/db.rs @@ -6,9 +6,10 @@ use rockbound::{ utils::{get_first, get_last}, OptimisticTransactionDB, SchemaBatch, SchemaDBOperationsExt, }; -use strata_asm_types::{L1BlockManifest, L1Tx, L1TxRef}; +use strata_asm_types::L1TxRef; +use strata_btc_types::legacy::{L1BlockManifest, L1Tx}; use strata_db::{errors::DbError, traits::*, DbResult}; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use tracing::*; use super::schemas::{L1BlockSchema, L1BlocksByHeightSchema, L1CanonicalBlockSchema, TxnSchema}; diff --git a/crates/db-store-rocksdb/src/l1/schemas.rs b/crates/db-store-rocksdb/src/l1/schemas.rs index af5a7aed59..27f425df39 100644 --- a/crates/db-store-rocksdb/src/l1/schemas.rs +++ b/crates/db-store-rocksdb/src/l1/schemas.rs @@ -1,5 +1,5 @@ -use strata_asm_types::{L1BlockManifest, L1Tx}; -use strata_primitives::l1::L1BlockId; +use strata_btc_types::legacy::{L1BlockManifest, L1Tx}; +use strata_identifiers::L1BlockId; use crate::{ define_table_with_default_codec, define_table_with_seek_key_codec, define_table_without_codec, diff --git a/crates/db-store-rocksdb/src/prover/db.rs b/crates/db-store-rocksdb/src/prover/db.rs index 44e81e3558..dcc4cb5009 100644 --- a/crates/db-store-rocksdb/src/prover/db.rs +++ b/crates/db-store-rocksdb/src/prover/db.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use rockbound::{OptimisticTransactionDB, SchemaDBOperationsExt, TransactionRetry}; use strata_db::{errors::DbError, traits::ProofDatabase, DbResult}; -use strata_primitives::proof::{ProofContext, ProofKey}; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use zkaleido::ProofReceiptWithMetadata; use super::schemas::{ProofDepsSchema, ProofSchema}; diff --git a/crates/db-store-rocksdb/src/prover/schemas.rs b/crates/db-store-rocksdb/src/prover/schemas.rs index 2a9ffdbbbe..4a0dfa2dc8 100644 --- a/crates/db-store-rocksdb/src/prover/schemas.rs +++ b/crates/db-store-rocksdb/src/prover/schemas.rs @@ -1,4 +1,4 @@ -use strata_primitives::proof::{ProofContext, ProofKey}; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use zkaleido::ProofReceiptWithMetadata; use crate::{define_table_with_default_codec, define_table_without_codec, impl_borsh_value_codec}; diff --git a/crates/db-store-rocksdb/src/writer/db.rs b/crates/db-store-rocksdb/src/writer/db.rs index e29a36831c..9126a92762 100644 --- a/crates/db-store-rocksdb/src/writer/db.rs +++ b/crates/db-store-rocksdb/src/writer/db.rs @@ -7,7 +7,7 @@ use strata_db::{ types::{BundledPayloadEntry, IntentEntry}, DbResult, }; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::schemas::{IntentIdxSchema, IntentSchema, PayloadSchema}; use crate::{sequence::get_next_id, DbOpsConfig}; diff --git a/crates/db-store-rocksdb/src/writer/schemas.rs b/crates/db-store-rocksdb/src/writer/schemas.rs index 02a5d05d5f..be33b9cca3 100644 --- a/crates/db-store-rocksdb/src/writer/schemas.rs +++ b/crates/db-store-rocksdb/src/writer/schemas.rs @@ -1,5 +1,5 @@ use strata_db::types::{BundledPayloadEntry, IntentEntry}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::{ define_table_with_default_codec, define_table_with_seek_key_codec, define_table_without_codec, diff --git a/crates/db-store-sled/src/broadcaster/db.rs b/crates/db-store-sled/src/broadcaster/db.rs index d66fb74f00..20357929a8 100644 --- a/crates/db-store-sled/src/broadcaster/db.rs +++ b/crates/db-store-sled/src/broadcaster/db.rs @@ -1,5 +1,5 @@ use strata_db::{DbResult, errors::DbError, traits::L1BroadcastDatabase, types::L1TxEntry}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::schemas::{BcastL1TxIdSchema, BcastL1TxSchema}; use crate::{ diff --git a/crates/db-store-sled/src/broadcaster/schemas.rs b/crates/db-store-sled/src/broadcaster/schemas.rs index 354998f6d0..e5a344e243 100644 --- a/crates/db-store-sled/src/broadcaster/schemas.rs +++ b/crates/db-store-sled/src/broadcaster/schemas.rs @@ -1,5 +1,5 @@ use strata_db::types::L1TxEntry; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::{ define_table_with_default_codec, define_table_with_integer_key, define_table_without_codec, diff --git a/crates/db-store-sled/src/chain_state/db.rs b/crates/db-store-sled/src/chain_state/db.rs index bb06e0c689..94a74e56ba 100644 --- a/crates/db-store-sled/src/chain_state/db.rs +++ b/crates/db-store-sled/src/chain_state/db.rs @@ -3,7 +3,7 @@ use strata_db::{ chainstate::{ChainstateDatabase, StateInstanceId, WriteBatchId}, }; use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::{ chain_state::schemas::{StateInstanceEntry, StateInstanceSchema, WriteBatchSchema}, diff --git a/crates/db-store-sled/src/checkpoint/db.rs b/crates/db-store-sled/src/checkpoint/db.rs index 653aeb4bd4..d32d32d857 100644 --- a/crates/db-store-sled/src/checkpoint/db.rs +++ b/crates/db-store-sled/src/checkpoint/db.rs @@ -1,6 +1,6 @@ use strata_checkpoint_types::EpochSummary; use strata_db::{DbError, DbResult, traits::CheckpointDatabase, types::CheckpointEntry}; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use super::schemas::*; use crate::{define_sled_database, utils::first}; diff --git a/crates/db-store-sled/src/l1/db.rs b/crates/db-store-sled/src/l1/db.rs index 414066f91a..8a000590a6 100644 --- a/crates/db-store-sled/src/l1/db.rs +++ b/crates/db-store-sled/src/l1/db.rs @@ -1,6 +1,7 @@ -use strata_asm_types::{L1BlockManifest, L1Tx, L1TxRef}; +use strata_asm_types::L1TxRef; +use strata_btc_types::legacy::{L1BlockManifest, L1Tx}; use strata_db::{DbResult, errors::DbError, traits::*}; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use typed_sled::batch::SledBatch; use super::schemas::{L1BlockSchema, L1BlocksByHeightSchema, L1CanonicalBlockSchema, TxnSchema}; diff --git a/crates/db-store-sled/src/l1/schemas.rs b/crates/db-store-sled/src/l1/schemas.rs index 4c3765c62b..a29390627e 100644 --- a/crates/db-store-sled/src/l1/schemas.rs +++ b/crates/db-store-sled/src/l1/schemas.rs @@ -1,5 +1,5 @@ -use strata_asm_types::{L1BlockManifest, L1Tx}; -use strata_primitives::l1::L1BlockId; +use strata_btc_types::legacy::{L1BlockManifest, L1Tx}; +use strata_identifiers::L1BlockId; use crate::{ define_table_with_default_codec, define_table_with_integer_key, define_table_without_codec, diff --git a/crates/db-store-sled/src/prover/db.rs b/crates/db-store-sled/src/prover/db.rs index 32b34fd41d..e54c269db7 100644 --- a/crates/db-store-sled/src/prover/db.rs +++ b/crates/db-store-sled/src/prover/db.rs @@ -1,5 +1,5 @@ use strata_db::{DbResult, errors::DbError, traits::ProofDatabase}; -use strata_primitives::proof::{ProofContext, ProofKey}; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use zkaleido::ProofReceiptWithMetadata; use super::schemas::{ProofDepsSchema, ProofSchema}; diff --git a/crates/db-store-sled/src/prover/schemas.rs b/crates/db-store-sled/src/prover/schemas.rs index 2a9ffdbbbe..4a0dfa2dc8 100644 --- a/crates/db-store-sled/src/prover/schemas.rs +++ b/crates/db-store-sled/src/prover/schemas.rs @@ -1,4 +1,4 @@ -use strata_primitives::proof::{ProofContext, ProofKey}; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use zkaleido::ProofReceiptWithMetadata; use crate::{define_table_with_default_codec, define_table_without_codec, impl_borsh_value_codec}; diff --git a/crates/db-store-sled/src/writer/db.rs b/crates/db-store-sled/src/writer/db.rs index c274a2aaa2..27e0f568f2 100644 --- a/crates/db-store-sled/src/writer/db.rs +++ b/crates/db-store-sled/src/writer/db.rs @@ -4,7 +4,7 @@ use strata_db::{ traits::L1WriterDatabase, types::{BundledPayloadEntry, IntentEntry}, }; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use super::schemas::{IntentIdxSchema, IntentSchema, PayloadSchema}; use crate::{ diff --git a/crates/db-store-sled/src/writer/schemas.rs b/crates/db-store-sled/src/writer/schemas.rs index 6d146db6fe..c85ffac6e6 100644 --- a/crates/db-store-sled/src/writer/schemas.rs +++ b/crates/db-store-sled/src/writer/schemas.rs @@ -1,5 +1,5 @@ use strata_db::types::{BundledPayloadEntry, IntentEntry}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::{ define_table_with_default_codec, define_table_with_integer_key, define_table_without_codec, diff --git a/crates/db-tests/src/chain_state_tests.rs b/crates/db-tests/src/chain_state_tests.rs index 53fa87b938..4363af8cf2 100644 --- a/crates/db-tests/src/chain_state_tests.rs +++ b/crates/db-tests/src/chain_state_tests.rs @@ -1,6 +1,6 @@ use strata_db::chainstate::ChainstateDatabase; use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use strata_test_utils::ArbitraryGenerator; pub fn test_create_and_get_state_instance(db: &impl ChainstateDatabase) { diff --git a/crates/db-tests/src/l1_broadcast_tests.rs b/crates/db-tests/src/l1_broadcast_tests.rs index 296759eb6f..f1ce60f015 100644 --- a/crates/db-tests/src/l1_broadcast_tests.rs +++ b/crates/db-tests/src/l1_broadcast_tests.rs @@ -3,7 +3,7 @@ use strata_db::{ traits::L1BroadcastDatabase, types::{L1TxEntry, L1TxStatus}, }; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use strata_test_utils_btc::get_test_bitcoin_txs; pub fn test_get_last_tx_entry(db: &impl L1BroadcastDatabase) { diff --git a/crates/db-tests/src/l1_writer_tests.rs b/crates/db-tests/src/l1_writer_tests.rs index c48611de46..80b90edbde 100644 --- a/crates/db-tests/src/l1_writer_tests.rs +++ b/crates/db-tests/src/l1_writer_tests.rs @@ -2,7 +2,7 @@ use strata_db::{ traits::L1WriterDatabase, types::{BundledPayloadEntry, IntentEntry}, }; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use strata_test_utils::ArbitraryGenerator; // ===== Payload Entry Tests ===== diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 5581b57900..78ff451a1f 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -8,7 +8,10 @@ workspace = true [dependencies] strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true +strata-crypto.workspace = true +strata-identifiers.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true strata-primitives.workspace = true diff --git a/crates/db/src/chainstate.rs b/crates/db/src/chainstate.rs index ec947275d3..0f74d9ee76 100644 --- a/crates/db/src/chainstate.rs +++ b/crates/db/src/chainstate.rs @@ -14,7 +14,7 @@ //! potentially-many ledger entries. use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::DbResult; diff --git a/crates/db/src/traits.rs b/crates/db/src/traits.rs index 29c4643e00..095eba873d 100644 --- a/crates/db/src/traits.rs +++ b/crates/db/src/traits.rs @@ -5,13 +5,12 @@ use std::sync::Arc; use borsh::{BorshDeserialize, BorshSerialize}; use serde::Serialize; -use strata_asm_types::{L1BlockManifest, L1Tx, L1TxRef}; +use strata_asm_types::L1TxRef; +use strata_btc_types::{L1BlockManifest, L1Tx}; use strata_checkpoint_types::EpochSummary; +use strata_crypto::proof_vk::{ProofContext, ProofKey}; use strata_ol_chain_types::L2BlockBundle; -use strata_primitives::{ - prelude::*, - proof::{ProofContext, ProofKey}, -}; +use strata_primitives::prelude::*; use strata_state::{asm_state::AsmState, client_state::ClientState, operation::*}; use zkaleido::ProofReceiptWithMetadata; diff --git a/crates/eectl/Cargo.toml b/crates/eectl/Cargo.toml index 0696f93ea8..7631aeab33 100644 --- a/crates/eectl/Cargo.toml +++ b/crates/eectl/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] strata-common.workspace = true strata-db.workspace = true +strata-identifiers.workspace = true strata-ol-chain-types.workspace = true strata-primitives.workspace = true strata-state.workspace = true diff --git a/crates/eectl/src/errors.rs b/crates/eectl/src/errors.rs index 58d3f6809a..8633d5e916 100644 --- a/crates/eectl/src/errors.rs +++ b/crates/eectl/src/errors.rs @@ -1,5 +1,5 @@ use strata_db::DbError; -use strata_primitives::l2::L2BlockCommitment; +use strata_identifiers::L2BlockCommitment; use thiserror::Error; pub type EngineResult = Result; diff --git a/crates/eectl/src/messages.rs b/crates/eectl/src/messages.rs index 1c663a3395..7fb05b6aef 100644 --- a/crates/eectl/src/messages.rs +++ b/crates/eectl/src/messages.rs @@ -1,6 +1,6 @@ use strata_ol_chain_types::{L2BlockBundle, L2BlockId}; use strata_primitives::{bitcoin_bosd::Descriptor, prelude::*}; -use strata_state::exec_update::{ExecUpdate, Op}; +use strata_ol_chain_types::{ExecUpdate, Op}; /// Succinct commitment to relevant EL block data. // This ended up being the same as the EL payload types in the state crate, diff --git a/crates/eectl/src/stub.rs b/crates/eectl/src/stub.rs index 0307bb8e79..52039724c0 100644 --- a/crates/eectl/src/stub.rs +++ b/crates/eectl/src/stub.rs @@ -7,11 +7,9 @@ use std::{collections::*, sync::Mutex, time}; -use strata_primitives::buf::Buf32; -use strata_state::{ - exec_update::{ExecUpdate, UpdateInput, UpdateOutput}, - prelude::*, -}; +use strata_identifiers::Buf32; +use strata_ol_chain_types::{ExecUpdate, UpdateInput, UpdateOutput}; +use strata_state::prelude::*; use crate::{engine::*, errors::*, messages::*}; diff --git a/crates/evmexec/src/engine.rs b/crates/evmexec/src/engine.rs index 14e1d70e92..46aa23b193 100644 --- a/crates/evmexec/src/engine.rs +++ b/crates/evmexec/src/engine.rs @@ -444,7 +444,7 @@ mod tests { use revm_primitives::{alloy_primitives::Bloom, Bytes, FixedBytes, U256}; use strata_eectl::{errors::EngineResult, messages::PayloadEnv}; use strata_ol_chain_types::{L2Block, L2BlockAccessory}; - use strata_primitives::buf::Buf32; + use strata_identifiers::Buf32; use super::*; use crate::http_client::MockEngineRpc; diff --git a/crates/evmexec/src/fork_choice_state.rs b/crates/evmexec/src/fork_choice_state.rs index 3ad7b85b08..1406f14c57 100644 --- a/crates/evmexec/src/fork_choice_state.rs +++ b/crates/evmexec/src/fork_choice_state.rs @@ -6,7 +6,7 @@ use revm_primitives::B256; use strata_db::errors::DbError; use strata_ol_chain_types::{L2BlockBundle, L2BlockId}; use strata_ol_chainstate_types::ChainstateEntry; -use strata_primitives::params::RollupParams; +use strata_params::RollupParams; use strata_storage::*; use tracing::*; diff --git a/crates/identifiers/Cargo.toml b/crates/identifiers/Cargo.toml new file mode 100644 index 0000000000..0a30679ea9 --- /dev/null +++ b/crates/identifiers/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "strata-identifiers" +version = "0.1.0" +edition = "2024" + +[dependencies] +arbitrary.workspace = true +bitcoin = { workspace = true, optional = true } +bitcoin-primitives.workspace = true +borsh.workspace = true +const-hex.workspace = true +hex.workspace = true +serde.workspace = true +sha2.workspace = true +thiserror.workspace = true +zeroize.workspace = true + +[dev-dependencies] +bincode.workspace = true +bitcoin_hashes.workspace = true +rand.workspace = true +serde_json.workspace = true + +[lints] +workspace = true + +[features] +default = ["fullbtc"] +fullbtc = ["dep:bitcoin"] diff --git a/crates/identifiers/src/bitcoin_amount.rs b/crates/identifiers/src/bitcoin_amount.rs new file mode 100644 index 0000000000..c43382af17 --- /dev/null +++ b/crates/identifiers/src/bitcoin_amount.rs @@ -0,0 +1,123 @@ +use std::fmt; + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +/// A wrapper for bitcoin amount in sats similar to the implementation in [`bitcoin::Amount`]. +/// +/// NOTE: This wrapper has been created so that we can implement `Borsh*` traits on it. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub struct BitcoinAmount(u64); + +impl fmt::Display for BitcoinAmount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(feature = "fullbtc")] +impl From for BitcoinAmount { + fn from(value: bitcoin::Amount) -> Self { + Self::from_sat(value.to_sat()) + } +} + +#[cfg(feature = "fullbtc")] +impl From for bitcoin::Amount { + fn from(value: BitcoinAmount) -> Self { + Self::from_sat(value.to_sat()) + } +} + +impl BitcoinAmount { + /// The zero amount. + pub const ZERO: BitcoinAmount = Self(0); + + /// The maximum value allowed as an amount. Useful for sanity checking. + pub const MAX_MONEY: BitcoinAmount = Self::from_int_btc(21_000_000); + + /// The minimum value of an amount. + pub const MIN: BitcoinAmount = Self::ZERO; + + /// The maximum value of an amount. + pub const MAX: BitcoinAmount = Self(u64::MAX); + + /// The number of bytes that an amount contributes to the size of a transaction. + /// Serialized length of a u64. + pub const SIZE: usize = 8; + + /// The number of sats in 1 bitcoin. + pub const SATS_FACTOR: u64 = 100_000_000; + + /// Get the number of sats in this [`BitcoinAmount`]. + pub fn to_sat(&self) -> u64 { + self.0 + } + + /// Create a [`BitcoinAmount`] with sats precision and the given number of sats. + pub const fn from_sat(value: u64) -> Self { + Self(value) + } + + /// Convert from a value strataing integer values of bitcoins to a [`BitcoinAmount`] + /// in const context. + /// + /// ## Panics + /// + /// The function panics if the argument multiplied by the number of sats + /// per bitcoin overflows a u64 type, or is greater than [`BitcoinAmount::MAX_MONEY`]. + pub const fn from_int_btc(btc: u64) -> Self { + match btc.checked_mul(Self::SATS_FACTOR) { + Some(amount) => Self::from_sat(amount), + None => { + panic!("number of sats greater than u64::MAX"); + } + } + } + + /// Checked addition. Returns [`None`] if overflow occurred. + pub fn checked_add(self, rhs: Self) -> Option { + self.0.checked_add(rhs.0).map(Self::from_sat) + } + + /// Checked subtraction. Returns [`None`] if overflow occurred. + pub fn checked_sub(self, rhs: Self) -> Option { + self.0.checked_sub(rhs.0).map(Self::from_sat) + } + + /// Checked multiplication. Returns [`None`] if overflow occurred. + pub fn checked_mul(self, rhs: u64) -> Option { + self.0.checked_mul(rhs).map(Self::from_sat) + } + + /// Checked division. Returns [`None`] if `rhs == 0`. + pub fn checked_div(self, rhs: u64) -> Option { + self.0.checked_div(rhs).map(Self::from_sat) + } + + /// Saturating subtraction. Computes `self - rhs`, returning [`Self::ZERO`] if overflow + /// occurred. + pub fn saturating_sub(self, rhs: Self) -> Self { + Self::from_sat(self.to_sat().saturating_sub(rhs.to_sat())) + } + + /// Saturating addition. Computes `self + rhs`, saturating at the numeric bounds. + pub fn saturating_add(self, rhs: Self) -> Self { + Self::from_sat(self.to_sat().saturating_add(rhs.to_sat())) + } +} diff --git a/crates/identifiers/src/buf.rs b/crates/identifiers/src/buf.rs new file mode 100644 index 0000000000..5c1031036b --- /dev/null +++ b/crates/identifiers/src/buf.rs @@ -0,0 +1,323 @@ +use std::str::FromStr; + +use bitcoin_primitives::{BlockHash, Txid, Wtxid}; +use const_hex as hex; +use zeroize::Zeroize; + +#[rustfmt::skip] +#[cfg(feature = "fullbtc")] +use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey, schnorr}; + +use crate::{errors::ParseError, macros::internal}; + +/// A 20-byte buffer. +/// +/// # Warning +/// +/// This type is not zeroized on drop. +/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. +/// This is useful for secret data that needs to be zeroized after use. +/// +/// # Example +/// +/// ``` +/// # use strata_identifiers::Buf20; +/// use zeroize::Zeroize; +/// +/// let mut buf = Buf20::from([1; 20]); +/// buf.zeroize(); +/// +/// assert_eq!(buf, Buf20::from([0; 20])); +/// ``` +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Buf20(pub [u8; 20]); +internal::impl_buf_common!(Buf20, 20); +internal::impl_buf_serde!(Buf20, 20); + +// NOTE: we cannot do `ZeroizeOnDrop` since `Buf20` is `Copy`. +impl Zeroize for Buf20 { + #[inline] + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +/// A 32-byte buffer. +/// +/// This is useful for hashes, transaction IDs, secret and public keys. +/// +/// # Warning +/// +/// This type is not zeroized on drop. +/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. +/// This is useful for secret data that needs to be zeroized after use. +/// +/// # Example +/// +/// ``` +/// # use strata_identifiers::Buf32; +/// use zeroize::Zeroize; +/// +/// let mut buf = Buf32::from([1; 32]); +/// buf.zeroize(); +/// +/// assert_eq!(buf, Buf32::from([0; 32])); +/// ``` +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Buf32(pub [u8; 32]); +internal::impl_buf_common!(Buf32, 32); +internal::impl_buf_serde!(Buf32, 32); + +impl FromStr for Buf32 { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + hex::decode_to_array(s).map(Self::new) + } +} + +impl From for Buf32 { + fn from(value: BlockHash) -> Self { + (*value.as_byte_array()).into() + } +} + +impl From for Buf32 { + fn from(value: Txid) -> Self { + let bytes: [u8; 32] = *value.as_byte_array(); + bytes.into() + } +} + +impl From<&Txid> for Buf32 { + fn from(value: &Txid) -> Self { + Self::from(*value) + } +} + +impl From for Txid { + fn from(value: Buf32) -> Self { + let mut bytes: [u8; 32] = [0; 32]; + bytes.copy_from_slice(value.0.as_slice()); + Self::from_byte_array(bytes) + } +} + +impl From for Buf32 { + fn from(value: Wtxid) -> Self { + let bytes: [u8; 32] = *value.as_byte_array(); + bytes.into() + } +} + +impl From for Wtxid { + fn from(value: Buf32) -> Self { + let mut bytes: [u8; 32] = [0; 32]; + bytes.copy_from_slice(value.0.as_slice()); + Self::from_byte_array(bytes) + } +} + +#[cfg(feature = "fullbtc")] +impl From for Buf32 { + fn from(value: bitcoin::BlockHash) -> Self { + use bitcoin::hashes::Hash; + value.as_raw_hash().as_byte_array().into() + } +} + +#[cfg(feature = "fullbtc")] +impl From for Buf32 { + fn from(value: bitcoin::Txid) -> Self { + use bitcoin::hashes::Hash; + Self::from(value.to_raw_hash().to_byte_array()) + } +} + +#[cfg(feature = "fullbtc")] +impl From for bitcoin::Txid { + fn from(value: Buf32) -> Self { + let mut bytes: [u8; 32] = [0; 32]; + bytes.copy_from_slice(value.0.as_slice()); + Self::from_raw_hash(bitcoin::hashes::Hash::from_byte_array(bytes)) + } +} + +#[cfg(feature = "fullbtc")] +impl From for Buf32 { + fn from(value: bitcoin::Wtxid) -> Self { + use bitcoin::hashes::Hash; + Self::from(value.as_raw_hash().to_byte_array()) + } +} + +#[cfg(feature = "fullbtc")] +impl From for bitcoin::Wtxid { + fn from(value: Buf32) -> Self { + Self::from_raw_hash(bitcoin::hashes::Hash::from_byte_array(value.into())) + } +} + +#[cfg(feature = "fullbtc")] +impl From for Buf32 { + fn from(value: SecretKey) -> Self { + let bytes: [u8; 32] = value.secret_bytes(); + bytes.into() + } +} + +#[cfg(feature = "fullbtc")] +impl From for SecretKey { + fn from(value: Buf32) -> Self { + SecretKey::from_slice(value.0.as_slice()).expect("could not convert Buf32 into SecretKey") + } +} + +#[cfg(feature = "fullbtc")] +impl TryFrom for XOnlyPublicKey { + type Error = ParseError; + + fn try_from(value: Buf32) -> Result { + XOnlyPublicKey::from_slice(&value.0).map_err(|_| ParseError::InvalidPoint(value)) + } +} + +#[cfg(feature = "fullbtc")] +impl From for Buf32 { + fn from(value: XOnlyPublicKey) -> Self { + Self::from(value.serialize()) + } +} + +// NOTE: we cannot do `ZeroizeOnDrop` since `Buf32` is `Copy`. +impl Zeroize for Buf32 { + #[inline] + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +/// A 64-byte buffer. +/// +/// This is useful for schnorr signatures. +/// +/// # Warning +/// +/// This type is not zeroized on drop. +/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. +/// This is useful for secret data that needs to be zeroized after use. +/// +/// # Example +/// +/// ``` +/// # use strata_identifiers::Buf64; +/// use zeroize::Zeroize; +/// +/// let mut buf = Buf64::from([1; 64]); +/// buf.zeroize(); +/// +/// assert_eq!(buf, Buf64::from([0; 64])); +/// ``` +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Buf64(pub [u8; 64]); +internal::impl_buf_common!(Buf64, 64); +internal::impl_buf_serde!(Buf64, 64); + +#[cfg(feature = "fullbtc")] +impl From for Buf64 { + fn from(value: schnorr::Signature) -> Self { + value.serialize().into() + } +} + +// NOTE: we cannot do `ZeroizeOnDrop` since `Buf64` is `Copy`. +impl Zeroize for Buf64 { + #[inline] + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_buf32_deserialization() { + // without 0x + assert_eq!( + Buf32::from([0; 32]), + serde_json::from_str( + "\"0000000000000000000000000000000000000000000000000000000000000000\"", + ) + .unwrap() + ); + + // with 0x + assert_eq!( + Buf32::from([1; 32]), + serde_json::from_str( + "\"0x0101010101010101010101010101010101010101010101010101010101010101\"", + ) + .unwrap() + ); + + // correct byte order + assert_eq!( + Buf32::from([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 170u8 + ]), + serde_json::from_str( + "\"0x01010101010101010101010101010101010101010101010101010101010101aa\"", + ) + .unwrap() + ); + } + + #[test] + fn test_buf32_serialization() { + assert_eq!( + serde_json::to_string(&Buf32::from([0; 32])).unwrap(), + String::from("\"0000000000000000000000000000000000000000000000000000000000000000\"") + ); + + assert_eq!( + serde_json::to_string(&Buf32::from([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 170u8 + ])) + .unwrap(), + String::from("\"01010101010101010101010101010101010101010101010101010101010101aa\"") + ); + } + + #[test] + fn test_zeroize() { + let mut buf20 = Buf20::from([1; 20]); + let mut buf32 = Buf32::from([1; 32]); + let mut buf64 = Buf64::from([1; 64]); + buf20.zeroize(); + buf32.zeroize(); + buf64.zeroize(); + assert_eq!(buf20, Buf20::from([0; 20])); + assert_eq!(buf32, Buf32::from([0; 32])); + assert_eq!(buf64, Buf64::from([0; 64])); + } + + #[test] + fn test_buf32_parse() { + "0x37ad61cff1367467a98cf7c54c4ac99e989f1fbb1bc1e646235e90c065c565ba" + .parse::() + .unwrap(); + } + + #[test] + fn test_buf32_from_str() { + Buf32::from_str("a9f913c3d7fe56c462228ad22bb7631742a121a6a138d57c1fc4a351314948fa") + .unwrap(); + + Buf32::from_str("81060cb3997dcefc463e3db0a776efb5360e458064a666459b8807f60c0201c2") + .unwrap(); + } +} diff --git a/crates/identifiers/src/cred_rule.rs b/crates/identifiers/src/cred_rule.rs new file mode 100644 index 0000000000..49342844b8 --- /dev/null +++ b/crates/identifiers/src/cred_rule.rs @@ -0,0 +1,16 @@ +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::Buf32; + +/// Credential rule for block validation +#[derive( + Clone, Debug, PartialEq, Eq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub enum CredRule { + /// No credential checking + Unchecked, + /// Schnorr signature verification + SchnorrKey(Buf32), +} diff --git a/crates/identifiers/src/epoch.rs b/crates/identifiers/src/epoch.rs new file mode 100644 index 0000000000..91f894a159 --- /dev/null +++ b/crates/identifiers/src/epoch.rs @@ -0,0 +1,127 @@ +//! Types relating to epoch bookkeeping. +//! +//! An epoch of a range of sequential blocks defined by the terminal block of +//! the epoch going back to (but not including) the terminal block of a previous +//! epoch. This uniquely identifies the epoch's final state indirectly, +//! although it's possible for conflicting epochs with different terminal blocks +//! to exist in theory, depending on the consensus algorithm. +//! +//! Epochs are *usually* always the same number of slots, but we're not +//! guaranteeing this yet, so we always include both the epoch number and slot +//! number of the terminal block. +//! +//! We also have a sentinel "null" epoch used to refer to the "finalized epoch" +//! as of the genesis block. + +use std::fmt; + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use const_hex as hex; +use serde::{Deserialize, Serialize}; + +use crate::{ + buf::Buf32, + l2::{L2BlockCommitment, L2BlockId}, +}; + +/// Commits to a particular epoch by the last block and slot. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub struct EpochCommitment { + epoch: u64, + last_slot: u64, + last_blkid: L2BlockId, +} + +impl EpochCommitment { + pub fn new(epoch: u64, last_slot: u64, last_blkid: L2BlockId) -> Self { + Self { + epoch, + last_slot, + last_blkid, + } + } + + /// Creates a new instance given the terminal block of an epoch and the + /// epoch index. + pub fn from_terminal(epoch: u64, block: L2BlockCommitment) -> Self { + Self::new(epoch, block.slot(), *block.blkid()) + } + + /// Creates a "null" epoch with 0 slot, epoch 0, and zeroed blkid. + pub fn null() -> Self { + Self::new(0, 0, L2BlockId::from(Buf32::zero())) + } + + pub fn epoch(&self) -> u64 { + self.epoch + } + + pub fn last_slot(&self) -> u64 { + self.last_slot + } + + pub fn last_blkid(&self) -> &L2BlockId { + &self.last_blkid + } + + /// Returns a [`L2BlockCommitment`] for the final block of the epoch. + pub fn to_block_commitment(&self) -> L2BlockCommitment { + L2BlockCommitment::new(self.last_slot, self.last_blkid) + } + + /// Returns if the terminal blkid is zero. This signifies a special case + /// for the genesis epoch (0) before the it is completed. + pub fn is_null(&self) -> bool { + Buf32::from(self.last_blkid).is_zero() + } +} + +impl fmt::Display for EpochCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Show first 2 and last 2 bytes of block ID (4 hex chars each) + let blkid_bytes = self.last_blkid.as_ref(); + let first_2 = &blkid_bytes[..2]; + let last_2 = &blkid_bytes[30..]; + + let mut first_hex = [0u8; 4]; + let mut last_hex = [0u8; 4]; + hex::encode_to_slice(first_2, &mut first_hex) + .expect("Failed to encode first 2 bytes to hex"); + hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); + + write!( + f, + "{}[{}]@{}..{}", + self.last_slot, + self.epoch, + std::str::from_utf8(&first_hex) + .expect("Failed to convert first 2 hex bytes to UTF-8 string"), + std::str::from_utf8(&last_hex) + .expect("Failed to convert last 2 hex bytes to UTF-8 string") + ) + } +} + +impl fmt::Debug for EpochCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "EpochCommitment(epoch={}, last_slot={}, last_blkid={:?})", + self.epoch, self.last_slot, self.last_blkid + ) + } +} diff --git a/crates/identifiers/src/errors.rs b/crates/identifiers/src/errors.rs new file mode 100644 index 0000000000..e9ffd3436e --- /dev/null +++ b/crates/identifiers/src/errors.rs @@ -0,0 +1,27 @@ +//! Errors during parsing/handling/conversion of identifiers. + +#[cfg(feature = "fullbtc")] +use bitcoin::{AddressType, secp256k1}; +use thiserror::Error; + +use crate::buf::Buf32; + +/// Parsing errors that can occur with L1 primitives, +/// such as addresses, pubkeys, and scripts. +#[derive(Debug, Clone, Error)] +pub enum ParseError { + /// The provided pubkey is invalid. + #[cfg(feature = "fullbtc")] + #[error("supplied pubkey is invalid")] + InvalidPubkey(#[from] secp256k1::Error), + + /// The provided 32-byte buffer is not a valid point on the curve. + #[cfg(feature = "fullbtc")] + #[error("not a valid point on the curve: {0}")] + InvalidPoint(Buf32), + + /// Converting from an unsupported [`Address`](bitcoin::Address) type for a [`Buf32`]. + #[cfg(feature = "fullbtc")] + #[error("only taproot addresses are supported but found {0:?}")] + UnsupportedAddress(Option), +} diff --git a/crates/identifiers/src/exec.rs b/crates/identifiers/src/exec.rs new file mode 100644 index 0000000000..3d0a392359 --- /dev/null +++ b/crates/identifiers/src/exec.rs @@ -0,0 +1,54 @@ +//! Execution block commitments. + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::buf::Buf32; + +/// A commitment to an execution block for some arbitrary execution chain at a +/// particular slot. +/// +/// Also permits a concept of a "null" block. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub struct ExecBlockCommitment { + // TODO rename slot to number? + slot: u64, + blkid: Buf32, +} + +impl ExecBlockCommitment { + pub fn new(slot: u64, blkid: Buf32) -> Self { + Self { slot, blkid } + } + + pub fn null() -> Self { + Self::new(0, Buf32::zero()) + } + + pub fn slot(&self) -> u64 { + self.slot + } + + pub fn blkid(&self) -> &Buf32 { + &self.blkid + } + + pub fn is_null(&self) -> bool { + self.slot == 0 && self.blkid().is_zero() + } +} diff --git a/crates/identifiers/src/hash.rs b/crates/identifiers/src/hash.rs new file mode 100644 index 0000000000..af78fd2452 --- /dev/null +++ b/crates/identifiers/src/hash.rs @@ -0,0 +1,56 @@ +use borsh::BorshSerialize; +use sha2::{Sha256, digest::Digest}; + +use crate::buf::Buf32; + +/// Direct untagged hash. +pub fn raw(buf: &[u8]) -> Buf32 { + Buf32::from(<[u8; 32]>::from(Sha256::digest(buf))) +} + +pub fn compute_borsh_hash(v: &T) -> Buf32 { + let mut hasher = Sha256::new(); + v.serialize(&mut hasher) + .expect("hash: serialization failed"); + let result = hasher.finalize(); + let arr: [u8; 32] = result.into(); + Buf32::from(arr) +} + +/// Implements a double SHA256 (`Sha256d`) hashing function using [RustCrypto's SHA-2 crate](https://github.com/RustCrypto/hashes/tree/master/sha2). +/// +/// This implementation is designed to be equivalent to the one found in the +/// [`bitcoin_hashes` crate](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/hashes/src/sha256d.rs) +/// but is built upon the [RustCrypto's SHA-2 crate](https://github.com/RustCrypto/hashes/tree/master/sha2), +/// because it has patches available from both the +/// [Risc0](https://github.com/risc0/RustCrypto-hashes) +/// and [Sp1](https://github.com/sp1-patches/RustCrypto-hashes) +/// crates. +pub fn sha256d(buf: &[u8]) -> Buf32 { + let mut hasher = Sha256::new(); + hasher.update(buf); + let result = hasher.finalize_reset(); + hasher.update(result); + let arr: [u8; 32] = hasher.finalize().into(); + Buf32::from(arr) +} + +#[cfg(test)] +mod tests { + use bitcoin_hashes::sha256d; + use rand::{RngCore, rngs::OsRng}; + + use super::sha256d; + use crate::buf::Buf32; + + #[test] + fn test_sha256d_equivalence() { + let mut array = [0u8; 32]; + OsRng.fill_bytes(&mut array); + + let expected = Buf32::from(sha256d::Hash::hash(&array).to_byte_array()); + let output = sha256d(&array); + + assert_eq!(expected, output); + } +} diff --git a/crates/identifiers/src/l1.rs b/crates/identifiers/src/l1.rs new file mode 100644 index 0000000000..99f79d75f8 --- /dev/null +++ b/crates/identifiers/src/l1.rs @@ -0,0 +1,344 @@ +//! L1 identifiers. + +use std::{fmt, io}; + +use arbitrary::{Arbitrary, Error as ArbitraryError, Result as ArbitraryResult, Unstructured}; +use bitcoin_primitives::{BlockHash, absolute}; +use borsh::{BorshDeserialize, BorshSerialize}; +use hex::encode_to_slice; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser}; + +use crate::{buf::*, hash::sha256d}; + +/// L1 height. +// TODO wrap this? +pub type L1Height = u32; + +/// ID of an L1 block, usually the hash of its header. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Default, + Arbitrary, + BorshSerialize, + BorshDeserialize, + Deserialize, + Serialize, +)] +pub struct L1BlockId(Buf32); + +impl L1BlockId { + /// Computes the [`L1BlockId`] from the header buf. This is expensive in proofs and + /// should only be done when necessary. + pub fn compute_from_header_buf(buf: &[u8]) -> L1BlockId { + Self::from(sha256d(buf)) + } +} + +// Custom implementation without Debug/Display to avoid conflicts +impl From for L1BlockId { + fn from(value: Buf32) -> Self { + Self(value) + } +} + +impl From for Buf32 { + fn from(value: L1BlockId) -> Self { + value.0 + } +} + +impl AsRef<[u8; 32]> for L1BlockId { + fn as_ref(&self) -> &[u8; 32] { + self.0.as_ref() + } +} + +impl From for L1BlockId { + fn from(value: BlockHash) -> Self { + L1BlockId(value.into()) + } +} + +impl From for BlockHash { + fn from(value: L1BlockId) -> Self { + BlockHash::from_byte_array(value.0.into()) + } +} + +#[cfg(feature = "fullbtc")] +impl From for L1BlockId { + fn from(value: bitcoin::BlockHash) -> Self { + use bitcoin::hashes::Hash; + Self::from(Buf32::from(value.as_raw_hash().to_byte_array())) + } +} + +#[cfg(feature = "fullbtc")] +impl From for bitcoin::BlockHash { + fn from(value: L1BlockId) -> Self { + // eww + let array: [u8; 32] = *value.as_ref(); + Self::from_raw_hash(bitcoin::hashes::Hash::from_byte_array(array)) + } +} + +/// L1 Block commitment with block height and ID. +/// +/// Note: Height is stored as u32 internally in Bitcoin's consensus format, +/// but we serialize/deserialize using a custom implementation to maintain +/// backwards compatibility with existing data (stored as u64). +// TODO make this just use u32 everywhere +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Arbitrary)] +pub struct L1BlockCommitment { + #[arbitrary(with = arbitrary_height)] + height: absolute::Height, + blkid: L1BlockId, +} + +impl L1BlockCommitment { + /// Create a new L1 block commitment. + /// + /// # Arguments + /// * `height` - The block height + /// * `blkid` - The block ID + pub fn new(height: absolute::Height, blkid: L1BlockId) -> Self { + Self { height, blkid } + } + + /// Annoying conversion because of mixed up crates. + #[cfg(feature = "fullbtc")] + pub fn new_btc(height: bitcoin::absolute::Height, blkid: L1BlockId) -> Self { + // Safe conversion. + Self::from_height_u64(height.to_consensus_u32() as u64, blkid).unwrap() + } + + /// Create a new L1 block commitment from a u64 height. + /// + /// Returns `None` if the height is invalid (greater than u32::MAX). + pub fn from_height_u64(height: u64, blkid: L1BlockId) -> Option { + let height = absolute::Height::from_consensus(height as u32).ok()?; + Some(Self { height, blkid }) + } + + /// Get the block height as a `bitcoin` crate u32. + #[cfg(feature = "fullbtc")] + pub fn height(&self) -> bitcoin::absolute::Height { + // Safe conversion. + bitcoin::absolute::Height::from_consensus(self.height.to_consensus_u32()).unwrap() + } + + /// Gets the height as a u32. + pub fn height_u32(&self) -> u32 { + self.height.to_consensus_u32() + } + + /// Get the block height as u64 for compatibility. + pub fn height_u64(&self) -> u64 { + self.height.to_consensus_u32() as u64 + } + + /// Get the block ID. + pub fn blkid(&self) -> &L1BlockId { + &self.blkid + } +} + +// Custom Arbitrary implementation for Height +fn arbitrary_height(u: &mut Unstructured<'_>) -> ArbitraryResult { + // Heights must be less than 500_000_000 (LOCK_TIME_THRESHOLD) + let h = u32::arbitrary(u)? % 500_000_000; + absolute::Height::from_consensus(h).map_err(|_| ArbitraryError::IncorrectFormat) +} + +// Custom Borsh serialization to maintain backward compatibility with u64 storage +impl BorshSerialize for L1BlockCommitment { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + // Serialize height as u64 for backward compatibility + let height_u64 = self.height.to_consensus_u32() as u64; + BorshSerialize::serialize(&height_u64, writer)?; + BorshSerialize::serialize(&self.blkid, writer)?; + Ok(()) + } +} + +impl BorshDeserialize for L1BlockCommitment { + fn deserialize_reader(reader: &mut R) -> io::Result { + let height_u64 = u64::deserialize_reader(reader)?; + let height = absolute::Height::from_consensus(height_u64 as u32).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("invalid block height {height_u64}: {e}"), + ) + })?; + let blkid = L1BlockId::deserialize_reader(reader)?; + Ok(Self { height, blkid }) + } +} + +// Custom serde implementation to maintain backward compatibility with u64 JSON +impl Serialize for L1BlockCommitment { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use ser::SerializeStruct; + let mut state = serializer.serialize_struct("L1BlockCommitment", 2)?; + let height_u64 = self.height.to_consensus_u32() as u64; + state.serialize_field("height", &height_u64)?; + state.serialize_field("blkid", &self.blkid)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for L1BlockCommitment { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct L1BlockCommitmentVisitor; + + impl<'de> de::Visitor<'de> for L1BlockCommitmentVisitor { + type Value = L1BlockCommitment; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("struct L1BlockCommitment or tuple (u64, L1BlockId)") + } + + // Support struct format (JSON, human-readable formats) + fn visit_map(self, mut map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut height: Option = None; + let mut blkid: Option = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "height" => { + height = Some(map.next_value()?); + } + "blkid" => { + blkid = Some(map.next_value()?); + } + _ => { + let _: de::IgnoredAny = map.next_value()?; + } + } + } + + let height = height.ok_or_else(|| de::Error::missing_field("height"))?; + let blkid = blkid.ok_or_else(|| de::Error::missing_field("blkid"))?; + + let height = absolute::Height::from_consensus(height as u32).map_err(|e| { + de::Error::custom(format!("invalid block height {}: {}", height, e)) + })?; + + Ok(L1BlockCommitment { height, blkid }) + } + + // Support tuple format (bincode, compact binary formats) + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let height_u64: u64 = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let blkid: L1BlockId = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + + let height = absolute::Height::from_consensus(height_u64 as u32).map_err(|e| { + de::Error::custom(format!("invalid block height {}: {}", height_u64, e)) + })?; + + Ok(L1BlockCommitment { height, blkid }) + } + } + + // For human-readable formats (JSON), use deserialize_any to support both struct and tuple + // For binary formats (bincode), use deserialize_tuple for backward compatibility + if deserializer.is_human_readable() { + deserializer.deserialize_any(L1BlockCommitmentVisitor) + } else { + // Bincode doesn't support deserialize_any, so we use deserialize_tuple + deserializer.deserialize_tuple(2, L1BlockCommitmentVisitor) + } + } +} + +impl Default for L1BlockCommitment { + fn default() -> Self { + Self { + height: absolute::Height::ZERO, + blkid: L1BlockId::default(), + } + } +} + +impl fmt::Display for L1BlockCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Show first 2 and last 2 bytes of block ID (4 hex chars each) + let blkid_bytes = self.blkid.as_ref(); + let first_2 = &blkid_bytes[..2]; + let last_2 = &blkid_bytes[30..]; + + let mut first_hex = [0u8; 4]; + let mut last_hex = [0u8; 4]; + hex::encode_to_slice(first_2, &mut first_hex) + .expect("Failed to encode first 2 bytes to hex"); + hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); + + write!( + f, + "{}@{}..{}", + self.height.to_consensus_u32(), + str::from_utf8(&first_hex) + .expect("Failed to convert first 2 hex bytes to UTF-8 string"), + str::from_utf8(&last_hex).expect("Failed to convert last 2 hex bytes to UTF-8 string") + ) + } +} + +impl fmt::Debug for L1BlockCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "L1BlockCommitment(height={}, blkid={:?})", + self.height.to_consensus_u32(), + self.blkid + ) + } +} + +// Custom debug implementation to print the block hash in little endian +impl fmt::Debug for L1BlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut bytes = self.0.0; + bytes.reverse(); + let mut buf = [0u8; 64]; // 32 bytes * 2 for hex + encode_to_slice(bytes, &mut buf).expect("buf: enc hex"); + // SAFETY: hex encoding always produces valid UTF-8 + let hex_str = unsafe { str::from_utf8_unchecked(&buf) }; + f.write_str(hex_str) + } +} + +// Custom display implementation to print the block hash in little endian +impl fmt::Display for L1BlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut bytes = self.0.0; + bytes.reverse(); + let mut buf = [0u8; 64]; // 32 bytes * 2 for hex + encode_to_slice(bytes, &mut buf).expect("buf: enc hex"); + // SAFETY: hex encoding always produces valid UTF-8 + let hex_str = unsafe { str::from_utf8_unchecked(&buf) }; + f.write_str(hex_str) + } +} diff --git a/crates/identifiers/src/l2.rs b/crates/identifiers/src/l2.rs new file mode 100644 index 0000000000..ee1ba4bc28 --- /dev/null +++ b/crates/identifiers/src/l2.rs @@ -0,0 +1,123 @@ +//! L2 identifiers. + +use std::fmt; + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use const_hex as hex; +use serde::{Deserialize, Serialize}; + +use crate::{buf::Buf32, impl_buf_wrapper}; + +// transitional type aliases +pub type L2BlockId = OLBlockId; +pub type L2BlockCommitment = OLBlockCommitment; + +/// ID of an L2 block, usually the hash of its root header. +#[derive( + Copy, + Clone, + Eq, + Default, + PartialEq, + Ord, + PartialOrd, + Hash, + Arbitrary, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub struct OLBlockId(Buf32); + +impl_buf_wrapper!(OLBlockId, Buf32, 32); + +impl OLBlockId { + /// Returns a dummy blkid that is all zeroes. + pub fn null() -> Self { + Self::from(Buf32::zero()) + } + + /// Checks to see if this is the dummy "zero" blkid. + pub fn is_null(&self) -> bool { + self.0.is_zero() + } +} + +/// Commits to a specific block at some slot. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Deserialize, + Serialize, +)] +pub struct OLBlockCommitment { + slot: u64, + blkid: OLBlockId, +} + +impl OLBlockCommitment { + pub fn new(slot: u64, blkid: OLBlockId) -> Self { + Self { slot, blkid } + } + + pub fn null() -> Self { + Self::new(0, OLBlockId::from(Buf32::zero())) + } + + pub fn slot(&self) -> u64 { + self.slot + } + + pub fn blkid(&self) -> &OLBlockId { + &self.blkid + } + + pub fn is_null(&self) -> bool { + self.slot == 0 && self.blkid.0.is_zero() + } +} + +impl fmt::Display for OLBlockCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Show first 2 and last 2 bytes of block ID (4 hex chars each) + let blkid_bytes = self.blkid.as_ref(); + let first_2 = &blkid_bytes[..2]; + let last_2 = &blkid_bytes[30..]; + + let mut first_hex = [0u8; 4]; + let mut last_hex = [0u8; 4]; + hex::encode_to_slice(first_2, &mut first_hex) + .expect("Failed to encode first 2 bytes to hex"); + hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); + + write!( + f, + "{}@{}..{}", + self.slot, + std::str::from_utf8(&first_hex) + .expect("Failed to convert first hex bytes to UTF-8 string"), + std::str::from_utf8(&last_hex) + .expect("Failed to convert last hex bytes to UTF-8 string") + ) + } +} + +impl fmt::Debug for OLBlockCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "OLBlockCommitment(slot={}, blkid={:?})", + self.slot, self.blkid + ) + } +} diff --git a/crates/identifiers/src/lib.rs b/crates/identifiers/src/lib.rs new file mode 100644 index 0000000000..503a330b6d --- /dev/null +++ b/crates/identifiers/src/lib.rs @@ -0,0 +1,21 @@ +mod bitcoin_amount; +mod buf; +mod cred_rule; +mod epoch; +mod errors; +mod exec; +pub mod hash; +mod l1; +mod l2; +mod macros; +pub mod utils; + +pub use bitcoin_amount::BitcoinAmount; +pub use buf::*; +pub use cred_rule::*; +pub use epoch::*; +pub use errors::ParseError; +pub use exec::*; +pub use hash::sha256d; +pub use l1::*; +pub use l2::*; diff --git a/crates/identifiers/src/macros.rs b/crates/identifiers/src/macros.rs new file mode 100644 index 0000000000..b20912005e --- /dev/null +++ b/crates/identifiers/src/macros.rs @@ -0,0 +1,382 @@ +#[macro_export] +macro_rules! impl_buf_wrapper { + ($wrapper:ident, $name:ident, $len:expr) => { + impl ::std::convert::From<$name> for $wrapper { + fn from(value: $name) -> Self { + Self(value) + } + } + + impl ::std::convert::From<$wrapper> for $name { + fn from(value: $wrapper) -> Self { + value.0 + } + } + + impl ::std::convert::AsRef<[u8; $len]> for $wrapper { + fn as_ref(&self) -> &[u8; $len] { + self.0.as_ref() + } + } + + impl ::core::fmt::Debug for $wrapper { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(&self.0, f) + } + } + + impl ::core::fmt::Display for $wrapper { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Display::fmt(&self.0, f) + } + } + }; +} + +pub(crate) mod internal { + // Crate-internal impls. + + macro_rules! impl_buf_common { + ($name:ident, $len:expr) => { + impl $name { + pub const LEN: usize = $len; + + pub const fn new(data: [u8; $len]) -> Self { + Self(data) + } + + pub const fn as_slice(&self) -> &[u8] { + &self.0 + } + + pub const fn as_mut_slice(&mut self) -> &mut [u8] { + &mut self.0 + } + + pub const fn as_bytes(&self) -> &[u8] { + self.0.as_slice() + } + + pub const fn zero() -> Self { + Self::new([0; $len]) + } + + pub const fn is_zero(&self) -> bool { + let mut i = 0; + while i < $len { + if self.0[i] != 0 { + return false; + } + i += 1; + } + true + } + } + + impl ::std::convert::AsRef<[u8; $len]> for $name { + fn as_ref(&self) -> &[u8; $len] { + &self.0 + } + } + + impl ::std::convert::AsMut<[u8]> for $name { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl ::std::convert::From<[u8; $len]> for $name { + fn from(data: [u8; $len]) -> Self { + Self(data) + } + } + + impl ::std::convert::From<$name> for [u8; $len] { + fn from(buf: $name) -> Self { + buf.0 + } + } + + impl<'a> ::std::convert::From<&'a [u8; $len]> for $name { + fn from(data: &'a [u8; $len]) -> Self { + Self(*data) + } + } + + impl<'a> ::std::convert::TryFrom<&'a [u8]> for $name { + type Error = &'a [u8]; + + fn try_from(value: &'a [u8]) -> Result { + if value.len() == $len { + let mut arr = [0; $len]; + arr.copy_from_slice(value); + Ok(Self(arr)) + } else { + Err(value) + } + } + } + + impl ::std::default::Default for $name { + fn default() -> Self { + Self([0; $len]) + } + } + + impl ::std::fmt::Debug for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + // twice as large, required by the hex::encode_to_slice. + let mut buf = [0; $len * 2]; + ::hex::encode_to_slice(self.0, &mut buf).expect("buf: enc hex"); + f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) }) + } + } + + impl ::std::fmt::Display for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + // fmt only first and last bits of data. + let mut buf = [0; 6]; + ::hex::encode_to_slice(&self.0[..3], &mut buf).expect("buf: enc hex"); + f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })?; + f.write_str("..")?; + ::hex::encode_to_slice(&self.0[$len - 3..], &mut buf).expect("buf: enc hex"); + f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })?; + Ok(()) + } + } + + impl ::borsh::BorshSerialize for $name { + fn serialize(&self, writer: &mut W) -> ::std::io::Result<()> { + let bytes = self.0.as_ref(); + let _ = writer.write(bytes)?; + Ok(()) + } + } + + impl ::borsh::BorshDeserialize for $name { + fn deserialize_reader( + reader: &mut R, + ) -> ::std::io::Result { + let mut array = [0u8; $len]; + reader.read_exact(&mut array)?; + Ok(array.into()) + } + } + + impl<'a> ::arbitrary::Arbitrary<'a> for $name { + fn arbitrary(u: &mut ::arbitrary::Unstructured<'a>) -> ::arbitrary::Result { + let mut array = [0u8; $len]; + u.fill_buffer(&mut array)?; + Ok(array.into()) + } + } + }; + } + + macro_rules! impl_buf_serde { + ($name:ident, $len:expr) => { + impl ::serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + // Convert the inner array to a hex string (without 0x prefix) + let hex_str = ::hex::encode(&self.0); + serializer.serialize_str(&hex_str) + } + } + + impl<'de> ::serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + // Define a Visitor for deserialization. + // P.S. Make it in the scope of the function to avoid name conflicts + // for different macro_rules invocations. + struct BufVisitor; + + impl<'de> ::serde::de::Visitor<'de> for BufVisitor { + type Value = $name; + + fn expecting( + &self, + formatter: &mut ::std::fmt::Formatter<'_>, + ) -> ::std::fmt::Result { + write!( + formatter, + "a hex string with an optional 0x prefix representing {} bytes", + $len + ) + } + + fn visit_str(self, v: &str) -> Result<$name, E> + where + E: ::serde::de::Error, + { + // Remove the optional "0x" or "0X" prefix if present. + let hex_str = if v.starts_with("0x") || v.starts_with("0X") { + &v[2..] + } else { + v + }; + + // Decode the hex string into a vector of bytes. + let bytes = ::hex::decode(hex_str).map_err(E::custom)?; + + // Ensure the decoded bytes have the expected length. + if bytes.len() != $len { + return Err(E::custom(format!( + "expected {} bytes, got {}", + $len, + bytes.len() + ))); + } + + // Convert the Vec into a fixed-size array. + let mut array = [0u8; $len]; + array.copy_from_slice(&bytes); + Ok($name(array)) + } + + fn visit_bytes(self, v: &[u8]) -> Result<$name, E> + where + E: ::serde::de::Error, + { + if v.len() == $len { + let mut array = [0u8; $len]; + array.copy_from_slice(v); + Ok($name(array)) + } else { + // Try to interpret the bytes as a UTF-8 encoded hex string. + let s = ::std::str::from_utf8(v).map_err(E::custom)?; + self.visit_str(s) + } + } + + fn visit_seq(self, mut seq: A) -> Result<$name, A::Error> + where + A: ::serde::de::SeqAccess<'de>, + { + let mut array = [0u8; $len]; + for i in 0..$len { + array[i] = seq + .next_element::()? + .ok_or_else(|| ::serde::de::Error::invalid_length(i, &self))?; + } + // Ensure there are no extra elements. + if let Some(_) = seq.next_element::()? { + return Err(::serde::de::Error::custom(format!( + "expected a sequence of exactly {} bytes, but found extra elements", + $len + ))); + } + Ok($name(array)) + } + } + + if deserializer.is_human_readable() { + // For human-readable formats, support multiple input types. + // Use with the _any, so serde can decide whether to visit seq, bytes or str. + deserializer.deserialize_any(BufVisitor) + } else { + // Bincode does not support DeserializeAny, so deserializing with the _str. + deserializer.deserialize_str(BufVisitor) + } + } + } + }; + } + + pub(crate) use impl_buf_common; + pub(crate) use impl_buf_serde; +} + +#[cfg(test)] +mod tests { + + #[derive(PartialEq)] + pub struct TestBuf20([u8; 20]); + + crate::macros::internal::impl_buf_common!(TestBuf20, 20); + crate::macros::internal::impl_buf_serde!(TestBuf20, 20); + + #[test] + fn test_from_into_array() { + let buf = TestBuf20::new([5u8; 20]); + let arr: [u8; 20] = buf.into(); + assert_eq!(arr, [5; 20]); + } + + #[test] + fn test_from_array_ref() { + let arr = [2u8; 20]; + let buf: TestBuf20 = TestBuf20::from(&arr); + assert_eq!(buf.as_slice(), &arr); + } + + #[test] + fn test_default() { + let buf = TestBuf20::default(); + assert_eq!(buf.as_slice(), &[0; 20]); + } + + #[test] + fn test_serialize_hex() { + let data = [1u8; 20]; + let buf = TestBuf20(data); + let json = serde_json::to_string(&buf).unwrap(); + // Since we serialize as a string, json should be the hex-encoded string wrapped in quotes. + let expected = format!("\"{}\"", hex::encode(data)); + assert_eq!(json, expected); + } + + #[test] + fn test_deserialize_hex_without_prefix() { + let data = [2u8; 20]; + let hex_str = hex::encode(data); + let json = format!("\"{hex_str}\""); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_hex_with_prefix() { + let data = [3u8; 20]; + let hex_str = hex::encode(data); + let json = format!("\"0x{hex_str}\""); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_from_seq() { + // Provide a JSON array of numbers. + let data = [5u8; 20]; + let json = serde_json::to_string(&data).unwrap(); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_from_bytes_via_array() { + // Although JSON doesn't have a native "bytes" type, this test uses a JSON array + // to exercise the same code path as visit_bytes when deserializing a sequence. + let data = [7u8; 20]; + // Simulate input as a JSON array + let json = serde_json::to_string(&data).unwrap(); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_bincode_roundtrip() { + let data = [9u8; 20]; + let buf = TestBuf20(data); + // bincode is non-human-readable so our implementation will use deserialize_tuple. + let encoded = bincode::serialize(&buf).expect("bincode serialization failed"); + let decoded: TestBuf20 = + bincode::deserialize(&encoded).expect("bincode deserialization failed"); + assert_eq!(buf, decoded); + } +} diff --git a/crates/identifiers/src/utils.rs b/crates/identifiers/src/utils.rs new file mode 100644 index 0000000000..2202819312 --- /dev/null +++ b/crates/identifiers/src/utils.rs @@ -0,0 +1,73 @@ +use crate::{hash::sha256d, Buf32}; + +/// Generates cohashes and computes the Merkle root for a transaction ID at a specific index +/// within a given slice of elements that can be converted to [`Buf32`]. +/// +/// This function supports any type that implements the [`Into`] trait, such as +/// [`Txid`s](bitcoin::Txid) or [`Wtxid`s](bitcoin::Wtxid). +/// +/// # Parameters +/// +/// - `ids`: A slice of ids ([`Txid`s](bitcoin::Txid) or [`Wtxid`s](bitcoin::Wtxid)) that can be +/// converted into [`Buf32`]. +/// - `index`: The index of the transaction for which we want the cohashes. +/// +/// # Notes +/// +/// Cohashes refer to the intermediate hashes (sometimes called "siblings") needed to +/// reconstruct the Merkle path for a given transaction. These intermediate hashes, along with +/// the transaction's hash itself, can be used to compute the Merkle root, thus verifying the +/// transaction's membership in the Merkle tree. +/// +/// # Returns +/// +/// - A tuple `(Vec, Buf32)` containing the cohashes and the Merkle root. +/// +/// # Panics +/// +/// - If the `index` is out of bounds for the `elements` length. +pub fn get_cohashes(ids: &[T], index: u32) -> (Vec, Buf32) +where + T: Into + Clone, +{ + assert!( + (index as usize) < ids.len(), + "The transaction index should be within the txids length" + ); + let mut curr_level: Vec = ids.iter().cloned().map(Into::into).collect(); + + let mut curr_index = index; + let mut proof = Vec::new(); + + while curr_level.len() > 1 { + let len = curr_level.len(); + if !len.is_multiple_of(2) { + curr_level.push(curr_level[len - 1]); + } + + let proof_item_index = if curr_index.is_multiple_of(2) { + curr_index + 1 + } else { + curr_index - 1 + }; + + let item = curr_level[proof_item_index as usize]; + proof.push(item); + + // construct pairwise hash + curr_level = curr_level + .chunks(2) + .map(|pair| { + let [a, b] = pair else { + panic!("utils: cohash chunk should be a pair"); + }; + let mut arr = [0u8; 64]; + arr[..32].copy_from_slice(a.as_bytes()); + arr[32..].copy_from_slice(b.as_bytes()); + sha256d(&arr) + }) + .collect::>(); + curr_index >>= 1; + } + (proof, curr_level[0]) +} diff --git a/crates/l1tx/Cargo.toml b/crates/l1tx/Cargo.toml index bec01ce2a9..8ba8a666ca 100644 --- a/crates/l1tx/Cargo.toml +++ b/crates/l1tx/Cargo.toml @@ -7,10 +7,12 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] -strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true strata-crypto.workspace = true +strata-identifiers.workspace = true strata-ol-chainstate-types.workspace = true +strata-params.workspace = true strata-primitives.workspace = true strata-state.workspace = true diff --git a/crates/l1tx/src/deposit/common.rs b/crates/l1tx/src/deposit/common.rs index 7c528065e9..df27f2cb07 100644 --- a/crates/l1tx/src/deposit/common.rs +++ b/crates/l1tx/src/deposit/common.rs @@ -1,5 +1,5 @@ use bitcoin::script::Instructions; -use strata_primitives::params::DepositTxParams; +use strata_params::DepositTxParams; use super::error::DepositParseError; use crate::utils::next_bytes; diff --git a/crates/l1tx/src/deposit/deposit_request.rs b/crates/l1tx/src/deposit/deposit_request.rs index 346eab1821..0725897f46 100644 --- a/crates/l1tx/src/deposit/deposit_request.rs +++ b/crates/l1tx/src/deposit/deposit_request.rs @@ -3,8 +3,8 @@ use std::convert::TryInto; use bitcoin::{opcodes::all::OP_RETURN, ScriptBuf, Transaction}; -use strata_asm_types::DepositRequestInfo; -use strata_primitives::params::DepositTxParams; +use strata_btc_types::DepositRequestInfo; +use strata_params::DepositTxParams; use tracing::debug; use super::{common::DepositRequestScriptInfo, error::DepositParseError}; diff --git a/crates/l1tx/src/deposit/deposit_tx.rs b/crates/l1tx/src/deposit/deposit_tx.rs index 9bf5dd9d3b..3b1039cb8b 100644 --- a/crates/l1tx/src/deposit/deposit_tx.rs +++ b/crates/l1tx/src/deposit/deposit_tx.rs @@ -9,8 +9,9 @@ use bitcoin::{ Amount, OutPoint, ScriptBuf, TapNodeHash, Transaction, TxOut, XOnlyPublicKey, }; use secp256k1::Message; -use strata_asm_types::DepositInfo; -use strata_primitives::{buf::Buf32, l1::OutputRef, prelude::DepositTxParams}; +use strata_btc_types::{DepositInfo, OutputRef}; +use strata_identifiers::Buf32; +use strata_params::DepositTxParams; use crate::{ deposit::error::DepositParseError, diff --git a/crates/l1tx/src/envelope/builder.rs b/crates/l1tx/src/envelope/builder.rs index cb55944f1b..a244f17291 100644 --- a/crates/l1tx/src/envelope/builder.rs +++ b/crates/l1tx/src/envelope/builder.rs @@ -7,10 +7,8 @@ use bitcoin::{ script::PushBytesBuf, ScriptBuf, }; -use strata_primitives::{ - l1::payload::{L1Payload, L1PayloadType}, - params::Params, -}; +use strata_params::Params; +use strata_primitives::l1::payload::{L1Payload, L1PayloadType}; // Generates a [`ScriptBuf`] that consists of `OP_IF .. OP_ENDIF` block pub fn build_envelope_script(params: &Params, payloads: &[L1Payload]) -> anyhow::Result { diff --git a/crates/l1tx/src/filter/indexer.rs b/crates/l1tx/src/filter/indexer.rs index b6f00f554b..43ae09e500 100644 --- a/crates/l1tx/src/filter/indexer.rs +++ b/crates/l1tx/src/filter/indexer.rs @@ -1,5 +1,5 @@ use bitcoin::{Block, Transaction}; -use strata_asm_types::{DepositInfo, DepositSpendInfo, WithdrawalFulfillmentInfo}; +use strata_btc_types::{DepositInfo, DepositSpendInfo, WithdrawalFulfillmentInfo}; use strata_checkpoint_types::SignedCheckpoint; use strata_primitives::indexed::Indexed; diff --git a/crates/l1tx/src/filter/mod.rs b/crates/l1tx/src/filter/mod.rs index b35063e926..7a47ee054c 100644 --- a/crates/l1tx/src/filter/mod.rs +++ b/crates/l1tx/src/filter/mod.rs @@ -1,5 +1,5 @@ use bitcoin::Transaction; -use strata_asm_types::{DepositInfo, DepositSpendInfo}; +use strata_btc_types::{DepositInfo, DepositSpendInfo}; use strata_primitives::l1::OutputRef; mod checkpoint; diff --git a/crates/l1tx/src/filter/types.rs b/crates/l1tx/src/filter/types.rs index a08c60337e..712f4a142b 100644 --- a/crates/l1tx/src/filter/types.rs +++ b/crates/l1tx/src/filter/types.rs @@ -1,11 +1,11 @@ use bitcoin::{hashes::Hash, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use strata_ol_chainstate_types::Chainstate; +use strata_btc_types::OutputRef; +use strata_identifiers::{Buf32, CredRule}; +use strata_params::{DepositTxParams, RollupParams}; use strata_primitives::{ - block_credential::CredRule, - buf::Buf32, - l1::{BitcoinAddress, BitcoinAmount, BitcoinScriptBuf, OutputRef, XOnlyPk}, - params::{DepositTxParams, RollupParams}, + l1::{BitcoinAddress, BitcoinAmount, BitcoinScriptBuf, XOnlyPk}, sorted_vec::{FlatTable, SortedVec, TableEntry}, }; use strata_state::bridge_state::{DepositEntry, DepositState}; diff --git a/crates/l1tx/src/filter/withdrawal_fulfillment.rs b/crates/l1tx/src/filter/withdrawal_fulfillment.rs index 22c19dc50c..f666f1410e 100644 --- a/crates/l1tx/src/filter/withdrawal_fulfillment.rs +++ b/crates/l1tx/src/filter/withdrawal_fulfillment.rs @@ -1,5 +1,5 @@ use bitcoin::{ScriptBuf, Transaction}; -use strata_asm_types::WithdrawalFulfillmentInfo; +use strata_btc_types::WithdrawalFulfillmentInfo; use strata_primitives::{buf::Buf32, l1::BitcoinAmount}; use tracing::{debug, error}; diff --git a/crates/l1tx/src/messages.rs b/crates/l1tx/src/messages.rs index 611da47926..8fa119b6bb 100644 --- a/crates/l1tx/src/messages.rs +++ b/crates/l1tx/src/messages.rs @@ -1,4 +1,4 @@ -use strata_asm_types::{DaCommitment, ProtocolOperation}; +use strata_btc_types::{DaCommitment, ProtocolOperation}; use strata_primitives::indexed::Indexed; /// Container for the different kinds of messages that we could extract from a L1 tx. diff --git a/crates/l1tx/src/utils.rs b/crates/l1tx/src/utils.rs index 6f607a6136..99df4b0612 100644 --- a/crates/l1tx/src/utils.rs +++ b/crates/l1tx/src/utils.rs @@ -7,11 +7,9 @@ use bitcoin::{ Address, Network, Opcode, XOnlyPublicKey, }; use strata_crypto::multisig::aggregate_schnorr_keys; -use strata_primitives::{ - buf::Buf32, - l1::BitcoinAddress, - params::{OperatorConfig, RollupParams}, -}; +use strata_identifiers::Buf32; +use strata_params::{OperatorConfig, RollupParams}; +use strata_primitives::l1::BitcoinAddress; /// Extract next instruction and try to parse it as an opcode pub fn next_op(instructions: &mut Instructions<'_>) -> Option { @@ -92,9 +90,9 @@ pub mod test_utils { secp256k1::{Keypair, Secp256k1, SecretKey}, Address, Network, }; + use strata_params::Params; use strata_primitives::{ l1::{BitcoinAddress, XOnlyPk}, - params::Params, sorted_vec::FlatTable, }; use strata_state::bridge_state::DepositEntry; diff --git a/crates/ledger-types/Cargo.toml b/crates/ledger-types/Cargo.toml new file mode 100644 index 0000000000..a3a774aca4 --- /dev/null +++ b/crates/ledger-types/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "strata-ledger-types" +version = "0.1.0" +edition = "2024" + +[dependencies] +strata-acct-types.workspace = true +strata-identifiers.workspace = true +strata-snark-acct-types.workspace = true + +[lints] +workspace = true diff --git a/crates/ledger-types/src/l1vs.rs b/crates/ledger-types/src/l1vs.rs new file mode 100644 index 0000000000..bf9c09ba55 --- /dev/null +++ b/crates/ledger-types/src/l1vs.rs @@ -0,0 +1,49 @@ +//! L1 view state. +// TODO should this be renamed to "epochal state" since we encompasses a little +// more than just L1 state like this current epoch (which we include here since +// it's only updated at the epoch boundaries anyways) + +use strata_acct_types::BitcoinAmount; +pub use strata_ol_chain_types::{AsmManifest, EpochCommitment, L1BlockId, L1Height}; + +/// State relating to the L1 view. +/// +/// This is only updated in the sealing phase at epoch boundaries, so it has no +/// DA footprint. There will probably also be some extra things in here that +/// have a similar data path without DA footprint. +pub trait IL1ViewState { + /// Gets the current epoch. + fn cur_epoch(&self) -> u32; + + /// Sets the current epoch. + fn set_cur_epoch(&mut self, epoch: u32); + + /// Last L1 block ID. + fn last_l1_blkid(&self) -> &L1BlockId; + + /// Last L1 block height. + fn last_l1_height(&self) -> L1Height; + + /// Appends a new ASM manifest to the accumulator, also updating the last L1 + /// block height and other fields. + // TODO should this take a L1BlockCommitment with this? + fn append_manifest(&mut self, mf: AsmManifest); + + /// Gets the field for the epoch that the ASM considers to be valid. + /// + /// This is our perspective of the perspective of the last block's ASM + /// manifest we've accepted. + fn asm_recorded_epoch(&self) -> &EpochCommitment; + + /// Sets the field for the epoch that the ASM considers to be finalized. + /// + /// This is our perspective of the perspective of the last block's ASM + /// manifest we've accepted. + fn set_asm_recorded_epoch(&mut self, epoch: EpochCommitment); + + /// Gets the total OL ledger balance. + fn total_ledger_balance(&self) -> BitcoinAmount; + + /// Sets the total OL ledger balance. + fn set_total_ledger_balance(&mut self, amt: BitcoinAmount) -> BitcoinAmount; +} diff --git a/crates/ledger-types/src/lib.rs b/crates/ledger-types/src/lib.rs new file mode 100644 index 0000000000..2881893622 --- /dev/null +++ b/crates/ledger-types/src/lib.rs @@ -0,0 +1,43 @@ +//! Ledger data types. +//! +//! This crate is NOT about the basic data structures themselves. This crate +//! focuses on how we access the ledger data structures in state transition +//! execution contexts. +//! +//! We present a trait that represents the various types of structures we +//! interact with in the ledger's state, and expose accessor functions on it. +//! The different impls of these traits are tailored for different contexts. In +//! some contexts we care about tracing DA generation, in others we may be doing +//! blocking fetches from disk we want to trace for later proof generation. +//! +//! We use the `I` prefix convention which is normally uncommon in Rust to refer +//! to these abstract data structures. This is because the "ordinary" struct +//! versions of these data structure we use on the wire are the "real" versions +//! we want to think of them as being, but these traits are standins for those. +//! Making up new names for these items would crate too much confusion. +//! +//! As for structure, this design is based around a "toplevel" state that is not +//! ever actually directly accessed. Below it, there are two parts: +//! +//! * A "global" state that is treated using the DA framework directly. +//! * A "L1 view" state that is only updated in the sealing phase and isn't included in DA. +//! * An "accounts" table, which are selectively loaded. +//! +//! These parts are committed to in the toplevel state, which is updated later +//! when we finish a state transition. + +mod account; +mod coin; +mod global_state; +mod l1vs; +mod state_accessor; + +pub use account::{AccountTypeState, IAccountState, ISnarkAccountState, ISnarkAccountStateExt}; +pub use coin::Coin; +pub use global_state::IGlobalState; +pub use l1vs::IL1ViewState; +pub use state_accessor::StateAccessor; + +// transitional crap, still not happy with the way these crates go together +#[rustfmt::skip] +pub use l1vs::{AsmManifest, EpochCommitment, L1Height, L1BlockId}; diff --git a/crates/ol-chain-types/Cargo.toml b/crates/ol-chain-types/Cargo.toml index 475e66eda3..a6dcad5305 100644 --- a/crates/ol-chain-types/Cargo.toml +++ b/crates/ol-chain-types/Cargo.toml @@ -7,16 +7,21 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] -strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-crypto.workspace = true -strata-primitives.workspace = true -strata-state.workspace = true +strata-identifiers.workspace = true +strata-params.workspace = true arbitrary.workspace = true +bitcoin-bosd = { workspace = true, default-features = false } borsh.workspace = true +num_enum.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true [dev-dependencies] strata-test-utils.workspace = true + +[features] +default = [] diff --git a/crates/ol-chain-types/src/block.rs b/crates/ol-chain-types/src/block.rs index 988034e5cc..046865792e 100644 --- a/crates/ol-chain-types/src/block.rs +++ b/crates/ol-chain-types/src/block.rs @@ -1,11 +1,13 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_asm_types::L1BlockManifest; -use strata_primitives::prelude::*; -use strata_state::exec_update::ExecUpdate; +use strata_btc_types::L1BlockManifest; +use strata_identifiers::*; -use crate::header::{L2BlockHeader, SignedL2BlockHeader}; +use crate::{ + exec_update::ExecUpdate, + header::{L2BlockHeader, SignedL2BlockHeader}, +}; /// Full contents of the bare L2 block. #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] diff --git a/crates/state/src/bridge_ops.rs b/crates/ol-chain-types/src/bridge_ops.rs similarity index 94% rename from crates/state/src/bridge_ops.rs rename to crates/ol-chain-types/src/bridge_ops.rs index 7a2fd399f9..fc454159bb 100644 --- a/crates/state/src/bridge_ops.rs +++ b/crates/ol-chain-types/src/bridge_ops.rs @@ -1,8 +1,10 @@ //! Types for managing pending bridging operations in the CL state. +// TODO rework this module since this types have different relationships now +use bitcoin_bosd::Descriptor; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{bitcoin_bosd::Descriptor, buf::Buf32, l1::BitcoinAmount}; +use strata_identifiers::{BitcoinAmount, Buf32}; // TODO make this not hardcoded! pub const WITHDRAWAL_DENOMINATION: BitcoinAmount = BitcoinAmount::from_int_btc(10); diff --git a/crates/ol-chain-types/src/epoch.rs b/crates/ol-chain-types/src/epoch.rs new file mode 100644 index 0000000000..71cccc0a89 --- /dev/null +++ b/crates/ol-chain-types/src/epoch.rs @@ -0,0 +1,4 @@ +//! General epoch identifier types. + +// TODO move this type to this crate +pub use strata_identifiers::EpochCommitment; diff --git a/crates/state/src/exec_update.rs b/crates/ol-chain-types/src/exec_update.rs similarity index 93% rename from crates/state/src/exec_update.rs rename to crates/ol-chain-types/src/exec_update.rs index 8a8cb70f39..d701a70ce2 100644 --- a/crates/state/src/exec_update.rs +++ b/crates/ol-chain-types/src/exec_update.rs @@ -1,16 +1,16 @@ //! Chain data types relating to the CL's updating view of an execution //! environment's state. For now the EVM EL is the only execution environment. +// TODO remove this whole module soon, it's irrelevant use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - buf::Buf32, evm_exec::create_evm_extra_payload, prelude::payload::BlobSpec, -}; +use strata_identifiers::Buf32; use crate::{ bridge_ops::{self, DepositIntent}, - prelude::StateQueue, + legacy_da_payload::*, + state_queue::StateQueue, }; /// Full update payload containing inputs and outputs to an EE state update. @@ -79,6 +79,15 @@ impl<'a> Arbitrary<'a> for UpdateInput { } } +/// Generate extra_payload for evm el +/// +/// This corresponds to the version in `strata-primitives::evm_exec`, which we +/// don't want to depend on here. +fn create_evm_extra_payload(block_hash: Buf32) -> Vec { + // This is silly, but it is actually equivalent. + block_hash.as_bytes().to_vec() +} + impl UpdateInput { pub fn new( update_idx: u64, diff --git a/crates/ol-chain-types/src/header.rs b/crates/ol-chain-types/src/header.rs index 3e5c22ce12..906aec28d5 100644 --- a/crates/ol-chain-types/src/header.rs +++ b/crates/ol-chain-types/src/header.rs @@ -3,11 +3,7 @@ use std::io::{self, Cursor, Write}; use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use strata_primitives::{ - buf::{Buf32, Buf64}, - hash, - l2::{L2BlockCommitment, L2BlockId}, -}; +use strata_identifiers::{hash, Buf32, Buf64, L2BlockCommitment, L2BlockId}; use crate::block::L2BlockBody; @@ -87,7 +83,7 @@ impl L2BlockHeader { // 8 + 8 + 8 + 32 + 32 + 32 + 32 = 152 let mut buf = [0; 152]; fill_sighash_buf(self, &mut buf).expect("blockasm: compute sighash"); - strata_primitives::hash::raw(&buf) + strata_identifiers::hash::raw(&buf) } } diff --git a/crates/ol-chain-types/src/id.rs b/crates/ol-chain-types/src/id.rs index 5acbc0fc14..8d7e6d4c79 100644 --- a/crates/ol-chain-types/src/id.rs +++ b/crates/ol-chain-types/src/id.rs @@ -1 +1,2 @@ -pub use strata_primitives::l2::L2BlockId; +// TODO remove this reexport +pub use strata_identifiers::L2BlockId; diff --git a/crates/ol-chain-types/src/l1_segment.rs b/crates/ol-chain-types/src/l1_segment.rs new file mode 100644 index 0000000000..01c0b0b552 --- /dev/null +++ b/crates/ol-chain-types/src/l1_segment.rs @@ -0,0 +1 @@ +//! L1 segment types. diff --git a/crates/ol-chain-types/src/legacy/mod.rs b/crates/ol-chain-types/src/legacy/mod.rs new file mode 100644 index 0000000000..9e8ca1804e --- /dev/null +++ b/crates/ol-chain-types/src/legacy/mod.rs @@ -0,0 +1,2 @@ +// Legacy types have been moved to strata-btc-types. +// Downstream crates should import from strata_btc_types directly. diff --git a/crates/ol-chain-types/src/legacy_da_payload.rs b/crates/ol-chain-types/src/legacy_da_payload.rs new file mode 100644 index 0000000000..dc939e0492 --- /dev/null +++ b/crates/ol-chain-types/src/legacy_da_payload.rs @@ -0,0 +1,224 @@ +//! Types relating to payloads. +//! +//! These types don't care about the *purpose* of the payloads, we only care about what's in them. +//! +//! This module is going to be removed soon while we rework how DA works. +// TODO remove these types + +use arbitrary::Arbitrary; +use borsh::{BorshDeserialize, BorshSerialize}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use serde::{Deserialize, Serialize}; + +use strata_identifiers::{Buf32, hash}; + +/// DA destination identifier. This will eventually be used to enable +/// storing payloads on alternative availability schemes. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + BorshDeserialize, + BorshSerialize, + IntoPrimitive, + TryFromPrimitive, + Serialize, + Deserialize, +)] +#[borsh(use_discriminant = true)] +#[repr(u8)] +pub enum PayloadDest { + /// If we expect the DA to be on the L1 chain that we settle to. This is + /// always the strongest DA layer we have access to. + L1 = 0, +} + +/// Manual `Arbitrary` impl so that we always generate L1 DA if we add future +/// ones that would work in totally different ways. +impl<'a> Arbitrary<'a> for PayloadDest { + fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self::L1) + } +} + +/// Summary of a DA payload to be included on a DA layer. Specifies the target and +/// a commitment to the payload. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Serialize, + Deserialize, +)] +pub struct BlobSpec { + /// Target settlement layer we're expecting the DA on. + dest: PayloadDest, + + /// Commitment to the payload (probably just a hash or a + /// merkle root) that we expect to see committed to DA. + commitment: Buf32, +} + +impl BlobSpec { + /// The target we expect the DA payload to be stored on. + pub fn dest(&self) -> PayloadDest { + self.dest + } + + /// Commitment to the payload. + pub fn commitment(&self) -> &Buf32 { + &self.commitment + } + + #[expect(dead_code, reason = "Constructor for testing purposes")] + fn new(dest: PayloadDest, commitment: Buf32) -> Self { + Self { dest, commitment } + } +} + +/// Summary of a DA payload to be included on a DA layer. Specifies the target and +/// a commitment to the payload. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Hash, + Arbitrary, + BorshDeserialize, + BorshSerialize, + Serialize, + Deserialize, +)] +pub struct PayloadSpec { + /// Target settlement layer we're expecting the DA on. + dest: PayloadDest, + + /// Commitment to the payload (probably just a hash or a + /// merkle root) that we expect to see committed to DA. + commitment: Buf32, +} + +impl PayloadSpec { + /// The target we expect the DA payload to be stored on. + pub fn dest(&self) -> PayloadDest { + self.dest + } + + /// Commitment to the payload. + pub fn commitment(&self) -> &Buf32 { + &self.commitment + } + + fn new(dest: PayloadDest, commitment: Buf32) -> Self { + Self { dest, commitment } + } +} + +/// Data that is submitted to L1. This can be DA, Checkpoint, etc. +#[derive( + Clone, Debug, Eq, PartialEq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct L1Payload { + data: Vec, + payload_type: L1PayloadType, +} + +impl L1Payload { + pub fn new(data: Vec, payload_type: L1PayloadType) -> Self { + Self { data, payload_type } + } + + pub fn new_checkpoint(data: Vec) -> Self { + Self::new(data, L1PayloadType::Checkpoint) + } + + pub fn new_da(data: Vec) -> Self { + Self::new(data, L1PayloadType::Da) + } + + pub fn data(&self) -> &[u8] { + &self.data + } + + pub fn payload_type(&self) -> &L1PayloadType { + &self.payload_type + } + + pub fn hash(&self) -> Buf32 { + let mut buf = self.data.clone(); + buf.extend(borsh::to_vec(&self.payload_type).expect("Could not serialize payload type")); + hash::raw(&buf) + } +} + +#[derive( + Clone, Debug, Eq, PartialEq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub enum L1PayloadType { + Checkpoint, + Da, +} + +/// Intent produced by the EE on a "full" verification, but if we're just +/// verifying a proof we may not have access to this but still want to reason +/// about it. +/// +/// These are never stored on-chain. +#[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize)] +// TODO: rename this to L1PayloadIntent and remove the dest field +pub struct PayloadIntent { + /// The destination for this payload. + dest: PayloadDest, + + /// Commitment to the payload. + commitment: Buf32, + + /// Blob payload. + payload: L1Payload, +} + +impl PayloadIntent { + pub fn new(dest: PayloadDest, commitment: Buf32, payload: L1Payload) -> Self { + Self { + dest, + commitment, + payload, + } + } + + /// The target we expect the DA payload to be stored on. + pub fn dest(&self) -> PayloadDest { + self.dest + } + + /// Commitment to the payload, which might be context-specific. This + /// is conceptually unrelated to the payload ID that we use for tracking which + /// payloads we've written in the L1 writer bookkeeping. + pub fn commitment(&self) -> &Buf32 { + &self.commitment + } + + /// The payload that matches the commitment. + pub fn payload(&self) -> &L1Payload { + &self.payload + } + + /// Generates the spec from the relevant parts of the payload intent that + /// uniquely refers to the payload data. + pub fn to_spec(&self) -> PayloadSpec { + PayloadSpec::new(self.dest, self.commitment) + } +} diff --git a/crates/ol-chain-types/src/legacy_manifest.rs b/crates/ol-chain-types/src/legacy_manifest.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/ol-chain-types/src/legacy_manifest.rs @@ -0,0 +1 @@ + diff --git a/crates/ol-chain-types/src/lib.rs b/crates/ol-chain-types/src/lib.rs index f48b1a1cd2..0fba7de23c 100644 --- a/crates/ol-chain-types/src/lib.rs +++ b/crates/ol-chain-types/src/lib.rs @@ -4,11 +4,23 @@ //! the state management layer. mod block; +mod bridge_ops; +mod epoch; +mod exec_update; mod header; mod id; +mod l1_segment; +pub mod legacy; +pub mod legacy_da_payload; +mod state_queue; mod validation; pub use block::*; +pub use bridge_ops::*; +pub use epoch::*; +pub use exec_update::*; pub use header::*; pub use id::*; +pub use l1_segment::*; +pub use state_queue::*; pub use validation::*; diff --git a/crates/state/src/state_queue.rs b/crates/ol-chain-types/src/state_queue.rs similarity index 100% rename from crates/state/src/state_queue.rs rename to crates/ol-chain-types/src/state_queue.rs diff --git a/crates/ol-chain-types/src/validation.rs b/crates/ol-chain-types/src/validation.rs index b871984920..7dfa5288c6 100644 --- a/crates/ol-chain-types/src/validation.rs +++ b/crates/ol-chain-types/src/validation.rs @@ -1,10 +1,8 @@ -use strata_crypto::verify_schnorr_sig; -use strata_primitives::{ - block_credential::CredRule, - buf::{Buf32, Buf64}, - hash, - params::RollupParams, -}; +// TODO this module should probably also be moved to some "validation-type" crate + +use strata_crypto::schnorr::verify_schnorr_sig; +use strata_identifiers::{hash, Buf32, Buf64, CredRule}; +use strata_params::RollupParams; use thiserror::Error; use tracing::warn; diff --git a/crates/ol-chainstate-types/Cargo.toml b/crates/ol-chainstate-types/Cargo.toml index 48874a75cc..a2968bcb54 100644 --- a/crates/ol-chainstate-types/Cargo.toml +++ b/crates/ol-chainstate-types/Cargo.toml @@ -8,6 +8,8 @@ workspace = true [dependencies] strata-asm-types.workspace = true +strata-btc-types.workspace = true +strata-ol-chain-types.workspace = true strata-primitives.workspace = true strata-state.workspace = true diff --git a/crates/ol-chainstate-types/src/chain_state.rs b/crates/ol-chainstate-types/src/chain_state.rs index aee87bc3fd..5bd76dd602 100644 --- a/crates/ol-chainstate-types/src/chain_state.rs +++ b/crates/ol-chainstate-types/src/chain_state.rs @@ -8,11 +8,10 @@ use strata_primitives::{ hash::compute_borsh_hash, l2::{L2BlockCommitment, L2BlockId}, }; +use strata_ol_chain_types::{DepositIntent, StateQueue, WithdrawalIntent}; use strata_state::{ - bridge_ops, bridge_state::{self, DepositsTable, OperatorTable}, exec_env::{self, ExecEnvState}, - state_queue::StateQueue, }; use crate::{genesis::GenesisStateData, l1_view::L1ViewState}; @@ -54,7 +53,7 @@ pub struct Chainstate { pub(crate) l1_state: L1ViewState, /// Pending withdrawals that have been initiated but haven't been sent out. - pub(crate) pending_withdraws: StateQueue, + pub(crate) pending_withdraws: StateQueue, /// Execution environment state. This is just for the single EE we support /// right now. @@ -164,7 +163,7 @@ impl Chainstate { self.is_epoch_finishing } - pub fn pending_withdraws(&self) -> &StateQueue { + pub fn pending_withdraws(&self) -> &StateQueue { &self.pending_withdraws } } diff --git a/crates/ol-chainstate-types/src/state_op.rs b/crates/ol-chainstate-types/src/state_op.rs index ef7ae720ae..53fd50c35a 100644 --- a/crates/ol-chainstate-types/src/state_op.rs +++ b/crates/ol-chainstate-types/src/state_op.rs @@ -6,7 +6,8 @@ use bitcoin::block::Header; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_asm_types::{L1VerificationError, WithdrawalFulfillmentInfo}; +use strata_asm_types::L1VerificationError; +use strata_btc_types::WithdrawalFulfillmentInfo; use strata_primitives::{ bridge::{BitcoinBlockHeight, OperatorIdx}, buf::Buf32, @@ -14,8 +15,8 @@ use strata_primitives::{ l1::{BitcoinAmount, OutputRef}, l2::{L2BlockCommitment, L2BlockId}, }; +use strata_ol_chain_types::DepositIntent; use strata_state::{ - bridge_ops::DepositIntent, bridge_state::{DepositEntry, DepositState, DispatchCommand, DispatchedState, FulfilledState}, }; use tracing::warn; diff --git a/crates/params/Cargo.toml b/crates/params/Cargo.toml new file mode 100644 index 0000000000..69df9d7d33 --- /dev/null +++ b/crates/params/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "strata-params" +version = "0.1.0" +edition = "2024" + +[dependencies] +strata-btc-types.workspace = true +strata-crypto.workspace = true +strata-identifiers.workspace = true +strata-l1-txfmt.workspace = true + +bincode.workspace = true +bitcoin.workspace = true +borsh.workspace = true +serde.workspace = true +thiserror.workspace = true + +[lints] +workspace = true diff --git a/crates/params/src/container.rs b/crates/params/src/container.rs new file mode 100644 index 0000000000..f7fdb17568 --- /dev/null +++ b/crates/params/src/container.rs @@ -0,0 +1,25 @@ +use bitcoin::Network; +use serde::{Deserialize, Serialize}; + +use crate::{rollup::RollupParams, sync::SyncParams}; + +/// Combined set of parameters across all the consensus logic. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Params { + pub rollup: RollupParams, + pub run: SyncParams, +} + +impl Params { + pub fn rollup(&self) -> &RollupParams { + &self.rollup + } + + pub fn run(&self) -> &SyncParams { + &self.run + } + + pub fn network(&self) -> Network { + self.rollup.network + } +} diff --git a/crates/params/src/lib.rs b/crates/params/src/lib.rs new file mode 100644 index 0000000000..d38158695e --- /dev/null +++ b/crates/params/src/lib.rs @@ -0,0 +1,11 @@ +//! Strata system parameters. +//! +//! This shouldn't be about params for *specific components*, but the general system. + +mod container; +mod rollup; +mod sync; + +pub use container::*; +pub use rollup::*; +pub use sync::*; diff --git a/crates/primitives/src/params.rs b/crates/params/src/rollup.rs similarity index 79% rename from crates/primitives/src/params.rs rename to crates/params/src/rollup.rs index 34618f7639..2d1b3185e3 100644 --- a/crates/primitives/src/params.rs +++ b/crates/params/src/rollup.rs @@ -1,21 +1,14 @@ //! Global consensus parameters for the rollup. -use bitcoin::{absolute, Amount, Network}; +use bitcoin::Network; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use strata_btc_types::{constants::TIMESTAMPS_FOR_MEDIAN, BitcoinAddress, OperatorPubkeys, XOnlyPk}; +use strata_crypto::proof_vk::RollupVerifyingKey; +use strata_identifiers::{hash, BitcoinAmount, Buf32, CredRule, L1BlockCommitment, L1BlockId, L1Height}; use strata_l1_txfmt::MagicBytes; use thiserror::Error; -use crate::{ - block_credential::CredRule, - constants::TIMESTAMPS_FOR_MEDIAN, - l1::{BitcoinAddress, BitcoinAmount, L1BlockCommitment, L1BlockId, XOnlyPk}, - operator::OperatorPubkeys, - prelude::Buf32, - proof::RollupVerifyingKey, - serde_helpers::serde_amount_sat, -}; - /// Consensus parameters that don't change for the lifetime of the network /// (unless there's some weird hard fork). #[derive(Clone, Debug, Deserialize, Serialize)] @@ -55,8 +48,7 @@ pub struct RollupParams { pub max_address_length: u8, /// Exact "at-rest" deposit amount, in sats. - #[serde(with = "serde_amount_sat")] - pub deposit_amount: Amount, + pub deposit_amount: BitcoinAmount, /// SP1 verifying key that is used to verify the Groth16 proof posted on Bitcoin // FIXME which proof? should this be `checkpoint_vk`? @@ -84,8 +76,8 @@ pub struct GenesisL1View { } impl GenesisL1View { - pub fn height(&self) -> absolute::Height { - self.blk.height() + pub fn height(&self) -> L1Height { + self.blk.height().to_consensus_u32() } pub fn height_u64(&self) -> u64 { @@ -124,7 +116,7 @@ impl RollupParams { return Err(ParamsError::ZeroProperty("max_address_length")); } - if self.deposit_amount == Amount::ZERO { + if self.deposit_amount == BitcoinAmount::ZERO { return Err(ParamsError::ZeroProperty("deposit_amount")); } @@ -141,7 +133,7 @@ impl RollupParams { pub fn compute_hash(&self) -> Buf32 { let raw_bytes = bincode::serialize(&self).expect("rollup params serialization failed"); - crate::hash::raw(&raw_bytes) + hash::raw(&raw_bytes) } pub fn rollup_vk(&self) -> &RollupVerifyingKey { @@ -185,42 +177,6 @@ impl ProofPublishMode { } } -/// Client sync parameters that are used to make the network work but don't -/// strictly have to be pre-agreed. These have to do with grace periods in -/// message delivery and whatnot. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SyncParams { - /// Number of blocks that we follow the L1 from. - pub l1_follow_distance: u64, - - /// Number of events after which we checkpoint the client - pub client_checkpoint_interval: u32, - - /// Max number of recent l2 blocks that can be fetched from RPC - pub l2_blocks_fetch_limit: u64, -} - -/// Combined set of parameters across all the consensus logic. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Params { - pub rollup: RollupParams, - pub run: SyncParams, -} - -impl Params { - pub fn rollup(&self) -> &RollupParams { - &self.rollup - } - - pub fn run(&self) -> &SyncParams { - &self.run - } - - pub fn network(&self) -> Network { - self.rollup.network - } -} - /// Describes how we determine the list of operators at genesis. // TODO improve how this looks when serialized #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] diff --git a/crates/params/src/sync.rs b/crates/params/src/sync.rs new file mode 100644 index 0000000000..67bd6b97c6 --- /dev/null +++ b/crates/params/src/sync.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Client sync parameters that are used to make the network work but don't +/// strictly have to be pre-agreed. These have to do with grace periods in +/// message delivery and whatnot. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SyncParams { + /// Number of blocks that we follow the L1 from. + pub l1_follow_distance: u64, + + /// Number of events after which we checkpoint the client + pub client_checkpoint_interval: u32, + + /// Max number of recent l2 blocks that can be fetched from RPC + pub l2_blocks_fetch_limit: u64, +} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index d38e4140f5..11c91ca90d 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -7,6 +7,12 @@ version = "0.3.0-alpha.1" workspace = true [dependencies] +strata-btc-types.workspace = true +strata-crypto.workspace = true +strata-identifiers.workspace = true +strata-l1-txfmt.workspace = true +strata-ol-chain-types.workspace = true + arbitrary.workspace = true bincode.workspace = true bitcoin = { workspace = true, features = ["serde", "rand-std"] } @@ -17,20 +23,15 @@ bitcoin-bosd = { workspace = true, features = [ "arbitrary", ] } borsh.workspace = true -const-hex = "1.14" -digest.workspace = true -hex.workspace = true +const-hex.workspace = true musig2 = { workspace = true, features = ["serde"] } num_enum.workspace = true rand = { workspace = true, optional = true } secp256k1 = { workspace = true, optional = true } serde.workspace = true sha2.workspace = true -strata-l1-txfmt.workspace = true thiserror.workspace = true zeroize.workspace = true -zkaleido-risc0-groth16-verifier.workspace = true -zkaleido-sp1-groth16-verifier.workspace = true [target.'cfg(target_os = "zkvm")'.dependencies] k256 = { version = "0.13.4", features = ["schnorr"] } @@ -43,4 +44,4 @@ serde_json.workspace = true [features] default = ["std", "rand"] rand = ["std", "dep:rand"] -std = ["dep:secp256k1"] +std = ["dep:secp256k1", "strata-crypto/std"] diff --git a/crates/primitives/src/bridge.rs b/crates/primitives/src/bridge.rs index 2293e4b02e..339c66f779 100644 --- a/crates/primitives/src/bridge.rs +++ b/crates/primitives/src/bridge.rs @@ -15,11 +15,12 @@ use musig2::{errors::KeyAggError, KeyAggContext, NonceSeed, PartialSignature, Pu use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; -use crate::{ - constants::{MUSIG2_PARTIAL_SIG_SIZE, NONCE_SEED_SIZE, PUB_NONCE_SIZE, SEC_NONCE_SIZE}, - l1::{BitcoinPsbt, TaprootSpendPath}, +use strata_crypto::multisig::constants::{ + MUSIG2_PARTIAL_SIG_SIZE, NONCE_SEED_SIZE, PUB_NONCE_SIZE, SEC_NONCE_SIZE, }; +use crate::l1::{BitcoinPsbt, TaprootSpendPath}; + /// The ID of an operator. /// /// We define it as a type alias over [`u32`] instead of a newtype because we perform a bunch of @@ -338,12 +339,13 @@ mod tests { }; use borsh::{BorshDeserialize, BorshSerialize}; - use super::{Musig2PubNonce, PublickeyTable}; - use crate::{ - bridge::{Musig2PartialSig, Musig2SecNonce}, - constants::{MUSIG2_PARTIAL_SIG_SIZE, PUB_NONCE_SIZE, SEC_NONCE_SIZE}, + use strata_crypto::multisig::constants::{ + MUSIG2_PARTIAL_SIG_SIZE, PUB_NONCE_SIZE, SEC_NONCE_SIZE, }; + use super::{Musig2PubNonce, PublickeyTable}; + use crate::bridge::{Musig2PartialSig, Musig2SecNonce}; + #[test] fn test_publickeytable_serialize_deserialize() { // Create a sample PublickeyTable diff --git a/crates/primitives/src/buf.rs b/crates/primitives/src/buf.rs index aa1327c154..cf87fd70cf 100644 --- a/crates/primitives/src/buf.rs +++ b/crates/primitives/src/buf.rs @@ -1,278 +1,2 @@ -use std::str::FromStr; - -use bitcoin::{ - hashes::Hash, - secp256k1::{schnorr, SecretKey, XOnlyPublicKey}, - BlockHash, Txid, Wtxid, -}; -use const_hex as hex; -use zeroize::Zeroize; - -use crate::{errors::ParseError, macros::internal}; - -/// A 20-byte buffer. -/// -/// # Warning -/// -/// This type is not zeroized on drop. -/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. -/// This is useful for secret data that needs to be zeroized after use. -/// -/// # Example -/// -/// ``` -/// # use strata_primitives::prelude::Buf20; -/// use zeroize::Zeroize; -/// -/// let mut buf = Buf20::from([1; 20]); -/// buf.zeroize(); -/// -/// assert_eq!(buf, Buf20::from([0; 20])); -/// ``` -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Buf20(pub [u8; 20]); -internal::impl_buf_common!(Buf20, 20); -internal::impl_buf_serde!(Buf20, 20); - -// NOTE: we cannot do `ZeroizeOnDrop` since `Buf20` is `Copy`. -impl Zeroize for Buf20 { - #[inline] - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -/// A 32-byte buffer. -/// -/// This is useful for hashes, transaction IDs, secret and public keys. -/// -/// # Warning -/// -/// This type is not zeroized on drop. -/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. -/// This is useful for secret data that needs to be zeroized after use. -/// -/// # Example -/// -/// ``` -/// # use strata_primitives::prelude::Buf32; -/// use zeroize::Zeroize; -/// -/// let mut buf = Buf32::from([1; 32]); -/// buf.zeroize(); -/// -/// assert_eq!(buf, Buf32::from([0; 32])); -/// ``` -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Buf32(pub [u8; 32]); -internal::impl_buf_common!(Buf32, 32); -internal::impl_buf_serde!(Buf32, 32); - -impl FromStr for Buf32 { - type Err = hex::FromHexError; - - fn from_str(s: &str) -> Result { - hex::decode_to_array(s).map(Self::new) - } -} - -impl From for Buf32 { - fn from(value: BlockHash) -> Self { - (*value.as_raw_hash().as_byte_array()).into() - } -} - -impl From for Buf32 { - fn from(value: Txid) -> Self { - let bytes: [u8; 32] = *value.as_raw_hash().as_byte_array(); - bytes.into() - } -} - -impl From<&Txid> for Buf32 { - fn from(value: &Txid) -> Self { - Self::from(*value) - } -} - -impl From for Txid { - fn from(value: Buf32) -> Self { - let mut bytes: [u8; 32] = [0; 32]; - bytes.copy_from_slice(value.0.as_slice()); - Txid::from_byte_array(bytes) - } -} - -impl From for Buf32 { - fn from(value: Wtxid) -> Self { - let bytes: [u8; 32] = *value.as_raw_hash().as_byte_array(); - bytes.into() - } -} - -impl From for Wtxid { - fn from(value: Buf32) -> Self { - let mut bytes: [u8; 32] = [0; 32]; - bytes.copy_from_slice(value.0.as_slice()); - Wtxid::from_byte_array(bytes) - } -} - -impl From for Buf32 { - fn from(value: SecretKey) -> Self { - let bytes: [u8; 32] = value.secret_bytes(); - bytes.into() - } -} - -impl From for SecretKey { - fn from(value: Buf32) -> Self { - SecretKey::from_slice(value.0.as_slice()).expect("could not convert Buf32 into SecretKey") - } -} - -impl TryFrom for XOnlyPublicKey { - type Error = ParseError; - - fn try_from(value: Buf32) -> Result { - XOnlyPublicKey::from_slice(&value.0).map_err(|_| ParseError::InvalidPoint(value)) - } -} - -impl From for Buf32 { - fn from(value: XOnlyPublicKey) -> Self { - Self::from(value.serialize()) - } -} - -// NOTE: we cannot do `ZeroizeOnDrop` since `Buf32` is `Copy`. -impl Zeroize for Buf32 { - #[inline] - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -/// A 64-byte buffer. -/// -/// This is useful for schnorr signatures. -/// -/// # Warning -/// -/// This type is not zeroized on drop. -/// However, it implements the [`Zeroize`] trait, so you can zeroize it manually. -/// This is useful for secret data that needs to be zeroized after use. -/// -/// # Example -/// -/// ``` -/// # use strata_primitives::prelude::Buf64; -/// use zeroize::Zeroize; -/// -/// let mut buf = Buf64::from([1; 64]); -/// buf.zeroize(); -/// -/// assert_eq!(buf, Buf64::from([0; 64])); -/// ``` -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Buf64(pub [u8; 64]); -internal::impl_buf_common!(Buf64, 64); -internal::impl_buf_serde!(Buf64, 64); - -impl From for Buf64 { - fn from(value: schnorr::Signature) -> Self { - value.serialize().into() - } -} - -// NOTE: we cannot do `ZeroizeOnDrop` since `Buf64` is `Copy`. -impl Zeroize for Buf64 { - #[inline] - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_buf32_deserialization() { - // without 0x - assert_eq!( - Buf32::from([0; 32]), - serde_json::from_str( - "\"0000000000000000000000000000000000000000000000000000000000000000\"", - ) - .unwrap() - ); - - // with 0x - assert_eq!( - Buf32::from([1; 32]), - serde_json::from_str( - "\"0x0101010101010101010101010101010101010101010101010101010101010101\"", - ) - .unwrap() - ); - - // correct byte order - assert_eq!( - Buf32::from([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 170u8 - ]), - serde_json::from_str( - "\"0x01010101010101010101010101010101010101010101010101010101010101aa\"", - ) - .unwrap() - ); - } - - #[test] - fn test_buf32_serialization() { - assert_eq!( - serde_json::to_string(&Buf32::from([0; 32])).unwrap(), - String::from("\"0000000000000000000000000000000000000000000000000000000000000000\"") - ); - - assert_eq!( - serde_json::to_string(&Buf32::from([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 170u8 - ])) - .unwrap(), - String::from("\"01010101010101010101010101010101010101010101010101010101010101aa\"") - ); - } - - #[test] - fn test_zeroize() { - let mut buf20 = Buf20::from([1; 20]); - let mut buf32 = Buf32::from([1; 32]); - let mut buf64 = Buf64::from([1; 64]); - buf20.zeroize(); - buf32.zeroize(); - buf64.zeroize(); - assert_eq!(buf20, Buf20::from([0; 20])); - assert_eq!(buf32, Buf32::from([0; 32])); - assert_eq!(buf64, Buf64::from([0; 64])); - } - - #[test] - fn test_buf32_parse() { - "0x37ad61cff1367467a98cf7c54c4ac99e989f1fbb1bc1e646235e90c065c565ba" - .parse::() - .unwrap(); - } - - #[test] - fn test_buf32_from_str() { - Buf32::from_str("a9f913c3d7fe56c462228ad22bb7631742a121a6a138d57c1fc4a351314948fa") - .unwrap(); - - Buf32::from_str("81060cb3997dcefc463e3db0a776efb5360e458064a666459b8807f60c0201c2") - .unwrap(); - } -} +// TODO update these imports +pub use strata_identifiers::{Buf20, Buf32, Buf64}; diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index 1936666b93..ebe197ccc8 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -1,36 +1,19 @@ //! Constants for magic numbers and strings used in the primitives. +// Re-export BTC constants +pub use strata_btc_types::constants::*; + use std::sync::LazyLock; use bitcoin::{bip32::ChildNumber, XOnlyPublicKey}; use secp256k1::hashes::{sha256, Hash}; -/// The size (in bytes) of a [`musig2::PartialSignature`]. -pub const MUSIG2_PARTIAL_SIG_SIZE: usize = 32; - -/// The size (in bytes) of a [`musig2::NonceSeed`]. -pub const NONCE_SEED_SIZE: usize = 32; - -/// The size (in bytes) of a [`musig2::PubNonce`]. -pub const PUB_NONCE_SIZE: usize = 66; - -/// The size (in bytes) of a [`musig2::SecNonce`]. -pub const SEC_NONCE_SIZE: usize = 64; - -/// The size (in bytes) of a Hash (such as [`Txid`](bitcoin::Txid)). -pub const HASH_SIZE: usize = 32; - /// The size (in bytes) Execution environment Address. pub const EE_ADDRESS_LEN: u8 = 20; /// Number of blocks after bridge in transaction confirmation that the recovery path can be spent. pub const RECOVER_DELAY: u32 = 1_008; -/// The number of timestamps used for calculating the median in Bitcoin header verification. -/// According to Bitcoin consensus rules, we need to check that a block's timestamp -/// is not lower than the median of the last eleven blocks' timestamps. -pub const TIMESTAMPS_FOR_MEDIAN: usize = 11; - /// Strata base index for keys. /// /// # Implementation Details diff --git a/crates/primitives/src/epoch.rs b/crates/primitives/src/epoch.rs index 91f894a159..71ee85b939 100644 --- a/crates/primitives/src/epoch.rs +++ b/crates/primitives/src/epoch.rs @@ -1,127 +1 @@ -//! Types relating to epoch bookkeeping. -//! -//! An epoch of a range of sequential blocks defined by the terminal block of -//! the epoch going back to (but not including) the terminal block of a previous -//! epoch. This uniquely identifies the epoch's final state indirectly, -//! although it's possible for conflicting epochs with different terminal blocks -//! to exist in theory, depending on the consensus algorithm. -//! -//! Epochs are *usually* always the same number of slots, but we're not -//! guaranteeing this yet, so we always include both the epoch number and slot -//! number of the terminal block. -//! -//! We also have a sentinel "null" epoch used to refer to the "finalized epoch" -//! as of the genesis block. - -use std::fmt; - -use arbitrary::Arbitrary; -use borsh::{BorshDeserialize, BorshSerialize}; -use const_hex as hex; -use serde::{Deserialize, Serialize}; - -use crate::{ - buf::Buf32, - l2::{L2BlockCommitment, L2BlockId}, -}; - -/// Commits to a particular epoch by the last block and slot. -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Arbitrary, - BorshDeserialize, - BorshSerialize, - Deserialize, - Serialize, -)] -pub struct EpochCommitment { - epoch: u64, - last_slot: u64, - last_blkid: L2BlockId, -} - -impl EpochCommitment { - pub fn new(epoch: u64, last_slot: u64, last_blkid: L2BlockId) -> Self { - Self { - epoch, - last_slot, - last_blkid, - } - } - - /// Creates a new instance given the terminal block of an epoch and the - /// epoch index. - pub fn from_terminal(epoch: u64, block: L2BlockCommitment) -> Self { - Self::new(epoch, block.slot(), *block.blkid()) - } - - /// Creates a "null" epoch with 0 slot, epoch 0, and zeroed blkid. - pub fn null() -> Self { - Self::new(0, 0, L2BlockId::from(Buf32::zero())) - } - - pub fn epoch(&self) -> u64 { - self.epoch - } - - pub fn last_slot(&self) -> u64 { - self.last_slot - } - - pub fn last_blkid(&self) -> &L2BlockId { - &self.last_blkid - } - - /// Returns a [`L2BlockCommitment`] for the final block of the epoch. - pub fn to_block_commitment(&self) -> L2BlockCommitment { - L2BlockCommitment::new(self.last_slot, self.last_blkid) - } - - /// Returns if the terminal blkid is zero. This signifies a special case - /// for the genesis epoch (0) before the it is completed. - pub fn is_null(&self) -> bool { - Buf32::from(self.last_blkid).is_zero() - } -} - -impl fmt::Display for EpochCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Show first 2 and last 2 bytes of block ID (4 hex chars each) - let blkid_bytes = self.last_blkid.as_ref(); - let first_2 = &blkid_bytes[..2]; - let last_2 = &blkid_bytes[30..]; - - let mut first_hex = [0u8; 4]; - let mut last_hex = [0u8; 4]; - hex::encode_to_slice(first_2, &mut first_hex) - .expect("Failed to encode first 2 bytes to hex"); - hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); - - write!( - f, - "{}[{}]@{}..{}", - self.last_slot, - self.epoch, - std::str::from_utf8(&first_hex) - .expect("Failed to convert first 2 hex bytes to UTF-8 string"), - std::str::from_utf8(&last_hex) - .expect("Failed to convert last 2 hex bytes to UTF-8 string") - ) - } -} - -impl fmt::Debug for EpochCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "EpochCommitment(epoch={}, last_slot={}, last_blkid={:?})", - self.epoch, self.last_slot, self.last_blkid - ) - } -} +pub use strata_identifiers::EpochCommitment; diff --git a/crates/primitives/src/errors.rs b/crates/primitives/src/errors.rs index a34be9ffbc..915e053192 100644 --- a/crates/primitives/src/errors.rs +++ b/crates/primitives/src/errors.rs @@ -34,3 +34,13 @@ pub enum ParseError { #[error("descriptor: {0}")] Descriptor(String), } + +impl From for ParseError { + fn from(value: strata_identifiers::ParseError) -> Self { + match value { + strata_identifiers::ParseError::InvalidPubkey(e) => Self::InvalidPubkey(e), + strata_identifiers::ParseError::InvalidPoint(buf) => Self::InvalidPoint(buf), + strata_identifiers::ParseError::UnsupportedAddress(ty) => Self::UnsupportedAddress(ty), + } + } +} diff --git a/crates/primitives/src/evm_exec.rs b/crates/primitives/src/evm_exec.rs index 30427c7aa1..fe3527ceb8 100644 --- a/crates/primitives/src/evm_exec.rs +++ b/crates/primitives/src/evm_exec.rs @@ -28,44 +28,5 @@ pub fn create_evm_extra_payload(block_hash: Buf32) -> Vec { borsh::to_vec(&extra_payload).expect("extra_payload vec") } -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Arbitrary, - BorshDeserialize, - BorshSerialize, - Deserialize, - Serialize, -)] -pub struct EvmEeBlockCommitment { - slot: u64, - blkid: Buf32, -} - -impl EvmEeBlockCommitment { - pub fn new(slot: u64, blkid: Buf32) -> Self { - Self { slot, blkid } - } - - pub fn null() -> Self { - Self::new(0, Buf32::zero()) - } - - pub fn slot(&self) -> u64 { - self.slot - } - - pub fn blkid(&self) -> &Buf32 { - &self.blkid - } - - pub fn is_null(&self) -> bool { - self.slot == 0 && self.blkid().is_zero() - } -} +// TODO remove this reexport +pub type EvmEeBlockCommitment = strata_identifiers::ExecBlockCommitment; diff --git a/crates/primitives/src/hash.rs b/crates/primitives/src/hash.rs index f97d17fe7a..3a2cd9e86b 100644 --- a/crates/primitives/src/hash.rs +++ b/crates/primitives/src/hash.rs @@ -1,58 +1,3 @@ //! Common wrapper around whatever we choose our native hash function to be. -use borsh::BorshSerialize; -use digest::Digest; -use sha2::Sha256; - -use crate::buf::Buf32; - -/// Direct untagged hash. -pub fn raw(buf: &[u8]) -> Buf32 { - Buf32::from(<[u8; 32]>::from(Sha256::digest(buf))) -} - -pub fn compute_borsh_hash(v: &T) -> Buf32 { - let mut hasher = Sha256::new(); - v.serialize(&mut hasher).expect("Serialization failed"); - let result = hasher.finalize(); - let arr: [u8; 32] = result.into(); - Buf32::from(arr) -} - -/// Implements a double SHA256 (`Sha256d`) hashing function using [RustCrypto's SHA-2 crate](https://github.com/RustCrypto/hashes/tree/master/sha2). -/// -/// This implementation is designed to be equivalent to the one found in the -/// [`bitcoin_hashes` crate](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/hashes/src/sha256d.rs) -/// but is built upon the [RustCrypto's SHA-2 crate](https://github.com/RustCrypto/hashes/tree/master/sha2), -/// because it has patches available from both the -/// [Risc0](https://github.com/risc0/RustCrypto-hashes) -/// and [Sp1](https://github.com/sp1-patches/RustCrypto-hashes) -/// crates. -pub fn sha256d(buf: &[u8]) -> Buf32 { - let mut hasher = Sha256::new(); - hasher.update(buf); - let result = hasher.finalize_reset(); - hasher.update(result); - let arr: [u8; 32] = hasher.finalize().into(); - Buf32::from(arr) -} - -#[cfg(test)] -mod tests { - use bitcoin::hashes::{sha256d, Hash}; - use rand::{rngs::OsRng, RngCore}; - - use super::sha256d; - use crate::buf::Buf32; - - #[test] - fn test_sha256d_equivalence() { - let mut array = [0u8; 32]; - OsRng.fill_bytes(&mut array); - - let expected = Buf32::from(sha256d::Hash::hash(&array).to_byte_array()); - let output = sha256d(&array); - - assert_eq!(expected, output); - } -} +pub use strata_identifiers::hash::{compute_borsh_hash, raw, sha256d}; diff --git a/crates/primitives/src/l1/block.rs b/crates/primitives/src/l1/block.rs index 5230c2b13c..8981323b72 100644 --- a/crates/primitives/src/l1/block.rs +++ b/crates/primitives/src/l1/block.rs @@ -1,307 +1,2 @@ -use std::{fmt, io, str}; - -use arbitrary::{Arbitrary, Error as ArbitraryError, Result as ArbitraryResult, Unstructured}; -use bitcoin::{absolute, hashes::Hash, BlockHash}; -use borsh::{BorshDeserialize, BorshSerialize}; -use const_hex as hex; -use hex::encode_to_slice; -use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::{buf::Buf32, hash::sha256d}; - -/// ID of an L1 block, usually the hash of its header. -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Default, - Arbitrary, - BorshSerialize, - BorshDeserialize, - Deserialize, - Serialize, -)] -pub struct L1BlockId(Buf32); - -impl L1BlockId { - /// Computes the [`L1BlockId`] from the header buf. This is expensive in proofs and - /// should only be done when necessary. - pub fn compute_from_header_buf(buf: &[u8]) -> L1BlockId { - Self::from(sha256d(buf)) - } -} - -// Custom implementation without Debug/Display to avoid conflicts -impl From for L1BlockId { - fn from(value: Buf32) -> Self { - Self(value) - } -} - -impl From for Buf32 { - fn from(value: L1BlockId) -> Self { - value.0 - } -} - -impl AsRef<[u8; 32]> for L1BlockId { - fn as_ref(&self) -> &[u8; 32] { - self.0.as_ref() - } -} - -impl From for L1BlockId { - fn from(value: BlockHash) -> Self { - L1BlockId(value.into()) - } -} - -impl From for BlockHash { - fn from(value: L1BlockId) -> Self { - BlockHash::from_byte_array(value.0.into()) - } -} - -/// L1 Block commitment with block height and ID. -/// -/// Note: Height is stored as u32 internally in Bitcoin's consensus format, -/// but we serialize/deserialize using a custom implementation to maintain -/// backwards compatibility with existing data (stored as u64). -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Arbitrary)] -pub struct L1BlockCommitment { - #[arbitrary(with = arbitrary_height)] - height: absolute::Height, - blkid: L1BlockId, -} - -// Custom Arbitrary implementation for Height -fn arbitrary_height(u: &mut Unstructured<'_>) -> ArbitraryResult { - // Heights must be less than 500_000_000 (LOCK_TIME_THRESHOLD) - let h = u32::arbitrary(u)? % 500_000_000; - absolute::Height::from_consensus(h).map_err(|_| ArbitraryError::IncorrectFormat) -} - -// Custom Borsh serialization to maintain backward compatibility with u64 storage -impl BorshSerialize for L1BlockCommitment { - fn serialize(&self, writer: &mut W) -> io::Result<()> { - // Serialize height as u64 for backward compatibility - let height_u64 = self.height.to_consensus_u32() as u64; - BorshSerialize::serialize(&height_u64, writer)?; - BorshSerialize::serialize(&self.blkid, writer)?; - Ok(()) - } -} - -impl BorshDeserialize for L1BlockCommitment { - fn deserialize_reader(reader: &mut R) -> io::Result { - let height_u64 = u64::deserialize_reader(reader)?; - let height = absolute::Height::from_consensus(height_u64 as u32).map_err(|e| { - io::Error::new( - io::ErrorKind::InvalidData, - format!("invalid block height {height_u64}: {e}"), - ) - })?; - let blkid = L1BlockId::deserialize_reader(reader)?; - Ok(Self { height, blkid }) - } -} - -// Custom serde implementation to maintain backward compatibility with u64 JSON -impl Serialize for L1BlockCommitment { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - use ser::SerializeStruct; - let mut state = serializer.serialize_struct("L1BlockCommitment", 2)?; - let height_u64 = self.height.to_consensus_u32() as u64; - state.serialize_field("height", &height_u64)?; - state.serialize_field("blkid", &self.blkid)?; - state.end() - } -} - -impl<'de> Deserialize<'de> for L1BlockCommitment { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct L1BlockCommitmentVisitor; - - impl<'de> de::Visitor<'de> for L1BlockCommitmentVisitor { - type Value = L1BlockCommitment; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("struct L1BlockCommitment or tuple (u64, L1BlockId)") - } - - // Support struct format (JSON, human-readable formats) - fn visit_map(self, mut map: V) -> Result - where - V: de::MapAccess<'de>, - { - let mut height: Option = None; - let mut blkid: Option = None; - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "height" => { - height = Some(map.next_value()?); - } - "blkid" => { - blkid = Some(map.next_value()?); - } - _ => { - let _: de::IgnoredAny = map.next_value()?; - } - } - } - - let height = height.ok_or_else(|| de::Error::missing_field("height"))?; - let blkid = blkid.ok_or_else(|| de::Error::missing_field("blkid"))?; - - let height = absolute::Height::from_consensus(height as u32).map_err(|e| { - de::Error::custom(format!("invalid block height {}: {}", height, e)) - })?; - - Ok(L1BlockCommitment { height, blkid }) - } - - // Support tuple format (bincode, compact binary formats) - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - let height_u64: u64 = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(0, &self))?; - let blkid: L1BlockId = seq - .next_element()? - .ok_or_else(|| de::Error::invalid_length(1, &self))?; - - let height = absolute::Height::from_consensus(height_u64 as u32).map_err(|e| { - de::Error::custom(format!("invalid block height {}: {}", height_u64, e)) - })?; - - Ok(L1BlockCommitment { height, blkid }) - } - } - - // For human-readable formats (JSON), use deserialize_any to support both struct and tuple - // For binary formats (bincode), use deserialize_tuple for backward compatibility - if deserializer.is_human_readable() { - deserializer.deserialize_any(L1BlockCommitmentVisitor) - } else { - // Bincode doesn't support deserialize_any, so we use deserialize_tuple - deserializer.deserialize_tuple(2, L1BlockCommitmentVisitor) - } - } -} - -impl Default for L1BlockCommitment { - fn default() -> Self { - Self { - height: absolute::Height::ZERO, - blkid: L1BlockId::default(), - } - } -} - -impl fmt::Display for L1BlockCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Show first 2 and last 2 bytes of block ID (4 hex chars each) - let blkid_bytes = self.blkid.as_ref(); - let first_2 = &blkid_bytes[..2]; - let last_2 = &blkid_bytes[30..]; - - let mut first_hex = [0u8; 4]; - let mut last_hex = [0u8; 4]; - hex::encode_to_slice(first_2, &mut first_hex) - .expect("Failed to encode first 2 bytes to hex"); - hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); - - write!( - f, - "{}@{}..{}", - self.height.to_consensus_u32(), - str::from_utf8(&first_hex) - .expect("Failed to convert first 2 hex bytes to UTF-8 string"), - str::from_utf8(&last_hex).expect("Failed to convert last 2 hex bytes to UTF-8 string") - ) - } -} - -impl fmt::Debug for L1BlockCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "L1BlockCommitment(height={}, blkid={:?})", - self.height.to_consensus_u32(), - self.blkid - ) - } -} - -impl L1BlockCommitment { - /// Create a new L1 block commitment. - /// - /// # Arguments - /// * `height` - The block height - /// * `blkid` - The block ID - pub fn new(height: absolute::Height, blkid: L1BlockId) -> Self { - Self { height, blkid } - } - - /// Create a new L1 block commitment from a u64 height. - /// - /// Returns `None` if the height is invalid (greater than u32::MAX). - pub fn from_height_u64(height: u64, blkid: L1BlockId) -> Option { - let height = absolute::Height::from_consensus(height as u32).ok()?; - Some(Self { height, blkid }) - } - - /// Get the block height. - pub fn height(&self) -> absolute::Height { - self.height - } - - /// Get the block height as u64 for compatibility. - pub fn height_u64(&self) -> u64 { - self.height.to_consensus_u32() as u64 - } - - /// Get the block ID. - pub fn blkid(&self) -> &L1BlockId { - &self.blkid - } -} - -// Custom debug implementation to print the block hash in little endian -impl fmt::Debug for L1BlockId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut bytes = self.0 .0; - bytes.reverse(); - let mut buf = [0u8; 64]; // 32 bytes * 2 for hex - encode_to_slice(bytes, &mut buf).expect("buf: enc hex"); - // SAFETY: hex encoding always produces valid UTF-8 - let hex_str = unsafe { str::from_utf8_unchecked(&buf) }; - f.write_str(hex_str) - } -} - -// Custom display implementation to print the block hash in little endian -impl fmt::Display for L1BlockId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut bytes = self.0 .0; - bytes.reverse(); - let mut buf = [0u8; 64]; // 32 bytes * 2 for hex - encode_to_slice(bytes, &mut buf).expect("buf: enc hex"); - // SAFETY: hex encoding always produces valid UTF-8 - let hex_str = unsafe { str::from_utf8_unchecked(&buf) }; - f.write_str(hex_str) - } -} +// TODO update these imports +pub use strata_identifiers::{L1BlockCommitment, L1BlockId, L1Height as Height}; diff --git a/crates/primitives/src/l1/btc.rs b/crates/primitives/src/l1/btc.rs index bca87f9599..0c966fd8e3 100644 --- a/crates/primitives/src/l1/btc.rs +++ b/crates/primitives/src/l1/btc.rs @@ -1,990 +1,11 @@ -use std::{ - fmt::{self, Debug, Display}, - io::{self, Read, Write}, - str, +// Re-export Bitcoin types from strata-btc-types +pub use strata_btc_types::{ + BitcoinAddress, BitcoinPsbt, BitcoinScriptBuf, BitcoinTxOut, BitcoinTxid, Outpoint, + TaprootSpendPath, XOnlyPk, }; - -use arbitrary::{Arbitrary, Unstructured}; -use bitcoin::{ - absolute::LockTime, - address::NetworkUnchecked, - consensus::{deserialize, encode, serialize}, - hashes::{sha256d, Hash}, - key::{rand, Keypair, Parity, TapTweak}, - secp256k1::{SecretKey, XOnlyPublicKey, SECP256K1}, - taproot::{ControlBlock, LeafVersion, TaprootMerkleBranch}, - transaction::Version, - Address, AddressType, Amount, Network, OutPoint, Psbt, ScriptBuf, Sequence, TapNodeHash, - Transaction, TxIn, TxOut, Txid, Witness, -}; -use bitcoin_bosd::Descriptor; -use borsh::{BorshDeserialize, BorshSerialize}; -use hex::encode_to_slice; -use rand::rngs::OsRng; -use serde::{de, Deserialize, Deserializer, Serialize}; - -use crate::{buf::Buf32, constants::HASH_SIZE, errors::ParseError}; - -/// L1 output reference. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct OutputRef(pub OutPoint); - -impl From for OutputRef { - fn from(value: OutPoint) -> Self { - Self(value) - } -} - -impl OutputRef { - pub fn new(txid: Txid, vout: u32) -> Self { - Self(OutPoint::new(txid, vout)) - } - - pub fn outpoint(&self) -> &OutPoint { - &self.0 - } -} - -// Implement BorshSerialize for the OutputRef wrapper. -impl BorshSerialize for OutputRef { - fn serialize(&self, writer: &mut W) -> Result<(), io::Error> { - // Serialize the transaction ID as bytes - writer.write_all(&self.0.txid[..])?; - - // Serialize the output index as a little-endian 4-byte integer - writer.write_all(&self.0.vout.to_le_bytes())?; - Ok(()) - } -} - -// Implement BorshDeserialize for the OutputRef wrapper. -impl BorshDeserialize for OutputRef { - fn deserialize_reader(reader: &mut R) -> Result { - // Read 32 bytes for the transaction ID - let mut txid_bytes = [0u8; HASH_SIZE]; - reader.read_exact(&mut txid_bytes)?; - let txid = bitcoin::Txid::from_slice(&txid_bytes).expect("should be a valid txid (hash)"); - - // Read 4 bytes for the output index - let mut vout_bytes = [0u8; 4]; - reader.read_exact(&mut vout_bytes)?; - let vout = u32::from_le_bytes(vout_bytes); - - Ok(OutputRef(OutPoint { txid, vout })) - } -} - -// Implement Arbitrary for the wrapper -impl<'a> Arbitrary<'a> for OutputRef { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - // Generate a random 32-byte array for the transaction ID (txid) - let mut txid_bytes = [0u8; HASH_SIZE]; - u.fill_buffer(&mut txid_bytes)?; - let txid_bytes = &txid_bytes[..]; - let hash = sha256d::Hash::from_slice(txid_bytes).unwrap(); - let txid = bitcoin::Txid::from_slice(&hash[..]).unwrap(); - - // Generate a random 4-byte integer for the output index (vout) - let vout = u.int_in_range(0..=u32::MAX)?; - - Ok(OutputRef(OutPoint { txid, vout })) - } -} -/// A wrapper around the [`bitcoin::Address`] type. -/// -/// It's created in order to implement some useful traits on it such as -/// [`serde::Deserialize`], [`borsh::BorshSerialize`] and [`borsh::BorshDeserialize`]. -// TODO: implement [`arbitrary::Arbitrary`]? -#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] -pub struct BitcoinAddress { - /// The [`bitcoin::Network`] that this address is valid in. - network: Network, - - /// The actual [`Address`] that this type wraps. - address: Address, -} - -impl BitcoinAddress { - /// Parses a [`BitcoinAddress`] from a string. - pub fn parse(address_str: &str, network: Network) -> Result { - let address = address_str - .parse::>() - .map_err(ParseError::InvalidAddress)?; - - let checked_address = address - .require_network(network) - .map_err(ParseError::InvalidAddress)?; - - Ok(Self { - network, - address: checked_address, - }) - } - - /// Parses a [`BitcoinAddress`] from raw bytes representation of a bitcoin Script. - pub fn from_bytes(bytes: &[u8], network: Network) -> Result { - let script_buf = ScriptBuf::from_bytes(bytes.to_vec()); - let address = Address::from_script(&script_buf, network)?; - Ok(Self { network, address }) - } - - pub fn from_descriptor(descriptor: &Descriptor, network: Network) -> Result { - let address = descriptor - .to_address(network) - .map_err(|err| ParseError::Descriptor(err.to_string()))?; - Ok(Self { network, address }) - } -} - -impl BitcoinAddress { - pub fn address(&self) -> &Address { - &self.address - } - - pub fn network(&self) -> &Network { - &self.network - } -} - -impl<'de> Deserialize<'de> for BitcoinAddress { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct BitcoinAddressShim { - network: Network, - address: String, - } - - let shim = BitcoinAddressShim::deserialize(deserializer)?; - let address = shim - .address - .parse::>() - .map_err(|_| de::Error::custom("invalid bitcoin address"))? - .require_network(shim.network) - .map_err(|_| de::Error::custom("address invalid for given network"))?; - - Ok(BitcoinAddress { - network: shim.network, - address, - }) - } -} - -impl BorshSerialize for BitcoinAddress { - fn serialize(&self, writer: &mut W) -> Result<(), io::Error> { - let address_string = self.address.to_string(); - - BorshSerialize::serialize(address_string.as_str(), writer)?; - - let network_byte = match self.network { - Network::Bitcoin => 0u8, - Network::Testnet => 1u8, - Network::Signet => 2u8, - Network::Regtest => 3u8, - other => unreachable!("should handle new variant: {}", other), - }; - - BorshSerialize::serialize(&network_byte, writer)?; - - Ok(()) - } -} - -impl BorshDeserialize for BitcoinAddress { - fn deserialize_reader(reader: &mut R) -> Result { - let address_str = String::deserialize_reader(reader)?; - - let network_byte = u8::deserialize_reader(reader)?; - let network = match network_byte { - 0u8 => Network::Bitcoin, - 1u8 => Network::Testnet, - 2u8 => Network::Signet, - 3u8 => Network::Regtest, - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid network byte: {network_byte}"), - )); - } - }; - - let address = address_str - .parse::>() - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid bitcoin address"))? - .require_network(network) - .map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - "address invalid for given network", - ) - })?; - - Ok(BitcoinAddress { address, network }) - } -} - -/// A wrapper for bitcoin amount in sats similar to the implementation in [`bitcoin::Amount`]. -/// -/// NOTE: This wrapper has been created so that we can implement `Borsh*` traits on it. -#[derive( - Arbitrary, - BorshSerialize, - BorshDeserialize, - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - PartialEq, - Serialize, -)] -pub struct BitcoinAmount(u64); - -impl Display for BitcoinAmount { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for BitcoinAmount { - fn from(value: Amount) -> Self { - Self::from_sat(value.to_sat()) - } -} - -impl From for Amount { - fn from(value: BitcoinAmount) -> Self { - Self::from_sat(value.to_sat()) - } -} - -impl BitcoinAmount { - // The zero amount. - pub const ZERO: BitcoinAmount = Self(0); - /// The maximum value allowed as an amount. Useful for sanity checking. - pub const MAX_MONEY: BitcoinAmount = Self::from_int_btc(21_000_000); - /// The minimum value of an amount. - pub const MIN: BitcoinAmount = Self::ZERO; - /// The maximum value of an amount. - pub const MAX: BitcoinAmount = Self(u64::MAX); - /// The number of bytes that an amount contributes to the size of a transaction. - /// Serialized length of a u64. - pub const SIZE: usize = 8; - - /// The number of sats in 1 bitcoin. - pub const SATS_FACTOR: u64 = 100_000_000; - - /// Get the number of sats in this [`BitcoinAmount`]. - pub fn to_sat(&self) -> u64 { - self.0 - } - - /// Create a [`BitcoinAmount`] with sats precision and the given number of sats. - pub const fn from_sat(value: u64) -> Self { - Self(value) - } - - /// Convert from a value strataing integer values of bitcoins to a [`BitcoinAmount`] - /// in const context. - /// - /// ## Panics - /// - /// The function panics if the argument multiplied by the number of sats - /// per bitcoin overflows a u64 type, or is greater than [`BitcoinAmount::MAX_MONEY`]. - pub const fn from_int_btc(btc: u64) -> Self { - match btc.checked_mul(Self::SATS_FACTOR) { - Some(amount) => Self::from_sat(amount), - None => { - panic!("number of sats greater than u64::MAX"); - } - } - } - - /// Checked addition. Returns [`None`] if overflow occurred. - pub fn checked_add(self, rhs: Self) -> Option { - self.0.checked_add(rhs.0).map(Self::from_sat) - } - - /// Checked subtraction. Returns [`None`] if overflow occurred. - pub fn checked_sub(self, rhs: Self) -> Option { - self.0.checked_sub(rhs.0).map(Self::from_sat) - } - - /// Checked multiplication. Returns [`None`] if overflow occurred. - pub fn checked_mul(self, rhs: u64) -> Option { - self.0.checked_mul(rhs).map(Self::from_sat) - } - - /// Checked division. Returns [`None`] if `rhs == 0`. - pub fn checked_div(self, rhs: u64) -> Option { - self.0.checked_div(rhs).map(Self::from_sat) - } - - /// Saturating subtraction. Computes `self - rhs`, returning [`Self::ZERO`] if overflow - /// occurred. - pub fn saturating_sub(self, rhs: Self) -> Self { - Self::from_sat(self.to_sat().saturating_sub(rhs.to_sat())) - } - - /// Saturating addition. Computes `self + rhs`, saturating at the numeric bounds. - pub fn saturating_add(self, rhs: Self) -> Self { - Self::from_sat(self.to_sat().saturating_add(rhs.to_sat())) - } -} - -/// [Borsh](borsh)-friendly Bitcoin [`Psbt`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct BitcoinPsbt(Psbt); - -impl BitcoinPsbt { - pub fn inner(&self) -> &Psbt { - &self.0 - } - - pub fn compute_txid(&self) -> Txid { - self.0.unsigned_tx.compute_txid() - } -} - -impl From for BitcoinPsbt { - fn from(value: Psbt) -> Self { - Self(value) - } -} - -impl From for Psbt { - fn from(value: BitcoinPsbt) -> Self { - value.0 - } -} - -impl BorshSerialize for BitcoinPsbt { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - // Serialize the PSBT using bitcoin's built-in serialization - let psbt_bytes = self.0.serialize(); - // First, write the length of the serialized PSBT (as u32) - BorshSerialize::serialize(&(psbt_bytes.len() as u32), writer)?; - // Then, write the actual serialized PSBT bytes - writer.write_all(&psbt_bytes)?; - Ok(()) - } -} - -impl BorshDeserialize for BitcoinPsbt { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - // First, read the length of the PSBT (as u32) - let len = u32::deserialize_reader(reader)? as usize; - // Then, create a buffer to hold the PSBT bytes and read them - let mut psbt_bytes = vec![0u8; len]; - reader.read_exact(&mut psbt_bytes)?; - // Use the bitcoin crate's deserialize method to create a Psbt from the bytes - let psbt = Psbt::deserialize(&psbt_bytes).map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid PSBT data") - })?; - Ok(BitcoinPsbt(psbt)) - } -} - -impl<'a> Arbitrary<'a> for BitcoinPsbt { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - let num_outputs = u.arbitrary_len::<[u8; 32]>()? % 5; - let mut output: Vec = vec![]; - - for _ in 0..num_outputs { - let txout = BitcoinTxOut::arbitrary(u)?; - let txout = TxOut::from(txout); - - output.push(txout); - } - - let tx = Transaction { - version: Version(1), - lock_time: LockTime::from_consensus(0), - input: vec![TxIn { - previous_output: OutPoint::null(), - witness: Witness::new(), - sequence: Sequence(0), - script_sig: ScriptBuf::new(), - }], - output, - }; - - let psbt = Psbt::from_unsigned_tx(tx).map_err(|_e| arbitrary::Error::IncorrectFormat)?; - let psbt = BitcoinPsbt::from(psbt); - - Ok(psbt) - } -} - -/// [Borsh](borsh)-friendly Bitcoin [`Txid`]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BitcoinTxid(Txid); - -impl From for BitcoinTxid { - fn from(value: Txid) -> Self { - Self(value) - } -} - -impl From for Txid { - fn from(value: BitcoinTxid) -> Self { - value.0 - } -} - -impl BitcoinTxid { - /// Creates a new [`BitcoinTxid`] from a [`Txid`]. - /// - /// # Notes - /// - /// [`Txid`] is [`Copy`]. - pub fn new(txid: &Txid) -> Self { - BitcoinTxid(*txid) - } - - /// Gets the inner Bitcoin [`Txid`] - pub fn inner(&self) -> Txid { - self.0 - } - - /// Gets the inner Bitcoin [`Txid`] as raw bytes [`Buf32`]. - pub fn inner_raw(&self) -> Buf32 { - self.0.as_raw_hash().to_byte_array().into() - } -} - -impl BorshSerialize for BitcoinTxid { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - // Serialize the txid using bitcoin's built-in serialization - let txid_bytes = self.0.to_byte_array(); - // First, write the length of the serialized txid (as u32) - BorshSerialize::serialize(&(32_u32), writer)?; - // Then, write the actual serialized PSBT bytes - writer.write_all(&txid_bytes)?; - Ok(()) - } -} - -impl BorshDeserialize for BitcoinTxid { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - // First, read the length tag - let len = u32::deserialize_reader(reader)? as usize; - - if len != HASH_SIZE { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("Invalid Txid size, expected: {HASH_SIZE}, got: {len}"), - )); - } - - // First, create a buffer to hold the txid bytes and read them - let mut txid_bytes = [0u8; HASH_SIZE]; - reader.read_exact(&mut txid_bytes)?; - // Use the bitcoin crate's deserialize method to create a Psbt from the bytes - let txid = Txid::from_byte_array(txid_bytes); - Ok(BitcoinTxid(txid)) - } -} - -impl<'a> Arbitrary<'a> for BitcoinTxid { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - let value = Buf32::arbitrary(u)?; - let txid = Txid::from(value); - - Ok(Self(txid)) - } -} - -/// A wrapper around [`bitcoin::TxOut`] that implements some additional traits. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct BitcoinTxOut(TxOut); - -impl BitcoinTxOut { - pub fn inner(&self) -> &TxOut { - &self.0 - } -} - -impl From for BitcoinTxOut { - fn from(value: TxOut) -> Self { - Self(value) - } -} - -impl From for TxOut { - fn from(value: BitcoinTxOut) -> Self { - value.0 - } -} - -// Implement BorshSerialize for BitcoinTxOut -impl BorshSerialize for BitcoinTxOut { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - // Serialize the value (u64) - BorshSerialize::serialize(&self.0.value.to_sat(), writer)?; - - // Serialize the script_pubkey (ScriptBuf) - let script_bytes = self.0.script_pubkey.to_bytes(); - BorshSerialize::serialize(&(script_bytes.len() as u64), writer)?; - writer.write_all(&script_bytes)?; - - Ok(()) - } -} - -// Implement BorshDeserialize for BitcoinTxOut -impl BorshDeserialize for BitcoinTxOut { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - // Deserialize the value (u64) - let value = u64::deserialize_reader(reader)?; - - // Deserialize the script_pubkey (ScriptBuf) - let script_len = u64::deserialize_reader(reader)? as usize; - let mut script_bytes = vec![0u8; script_len]; - reader.read_exact(&mut script_bytes)?; - let script_pubkey = ScriptBuf::from(script_bytes); - - Ok(BitcoinTxOut(TxOut { - value: Amount::from_sat(value), - script_pubkey, - })) - } -} - -/// Implement Arbitrary for ArbitraryTxOut -impl<'a> Arbitrary<'a> for BitcoinTxOut { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - // Generate arbitrary value and script for the TxOut - let value = u64::arbitrary(u)?; - let script_len = usize::arbitrary(u)? % 100; // Limit script length - let script_bytes = u.bytes(script_len)?; - let script_pubkey = ScriptBuf::from(script_bytes.to_vec()); - - Ok(Self(TxOut { - value: Amount::from_sat(value), - script_pubkey, - })) - } -} - -/// The components required in the witness stack to spend a taproot output. -/// -/// If a script-path path is being used, the witness stack needs the script being spent and the -/// control block in addition to the signature. -/// See [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs). -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum TaprootSpendPath { - /// Use the keypath spend. - /// - /// This only requires the signature for the tweaked internal key and nothing else. - Key, - - /// Use the script path spend. - /// - /// This requires the script being spent from as well as the [`ControlBlock`] in addition to - /// the elements that fulfill the spending condition in the script. - Script { - script_buf: ScriptBuf, - control_block: ControlBlock, - }, -} - -impl BorshSerialize for TaprootSpendPath { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - match self { - TaprootSpendPath::Key => { - // Variant index for Keypath is 0 - BorshSerialize::serialize(&0u32, writer)?; - } - TaprootSpendPath::Script { - script_buf, - control_block, - } => { - // Variant index for ScriptPath is 1 - BorshSerialize::serialize(&1u32, writer)?; - - // Serialize the ScriptBuf - let script_bytes = script_buf.to_bytes(); - BorshSerialize::serialize(&(script_bytes.len() as u64), writer)?; - writer.write_all(&script_bytes)?; - - // Serialize the ControlBlock using bitcoin's serialize method - let control_block_bytes = control_block.serialize(); - BorshSerialize::serialize(&(control_block_bytes.len() as u64), writer)?; - writer.write_all(&control_block_bytes)?; - } - } - Ok(()) - } -} - -// Implement BorshDeserialize for TaprootSpendInfo -impl BorshDeserialize for TaprootSpendPath { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - // Deserialize the variant index - let variant: u32 = BorshDeserialize::deserialize_reader(reader)?; - match variant { - 0 => Ok(TaprootSpendPath::Key), - 1 => { - // Deserialize the ScriptBuf - let script_len = u64::deserialize_reader(reader)? as usize; - let mut script_bytes = vec![0u8; script_len]; - reader.read_exact(&mut script_bytes)?; - let script_buf = ScriptBuf::from(script_bytes); - - // Deserialize the ControlBlock - let control_block_len = u64::deserialize_reader(reader)? as usize; - let mut control_block_bytes = vec![0u8; control_block_len]; - reader.read_exact(&mut control_block_bytes)?; - let control_block: ControlBlock = ControlBlock::decode(&control_block_bytes[..]) - .map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid ControlBlock") - })?; - - Ok(TaprootSpendPath::Script { - script_buf, - control_block, - }) - } - _ => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Unknown variant for TaprootSpendInfo", - )), - } - } -} - -// Implement Arbitrary for TaprootSpendInfo -impl<'a> Arbitrary<'a> for TaprootSpendPath { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - // Randomly decide which variant to generate - let variant = u.int_in_range(0..=1)?; - match variant { - 0 => Ok(TaprootSpendPath::Key), - 1 => { - // Arbitrary ScriptBuf (the script part of SpendInfo) - let script_len = usize::arbitrary(u)? % 100; // Limit the length of the script for practicality - let script_bytes = u.bytes(script_len)?; // Generate random bytes for the script - let script_buf = ScriptBuf::from(script_bytes.to_vec()); - - // Now we will manually generate the fields of the ControlBlock struct - - // Leaf version - let leaf_version = LeafVersion::TapScript; - - // Output key parity (Even or Odd) - let output_key_parity = if bool::arbitrary(u)? { - Parity::Even - } else { - Parity::Odd - }; - - // Generate a random secret key and derive the internal key - let secret_key = SecretKey::new(&mut OsRng); - let keypair = Keypair::from_secret_key(SECP256K1, &secret_key); - let (internal_key, _) = XOnlyPublicKey::from_keypair(&keypair); - - // Arbitrary Taproot merkle branch (vector of 32-byte hashes) - const BRANCH_LENGTH: usize = 10; - let mut tapnode_hashes: Vec = Vec::with_capacity(BRANCH_LENGTH); - for _ in 0..BRANCH_LENGTH { - let hash = TapNodeHash::from_slice(&<[u8; 32]>::arbitrary(u)?) - .map_err(|_e| arbitrary::Error::IncorrectFormat)?; - tapnode_hashes.push(hash); - } - - let tapnode_hashes: &[TapNodeHash; BRANCH_LENGTH] = - &tapnode_hashes[..BRANCH_LENGTH].try_into().unwrap(); - - let merkle_branch = TaprootMerkleBranch::from(*tapnode_hashes); - - // Construct the ControlBlock manually - let control_block = ControlBlock { - leaf_version, - output_key_parity, - internal_key, - merkle_branch, - }; - - // Construct the ScriptPath variant - Ok(TaprootSpendPath::Script { - script_buf, - control_block, - }) - } - _ => unreachable!(), - } - } -} - -/// Outpoint of a bitcoin tx -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, BorshSerialize, BorshDeserialize)] -pub struct Outpoint { - pub txid: Buf32, - pub vout: u32, -} - -// Custom debug implementation to print txid in little endian -impl Debug for Outpoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut txid_buf = [0u8; 64]; - { - let mut bytes = self.txid.0; - bytes.reverse(); - encode_to_slice(bytes, &mut txid_buf).expect("buf: enc hex"); - } - - f.debug_struct("Outpoint") - .field("txid", &unsafe { std::str::from_utf8_unchecked(&txid_buf) }) - .field("vout", &self.vout) - .finish() - } -} - -// Custom display implementation to print txid in little endian -impl Display for Outpoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut txid_buf = [0u8; 64]; - { - let mut bytes = self.txid.0; - bytes.reverse(); - encode_to_slice(bytes, &mut txid_buf).expect("buf: enc hex"); - } - - write!( - f, - "Outpoint {{ txid: {}, vout: {} }}", - // SAFETY: hex encoding always produces valid UTF-8 - unsafe { str::from_utf8_unchecked(&txid_buf) }, - self.vout - ) - } -} - -/// A wrapper around [`Buf32`] for XOnly Schnorr taproot pubkeys. -#[derive( - Debug, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct XOnlyPk(Buf32); - -impl XOnlyPk { - /// Construct a new [`XOnlyPk`] directly from a [`Buf32`]. - pub fn new(val: Buf32) -> Result { - if Self::is_valid_xonly_public_key(&val) { - Ok(Self(val)) - } else { - Err(ParseError::InvalidPoint(val)) - } - } - - /// Get the underlying [`Buf32`]. - pub fn inner(&self) -> &Buf32 { - &self.0 - } - - /// Convert a [`BitcoinAddress`] into a [`XOnlyPk`]. - pub fn from_address(checked_addr: &BitcoinAddress) -> Result { - let checked_addr = checked_addr.address(); - - if let Some(AddressType::P2tr) = checked_addr.address_type() { - let script_pubkey = checked_addr.script_pubkey(); - - // skip the version and length bytes - let pubkey_bytes = &script_pubkey.as_bytes()[2..34]; - let output_key: XOnlyPublicKey = XOnlyPublicKey::from_slice(pubkey_bytes)?; - - Ok(Self(Buf32(output_key.serialize()))) - } else { - Err(ParseError::UnsupportedAddress(checked_addr.address_type())) - } - } - - /// Convert the [`XOnlyPk`] to a `rust-bitcoin`'s [`XOnlyPublicKey`]. - pub fn to_xonly_public_key(&self) -> XOnlyPublicKey { - XOnlyPublicKey::from_slice(self.0.as_bytes()).expect("XOnlyPk is valid") - } - - /// Convert the [`XOnlyPk`] to an [`Address`]. - pub fn to_p2tr_address(&self, network: Network) -> Result { - let buf: [u8; 32] = self.0 .0; - let pubkey = XOnlyPublicKey::from_slice(&buf)?; - - Ok(Address::p2tr_tweaked( - pubkey.dangerous_assume_tweaked(), - network, - )) - } - - /// Converts [`XOnlyPk`] to [`Descriptor`]. - pub fn to_descriptor(&self) -> Result { - Descriptor::new_p2tr(&self.to_xonly_public_key().serialize()) - .map_err(|_| ParseError::InvalidPoint(self.0)) - } - - /// Checks if the [`Buf32`] is a valid [`XOnlyPublicKey`]. - fn is_valid_xonly_public_key(buf: &Buf32) -> bool { - XOnlyPublicKey::from_slice(buf.as_bytes()).is_ok() - } -} - -impl From for XOnlyPk { - fn from(value: XOnlyPublicKey) -> Self { - Self(Buf32(value.serialize())) - } -} - -impl TryFrom for Descriptor { - type Error = ParseError; - - fn try_from(value: XOnlyPk) -> Result { - let inner_xonly_pk = XOnlyPublicKey::try_from(value.0)?; - Ok(inner_xonly_pk.into()) - } -} - -/// Represents a raw, byte-encoded Bitcoin transaction with custom [`Arbitrary`] support. -/// Provides conversions (via [`TryFrom`]) to and from [`Transaction`]. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] -pub struct RawBitcoinTx(Vec); - -impl RawBitcoinTx { - /// Creates a new `RawBitcoinTx` from a raw byte vector. - pub fn from_raw_bytes(bytes: Vec) -> Self { - RawBitcoinTx(bytes) - } -} - -impl From for RawBitcoinTx { - fn from(value: Transaction) -> Self { - Self(serialize(&value)) - } -} - -impl TryFrom for Transaction { - type Error = encode::Error; - fn try_from(value: RawBitcoinTx) -> Result { - deserialize(&value.0) - } -} - -impl TryFrom<&RawBitcoinTx> for Transaction { - type Error = encode::Error; - fn try_from(value: &RawBitcoinTx) -> Result { - deserialize(&value.0) - } -} - -impl<'a> arbitrary::Arbitrary<'a> for RawBitcoinTx { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - // Random number of inputs and outputs (bounded for simplicity) - let input_count = u.int_in_range::(0..=4)?; - let output_count = u.int_in_range::(0..=4)?; - - // Build random inputs - let mut inputs = Vec::with_capacity(input_count); - for _ in 0..input_count { - // Random 32-byte TXID - let mut txid_bytes = [0u8; 32]; - u.fill_buffer(&mut txid_bytes)?; - - // Random vout - let vout = u32::arbitrary(u)?; - - // Random scriptSig (bounded size) - let script_sig_size = u.int_in_range::(0..=50)?; - let script_sig_bytes = u.bytes(script_sig_size)?; - let script_sig = ScriptBuf::from_bytes(script_sig_bytes.to_vec()); - - inputs.push(TxIn { - previous_output: OutPoint { - txid: Txid::from_byte_array(txid_bytes), - vout, - }, - script_sig, - sequence: Sequence::MAX, - witness: Witness::default(), // or generate random witness if desired - }); - } - - // Build random outputs - let mut outputs = Vec::with_capacity(output_count); - for _ in 0..output_count { - // Random value (in satoshis) - let value = Amount::from_sat(u64::arbitrary(u)?); - - // Random scriptPubKey (bounded size) - let script_pubkey_size = u.int_in_range::(0..=50)?; - let script_pubkey_bytes = u.bytes(script_pubkey_size)?; - let script_pubkey = ScriptBuf::from(script_pubkey_bytes.to_vec()); - - outputs.push(TxOut { - value, - script_pubkey, - }); - } - - // Construct the transaction - let tx = Transaction { - version: Version::ONE, - lock_time: LockTime::ZERO, - input: inputs, - output: outputs, - }; - - Ok(tx.into()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BitcoinScriptBuf(ScriptBuf); - -impl BitcoinScriptBuf { - pub fn inner(&self) -> &ScriptBuf { - &self.0 - } -} - -impl From for BitcoinScriptBuf { - fn from(value: ScriptBuf) -> Self { - Self(value) - } -} - -// Implement BorshSerialize for BitcoinScriptBuf -impl BorshSerialize for BitcoinScriptBuf { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - let script_bytes = self.0.to_bytes(); - BorshSerialize::serialize(&(script_bytes.len() as u32), writer)?; - writer.write_all(&script_bytes)?; - Ok(()) - } -} - -// Implement BorshDeserialize for BitcoinScriptBuf -impl BorshDeserialize for BitcoinScriptBuf { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - let script_len = u32::deserialize_reader(reader)? as usize; - let mut script_bytes = vec![0u8; script_len]; - reader.read_exact(&mut script_bytes)?; - let script_pubkey = ScriptBuf::from(script_bytes); - - Ok(BitcoinScriptBuf(script_pubkey)) - } -} - -impl<'a> Arbitrary<'a> for BitcoinScriptBuf { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - // Generate arbitrary script - let script_len = usize::arbitrary(u)? % 100; // Limit script length - let script_bytes = u.bytes(script_len)?; - let script = ScriptBuf::from(script_bytes.to_vec()); - - Ok(Self(script)) - } -} +// Re-export from identifiers +#[rustfmt::skip] +pub use strata_identifiers::BitcoinAmount; #[cfg(test)] mod tests { @@ -1006,7 +27,7 @@ mod tests { use super::{ BitcoinAddress, BitcoinAmount, BitcoinScriptBuf, BitcoinTxOut, BitcoinTxid, - BorshDeserialize, BorshSerialize, RawBitcoinTx, XOnlyPk, + BorshDeserialize, BorshSerialize, XOnlyPk, }; use crate::{ buf::Buf32, @@ -1439,17 +460,6 @@ mod tests { ); } - #[test] - fn test_bitcoin_tx_arbitrary_generation() { - let mut generator = ArbitraryGenerator::new(); - let raw_tx: RawBitcoinTx = generator.generate(); - let _: Transaction = raw_tx.try_into().expect("should generate valid tx"); - - let raw_tx = RawBitcoinTx::from_raw_bytes(generator.generate()); - let res: Result = raw_tx.try_into(); - assert!(res.is_err()); - } - #[test] fn test_xonly_pk_to_descriptor() { let xonly_pk = XOnlyPk::new(Buf32::from([2u8; 32])).unwrap(); diff --git a/crates/primitives/src/l1/mod.rs b/crates/primitives/src/l1/mod.rs index 87d56ffe7c..f66047fac8 100644 --- a/crates/primitives/src/l1/mod.rs +++ b/crates/primitives/src/l1/mod.rs @@ -8,3 +8,6 @@ pub use block::*; pub use btc::*; pub use params::*; pub use status::*; + +// Re-export OutputRef from btc-types +pub use strata_btc_types::OutputRef; diff --git a/crates/primitives/src/l1/payload.rs b/crates/primitives/src/l1/payload.rs index 09685963a6..d1f30b527e 100644 --- a/crates/primitives/src/l1/payload.rs +++ b/crates/primitives/src/l1/payload.rs @@ -1,221 +1,2 @@ -//! Types relating to payloads. -//! -//! These types don't care about the *purpose* of the payloads, we only care about what's in them. - -use arbitrary::Arbitrary; -use borsh::{BorshDeserialize, BorshSerialize}; -use num_enum::{IntoPrimitive, TryFromPrimitive}; -use serde::{Deserialize, Serialize}; - -use crate::{buf::Buf32, hash}; - -/// DA destination identifier. This will eventually be used to enable -/// storing payloads on alternative availability schemes. -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - BorshDeserialize, - BorshSerialize, - IntoPrimitive, - TryFromPrimitive, - Serialize, - Deserialize, -)] -#[borsh(use_discriminant = true)] -#[repr(u8)] -pub enum PayloadDest { - /// If we expect the DA to be on the L1 chain that we settle to. This is - /// always the strongest DA layer we have access to. - L1 = 0, -} - -/// Manual `Arbitrary` impl so that we always generate L1 DA if we add future -/// ones that would work in totally different ways. -impl<'a> Arbitrary<'a> for PayloadDest { - fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Ok(Self::L1) - } -} - -/// Summary of a DA payload to be included on a DA layer. Specifies the target and -/// a commitment to the payload. -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Hash, - Arbitrary, - BorshDeserialize, - BorshSerialize, - Serialize, - Deserialize, -)] -pub struct BlobSpec { - /// Target settlement layer we're expecting the DA on. - dest: PayloadDest, - - /// Commitment to the payload (probably just a hash or a - /// merkle root) that we expect to see committed to DA. - commitment: Buf32, -} - -impl BlobSpec { - /// The target we expect the DA payload to be stored on. - pub fn dest(&self) -> PayloadDest { - self.dest - } - - /// Commitment to the payload. - pub fn commitment(&self) -> &Buf32 { - &self.commitment - } - - #[expect(dead_code, reason = "Constructor for testing purposes")] - fn new(dest: PayloadDest, commitment: Buf32) -> Self { - Self { dest, commitment } - } -} - -/// Summary of a DA payload to be included on a DA layer. Specifies the target and -/// a commitment to the payload. -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Hash, - Arbitrary, - BorshDeserialize, - BorshSerialize, - Serialize, - Deserialize, -)] -pub struct PayloadSpec { - /// Target settlement layer we're expecting the DA on. - dest: PayloadDest, - - /// Commitment to the payload (probably just a hash or a - /// merkle root) that we expect to see committed to DA. - commitment: Buf32, -} - -impl PayloadSpec { - /// The target we expect the DA payload to be stored on. - pub fn dest(&self) -> PayloadDest { - self.dest - } - - /// Commitment to the payload. - pub fn commitment(&self) -> &Buf32 { - &self.commitment - } - - fn new(dest: PayloadDest, commitment: Buf32) -> Self { - Self { dest, commitment } - } -} - -/// Data that is submitted to L1. This can be DA, Checkpoint, etc. -#[derive( - Clone, Debug, Eq, PartialEq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct L1Payload { - data: Vec, - payload_type: L1PayloadType, -} - -impl L1Payload { - pub fn new(data: Vec, payload_type: L1PayloadType) -> Self { - Self { data, payload_type } - } - - pub fn new_checkpoint(data: Vec) -> Self { - Self::new(data, L1PayloadType::Checkpoint) - } - - pub fn new_da(data: Vec) -> Self { - Self::new(data, L1PayloadType::Da) - } - - pub fn data(&self) -> &[u8] { - &self.data - } - - pub fn payload_type(&self) -> &L1PayloadType { - &self.payload_type - } - - pub fn hash(&self) -> Buf32 { - let mut buf = self.data.clone(); - buf.extend(borsh::to_vec(&self.payload_type).expect("Could not serialize payload type")); - hash::raw(&buf) - } -} - -#[derive( - Clone, Debug, Eq, PartialEq, Arbitrary, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub enum L1PayloadType { - Checkpoint, - Da, -} - -/// Intent produced by the EE on a "full" verification, but if we're just -/// verifying a proof we may not have access to this but still want to reason -/// about it. -/// -/// These are never stored on-chain. -#[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize)] -// TODO: rename this to L1PayloadIntent and remove the dest field -pub struct PayloadIntent { - /// The destination for this payload. - dest: PayloadDest, - - /// Commitment to the payload. - commitment: Buf32, - - /// Blob payload. - payload: L1Payload, -} - -impl PayloadIntent { - pub fn new(dest: PayloadDest, commitment: Buf32, payload: L1Payload) -> Self { - Self { - dest, - commitment, - payload, - } - } - - /// The target we expect the DA payload to be stored on. - pub fn dest(&self) -> PayloadDest { - self.dest - } - - /// Commitment to the payload, which might be context-specific. This - /// is conceptually unrelated to the payload ID that we use for tracking which - /// payloads we've written in the L1 writer bookkeeping. - pub fn commitment(&self) -> &Buf32 { - &self.commitment - } - - /// The payload that matches the commitment. - pub fn payload(&self) -> &L1Payload { - &self.payload - } - - /// Generates the spec from the relevant parts of the payload intent that - /// uniquely refers to the payload data. - pub fn to_spec(&self) -> PayloadSpec { - PayloadSpec::new(self.dest, self.commitment) - } -} +// TODO remove these types +pub use strata_ol_chain_types::legacy_da_payload::*; diff --git a/crates/primitives/src/l2.rs b/crates/primitives/src/l2.rs index 20c0bfe4c3..d7210b31c7 100644 --- a/crates/primitives/src/l2.rs +++ b/crates/primitives/src/l2.rs @@ -1,117 +1,2 @@ -use std::fmt; - -use arbitrary::Arbitrary; -use borsh::{BorshDeserialize, BorshSerialize}; -use const_hex as hex; -use serde::{Deserialize, Serialize}; - -use crate::buf::Buf32; - -/// ID of an L2 block, usually the hash of its root header. -#[derive( - Copy, - Clone, - Eq, - Default, - PartialEq, - Ord, - PartialOrd, - Hash, - Arbitrary, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, -)] -pub struct L2BlockId(Buf32); - -impl_buf_wrapper!(L2BlockId, Buf32, 32); - -impl L2BlockId { - /// Returns a dummy blkid that is all zeroes. - pub fn null() -> Self { - Self::from(Buf32::zero()) - } - - /// Checks to see if this is the dummy "zero" blkid. - pub fn is_null(&self) -> bool { - self.0.is_zero() - } -} - -/// Commits to a specific block at some slot. -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Arbitrary, - BorshDeserialize, - BorshSerialize, - Deserialize, - Serialize, -)] -pub struct L2BlockCommitment { - slot: u64, - blkid: L2BlockId, -} - -impl L2BlockCommitment { - pub fn new(slot: u64, blkid: L2BlockId) -> Self { - Self { slot, blkid } - } - - pub fn null() -> Self { - Self::new(0, L2BlockId::from(Buf32::zero())) - } - - pub fn slot(&self) -> u64 { - self.slot - } - - pub fn blkid(&self) -> &L2BlockId { - &self.blkid - } - - pub fn is_null(&self) -> bool { - self.slot == 0 && self.blkid.0.is_zero() - } -} - -impl fmt::Display for L2BlockCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Show first 2 and last 2 bytes of block ID (4 hex chars each) - let blkid_bytes = self.blkid.as_ref(); - let first_2 = &blkid_bytes[..2]; - let last_2 = &blkid_bytes[30..]; - - let mut first_hex = [0u8; 4]; - let mut last_hex = [0u8; 4]; - hex::encode_to_slice(first_2, &mut first_hex) - .expect("Failed to encode first 2 bytes to hex"); - hex::encode_to_slice(last_2, &mut last_hex).expect("Failed to encode last 2 bytes to hex"); - - write!( - f, - "{}@{}..{}", - self.slot, - std::str::from_utf8(&first_hex) - .expect("Failed to convert first hex bytes to UTF-8 string"), - std::str::from_utf8(&last_hex) - .expect("Failed to convert last hex bytes to UTF-8 string") - ) - } -} - -impl fmt::Debug for L2BlockCommitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "L2BlockCommitment(slot={}, blkid={:?})", - self.slot, self.blkid - ) - } -} +// TODO update these imports +pub use strata_identifiers::{L2BlockCommitment, L2BlockId}; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e8b5056b20..e38ce7bd5c 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -3,6 +3,16 @@ // TODO import address types // TODO import generic account types +// Suppress unused crate dependencies warnings +#[cfg(not(test))] +use bincode as _; +#[cfg(not(test))] +use num_enum as _; +#[cfg(not(test))] +use strata_crypto as _; +#[cfg(not(test))] +use strata_l1_txfmt as _; + #[macro_use] mod macros; @@ -10,7 +20,6 @@ pub mod block_credential; pub mod bridge; pub mod buf; pub mod constants; -pub mod crypto; pub mod epoch; pub mod errors; pub mod evm_exec; @@ -20,7 +29,6 @@ pub mod keys; pub mod l1; pub mod l2; pub mod operator; -pub mod params; pub mod prelude; pub mod proof; pub mod relay; diff --git a/crates/primitives/src/macros.rs b/crates/primitives/src/macros.rs index b20912005e..5d73051d80 100644 --- a/crates/primitives/src/macros.rs +++ b/crates/primitives/src/macros.rs @@ -32,351 +32,3 @@ macro_rules! impl_buf_wrapper { } }; } - -pub(crate) mod internal { - // Crate-internal impls. - - macro_rules! impl_buf_common { - ($name:ident, $len:expr) => { - impl $name { - pub const LEN: usize = $len; - - pub const fn new(data: [u8; $len]) -> Self { - Self(data) - } - - pub const fn as_slice(&self) -> &[u8] { - &self.0 - } - - pub const fn as_mut_slice(&mut self) -> &mut [u8] { - &mut self.0 - } - - pub const fn as_bytes(&self) -> &[u8] { - self.0.as_slice() - } - - pub const fn zero() -> Self { - Self::new([0; $len]) - } - - pub const fn is_zero(&self) -> bool { - let mut i = 0; - while i < $len { - if self.0[i] != 0 { - return false; - } - i += 1; - } - true - } - } - - impl ::std::convert::AsRef<[u8; $len]> for $name { - fn as_ref(&self) -> &[u8; $len] { - &self.0 - } - } - - impl ::std::convert::AsMut<[u8]> for $name { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } - } - - impl ::std::convert::From<[u8; $len]> for $name { - fn from(data: [u8; $len]) -> Self { - Self(data) - } - } - - impl ::std::convert::From<$name> for [u8; $len] { - fn from(buf: $name) -> Self { - buf.0 - } - } - - impl<'a> ::std::convert::From<&'a [u8; $len]> for $name { - fn from(data: &'a [u8; $len]) -> Self { - Self(*data) - } - } - - impl<'a> ::std::convert::TryFrom<&'a [u8]> for $name { - type Error = &'a [u8]; - - fn try_from(value: &'a [u8]) -> Result { - if value.len() == $len { - let mut arr = [0; $len]; - arr.copy_from_slice(value); - Ok(Self(arr)) - } else { - Err(value) - } - } - } - - impl ::std::default::Default for $name { - fn default() -> Self { - Self([0; $len]) - } - } - - impl ::std::fmt::Debug for $name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - // twice as large, required by the hex::encode_to_slice. - let mut buf = [0; $len * 2]; - ::hex::encode_to_slice(self.0, &mut buf).expect("buf: enc hex"); - f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) }) - } - } - - impl ::std::fmt::Display for $name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - // fmt only first and last bits of data. - let mut buf = [0; 6]; - ::hex::encode_to_slice(&self.0[..3], &mut buf).expect("buf: enc hex"); - f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })?; - f.write_str("..")?; - ::hex::encode_to_slice(&self.0[$len - 3..], &mut buf).expect("buf: enc hex"); - f.write_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })?; - Ok(()) - } - } - - impl ::borsh::BorshSerialize for $name { - fn serialize(&self, writer: &mut W) -> ::std::io::Result<()> { - let bytes = self.0.as_ref(); - let _ = writer.write(bytes)?; - Ok(()) - } - } - - impl ::borsh::BorshDeserialize for $name { - fn deserialize_reader( - reader: &mut R, - ) -> ::std::io::Result { - let mut array = [0u8; $len]; - reader.read_exact(&mut array)?; - Ok(array.into()) - } - } - - impl<'a> ::arbitrary::Arbitrary<'a> for $name { - fn arbitrary(u: &mut ::arbitrary::Unstructured<'a>) -> ::arbitrary::Result { - let mut array = [0u8; $len]; - u.fill_buffer(&mut array)?; - Ok(array.into()) - } - } - }; - } - - macro_rules! impl_buf_serde { - ($name:ident, $len:expr) => { - impl ::serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: ::serde::Serializer, - { - // Convert the inner array to a hex string (without 0x prefix) - let hex_str = ::hex::encode(&self.0); - serializer.serialize_str(&hex_str) - } - } - - impl<'de> ::serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where - D: ::serde::Deserializer<'de>, - { - // Define a Visitor for deserialization. - // P.S. Make it in the scope of the function to avoid name conflicts - // for different macro_rules invocations. - struct BufVisitor; - - impl<'de> ::serde::de::Visitor<'de> for BufVisitor { - type Value = $name; - - fn expecting( - &self, - formatter: &mut ::std::fmt::Formatter<'_>, - ) -> ::std::fmt::Result { - write!( - formatter, - "a hex string with an optional 0x prefix representing {} bytes", - $len - ) - } - - fn visit_str(self, v: &str) -> Result<$name, E> - where - E: ::serde::de::Error, - { - // Remove the optional "0x" or "0X" prefix if present. - let hex_str = if v.starts_with("0x") || v.starts_with("0X") { - &v[2..] - } else { - v - }; - - // Decode the hex string into a vector of bytes. - let bytes = ::hex::decode(hex_str).map_err(E::custom)?; - - // Ensure the decoded bytes have the expected length. - if bytes.len() != $len { - return Err(E::custom(format!( - "expected {} bytes, got {}", - $len, - bytes.len() - ))); - } - - // Convert the Vec into a fixed-size array. - let mut array = [0u8; $len]; - array.copy_from_slice(&bytes); - Ok($name(array)) - } - - fn visit_bytes(self, v: &[u8]) -> Result<$name, E> - where - E: ::serde::de::Error, - { - if v.len() == $len { - let mut array = [0u8; $len]; - array.copy_from_slice(v); - Ok($name(array)) - } else { - // Try to interpret the bytes as a UTF-8 encoded hex string. - let s = ::std::str::from_utf8(v).map_err(E::custom)?; - self.visit_str(s) - } - } - - fn visit_seq(self, mut seq: A) -> Result<$name, A::Error> - where - A: ::serde::de::SeqAccess<'de>, - { - let mut array = [0u8; $len]; - for i in 0..$len { - array[i] = seq - .next_element::()? - .ok_or_else(|| ::serde::de::Error::invalid_length(i, &self))?; - } - // Ensure there are no extra elements. - if let Some(_) = seq.next_element::()? { - return Err(::serde::de::Error::custom(format!( - "expected a sequence of exactly {} bytes, but found extra elements", - $len - ))); - } - Ok($name(array)) - } - } - - if deserializer.is_human_readable() { - // For human-readable formats, support multiple input types. - // Use with the _any, so serde can decide whether to visit seq, bytes or str. - deserializer.deserialize_any(BufVisitor) - } else { - // Bincode does not support DeserializeAny, so deserializing with the _str. - deserializer.deserialize_str(BufVisitor) - } - } - } - }; - } - - pub(crate) use impl_buf_common; - pub(crate) use impl_buf_serde; -} - -#[cfg(test)] -mod tests { - - #[derive(PartialEq)] - pub struct TestBuf20([u8; 20]); - - crate::macros::internal::impl_buf_common!(TestBuf20, 20); - crate::macros::internal::impl_buf_serde!(TestBuf20, 20); - - #[test] - fn test_from_into_array() { - let buf = TestBuf20::new([5u8; 20]); - let arr: [u8; 20] = buf.into(); - assert_eq!(arr, [5; 20]); - } - - #[test] - fn test_from_array_ref() { - let arr = [2u8; 20]; - let buf: TestBuf20 = TestBuf20::from(&arr); - assert_eq!(buf.as_slice(), &arr); - } - - #[test] - fn test_default() { - let buf = TestBuf20::default(); - assert_eq!(buf.as_slice(), &[0; 20]); - } - - #[test] - fn test_serialize_hex() { - let data = [1u8; 20]; - let buf = TestBuf20(data); - let json = serde_json::to_string(&buf).unwrap(); - // Since we serialize as a string, json should be the hex-encoded string wrapped in quotes. - let expected = format!("\"{}\"", hex::encode(data)); - assert_eq!(json, expected); - } - - #[test] - fn test_deserialize_hex_without_prefix() { - let data = [2u8; 20]; - let hex_str = hex::encode(data); - let json = format!("\"{hex_str}\""); - let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); - assert_eq!(buf, TestBuf20(data)); - } - - #[test] - fn test_deserialize_hex_with_prefix() { - let data = [3u8; 20]; - let hex_str = hex::encode(data); - let json = format!("\"0x{hex_str}\""); - let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); - assert_eq!(buf, TestBuf20(data)); - } - - #[test] - fn test_deserialize_from_seq() { - // Provide a JSON array of numbers. - let data = [5u8; 20]; - let json = serde_json::to_string(&data).unwrap(); - let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); - assert_eq!(buf, TestBuf20(data)); - } - - #[test] - fn test_deserialize_from_bytes_via_array() { - // Although JSON doesn't have a native "bytes" type, this test uses a JSON array - // to exercise the same code path as visit_bytes when deserializing a sequence. - let data = [7u8; 20]; - // Simulate input as a JSON array - let json = serde_json::to_string(&data).unwrap(); - let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); - assert_eq!(buf, TestBuf20(data)); - } - - #[test] - fn test_bincode_roundtrip() { - let data = [9u8; 20]; - let buf = TestBuf20(data); - // bincode is non-human-readable so our implementation will use deserialize_tuple. - let encoded = bincode::serialize(&buf).expect("bincode serialization failed"); - let decoded: TestBuf20 = - bincode::deserialize(&encoded).expect("bincode deserialization failed"); - assert_eq!(buf, decoded); - } -} diff --git a/crates/primitives/src/operator.rs b/crates/primitives/src/operator.rs index c264aadc03..841e1c7e99 100644 --- a/crates/primitives/src/operator.rs +++ b/crates/primitives/src/operator.rs @@ -1,9 +1,9 @@ -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; - use super::bridge::OperatorIdx; use crate::prelude::Buf32; +// Re-export OperatorPubkeys from btc-types +pub use strata_btc_types::OperatorPubkeys; + /// Some type that can provide operator keys. pub trait OperatorKeyProvider { /// Returns the operator's signing pubkey, if it exists in the table. @@ -32,31 +32,3 @@ impl OperatorKeyProvider for StubOpKeyProv { } } } - -/// Container for operator pubkeys. -#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)] -pub struct OperatorPubkeys { - signing_pk: Buf32, - wallet_pk: Buf32, -} - -impl OperatorPubkeys { - pub fn new(signing_pk: Buf32, wallet_pk: Buf32) -> Self { - Self { - signing_pk, - wallet_pk, - } - } - - pub fn signing_pk(&self) -> &Buf32 { - &self.signing_pk - } - - pub fn wallet_pk(&self) -> &Buf32 { - &self.wallet_pk - } - - pub fn into_parts(self) -> (Buf32, Buf32) { - (self.signing_pk, self.wallet_pk) - } -} diff --git a/crates/primitives/src/prelude.rs b/crates/primitives/src/prelude.rs index 68471bd674..f7a04b9365 100644 --- a/crates/primitives/src/prelude.rs +++ b/crates/primitives/src/prelude.rs @@ -1,2 +1,2 @@ // Reexports from elsewhere in the crate. -pub use crate::{buf::*, epoch::*, l1::*, l2::*, params::*}; +pub use crate::{buf::*, epoch::*, l1::*, l2::*}; diff --git a/crates/primitives/src/proof.rs b/crates/primitives/src/proof.rs index 1ff497828f..8b13789179 100644 --- a/crates/primitives/src/proof.rs +++ b/crates/primitives/src/proof.rs @@ -1,154 +1 @@ -use std::fmt::Display; -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; -use zkaleido_risc0_groth16_verifier::Risc0Groth16Verifier; -use zkaleido_sp1_groth16_verifier::SP1Groth16Verifier; - -use crate::{evm_exec::EvmEeBlockCommitment, l2::L2BlockCommitment}; - -pub type Epoch = u64; - -/// Represents the verifying key used for verifying ZK proofs in a rollup context. -/// -/// This enum encapsulates verifying keys for different ZKVMs: -/// - `SP1VerifyingKey`: Used for verifying proofs generated using SP1. -/// - `Risc0VerifyingKey`: Used for verifying proofs generated using Risc0. -/// - `Native`: For functional testing purposes without ZKVM overhead. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum RollupVerifyingKey { - /// Verifying Key for proofs generated by SP1. - #[serde(rename = "sp1")] - SP1VerifyingKey(SP1Groth16Verifier), - - /// Verifying Key for proofs generated by Risc0. - #[serde(rename = "risc0")] - Risc0VerifyingKey(Risc0Groth16Verifier), - - /// Placeholder variant for functional testing. - /// - /// This variant allows skipping guest code compilation (e.g., ELFs for SP1 or Risc0) and is - /// used to test the prover-client and proof logic without the overhead of ZKVM - /// compilation. It is strictly for internal testing and must not be used in production - /// deployments. - #[serde(rename = "native")] - NativeVerifyingKey, -} - -impl BorshSerialize for RollupVerifyingKey { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - let encoded = bincode::serialize(self).map_err(std::io::Error::other)?; - BorshSerialize::serialize(&encoded, writer) - } -} - -impl BorshDeserialize for RollupVerifyingKey { - fn deserialize_reader(reader: &mut R) -> std::io::Result { - let encoded = Vec::::deserialize_reader(reader)?; - bincode::deserialize(&encoded).map_err(std::io::Error::other) - } -} - -/// Represents a context for different types of proofs. -/// -/// This enum categorizes proofs by their associated context, including the type of proof and its -/// range or scope. Each variant includes relevant metadata required to distinguish and track the -/// proof. -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - Hash, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, -)] -pub enum ProofContext { - /// Identifier for the EVM Execution Environment (EE) blocks used in generating the State - /// Transition Function (STF) proof. - EvmEeStf(EvmEeBlockCommitment, EvmEeBlockCommitment), - - /// Identifier for the Consensus Layer (CL) blocks used in generating the State Transition - /// Function (STF) proof. - ClStf(L2BlockCommitment, L2BlockCommitment), - - /// Identifier for a specific checkpoint being proven. - Checkpoint(u64), -} - -/// Represents the ZkVm host used for proof generation. -/// -/// This enum identifies the ZkVm environment utilized to create a proof. -/// Available hosts: -/// - `SP1`: SP1 ZKVM. -/// - `Risc0`: Risc0 ZKVM. -/// - `Native`: Native ZKVM. -#[non_exhaustive] -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - Hash, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, -)] -pub enum ProofZkVm { - SP1, - Risc0, - Native, -} - -/// Represents a unique key for identifying any type of proof. -/// -/// A `ProofKey` combines a `ProofContext` (which specifies the type of proof and its scope) -/// with a `ProofZkVm` (which specifies the ZKVM host used for proof generation). -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - Hash, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, -)] -pub struct ProofKey { - /// The unique identifier for the proof type and its context. - context: ProofContext, - /// The ZKVM host used for proof generation. - host: ProofZkVm, -} - -impl ProofKey { - pub fn new(context: ProofContext, host: ProofZkVm) -> Self { - Self { context, host } - } - - pub fn context(&self) -> &ProofContext { - &self.context - } - - pub fn host(&self) -> &ProofZkVm { - &self.host - } -} - -impl Display for ProofKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "ProofKey(context = {:?}, host = {:?})", - self.context, self.host - ) - } -} diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 85e5b6958f..2d49a5c300 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -1,78 +1,8 @@ use serde::{Deserialize, Serialize}; -use crate::{hash::sha256d, prelude::Buf32}; +pub use strata_identifiers::utils::get_cohashes; -/// Generates cohashes and computes the Merkle root for a transaction ID at a specific index -/// within a given slice of elements that can be converted to [`Buf32`]. -/// -/// This function supports any type that implements the [`Into`] trait, such as -/// [`Txid`s](bitcoin::Txid) or [`Wtxid`s](bitcoin::Wtxid). -/// -/// # Parameters -/// -/// - `ids`: A slice of ids ([`Txid`s](bitcoin::Txid) or [`Wtxid`s](bitcoin::Wtxid)) that can be -/// converted into [`Buf32`]. -/// - `index`: The index of the transaction for which we want the cohashes. -/// -/// # Notes -/// -/// Cohashes refer to the intermediate hashes (sometimes called "siblings") needed to -/// reconstruct the Merkle path for a given transaction. These intermediate hashes, along with -/// the transaction's hash itself, can be used to compute the Merkle root, thus verifying the -/// transaction’s membership in the Merkle tree. -/// -/// # Returns -/// -/// - A tuple `(Vec, Buf32)` containing the cohashes and the Merkle root. -/// -/// # Panics -/// -/// - If the `index` is out of bounds for the `elements` length. -pub fn get_cohashes(ids: &[T], index: u32) -> (Vec, Buf32) -where - T: Into + Clone, -{ - assert!( - (index as usize) < ids.len(), - "The transaction index should be within the txids length" - ); - let mut curr_level: Vec = ids.iter().cloned().map(Into::into).collect(); - - let mut curr_index = index; - let mut proof = Vec::new(); - - while curr_level.len() > 1 { - let len = curr_level.len(); - if !len.is_multiple_of(2) { - curr_level.push(curr_level[len - 1]); - } - - let proof_item_index = if curr_index.is_multiple_of(2) { - curr_index + 1 - } else { - curr_index - 1 - }; - - let item = curr_level[proof_item_index as usize]; - proof.push(item); - - // construct pairwise hash - curr_level = curr_level - .chunks(2) - .map(|pair| { - let [a, b] = pair else { - panic!("utils: cohash chunk should be a pair"); - }; - let mut arr = [0u8; 64]; - arr[..32].copy_from_slice(a.as_bytes()); - arr[32..].copy_from_slice(b.as_bytes()); - sha256d(&arr) - }) - .collect::>(); - curr_index >>= 1; - } - (proof, curr_level[0]) -} +use crate::prelude::Buf32; /// Temporary schnorr keypair. // FIXME why temporary? diff --git a/crates/proof-impl/cl-stf/src/lib.rs b/crates/proof-impl/cl-stf/src/lib.rs index e790b01466..5c61a81d62 100644 --- a/crates/proof-impl/cl-stf/src/lib.rs +++ b/crates/proof-impl/cl-stf/src/lib.rs @@ -10,7 +10,7 @@ use strata_ol_chain_types::{ check_block_credential, validate_block_structure, ExecSegment, L2Block, L2BlockHeader, L2Header, }; use strata_ol_chainstate_types::Chainstate; -use strata_primitives::params::RollupParams; +use strata_params::RollupParams; use zkaleido::ZkVmEnv; pub fn process_cl_stf(zkvm: &impl ZkVmEnv, el_vkey: &[u32; 8]) { diff --git a/crates/reth/evm/src/precompiles/schnorr.rs b/crates/reth/evm/src/precompiles/schnorr.rs index 3138ef8a41..eac54ef75d 100644 --- a/crates/reth/evm/src/precompiles/schnorr.rs +++ b/crates/reth/evm/src/precompiles/schnorr.rs @@ -3,7 +3,7 @@ use revm::precompile::{ }; use revm_primitives::Bytes; use strata_crypto::verify_schnorr_sig; -use strata_primitives::buf::{Buf32, Buf64}; +use strata_identifiers::{Buf32, Buf64}; use crate::constants::SCHNORR_ADDRESS; @@ -52,7 +52,7 @@ fn verify_schnorr_precompile(input: &[u8], _gas_limit: u64) -> PrecompileResult mod tests { use secp256k1::{Keypair, SecretKey, SECP256K1}; use strata_crypto::sign_schnorr_sig; - use strata_primitives::buf::{Buf32, Buf64}; + use strata_identifiers::{Buf32, Buf64}; use super::*; diff --git a/crates/rpc/types/src/lib.rs b/crates/rpc/types/src/lib.rs index bb2eb9ab0f..e2b83938ae 100644 --- a/crates/rpc/types/src/lib.rs +++ b/crates/rpc/types/src/lib.rs @@ -4,5 +4,5 @@ pub mod errors; pub mod types; pub use errors::*; -pub use strata_primitives::proof::ProofKey; +pub use strata_crypto::proof_vk::ProofKey; pub use types::*; diff --git a/crates/sequencer/src/block_template/worker.rs b/crates/sequencer/src/block_template/worker.rs index 794b4cff54..e00b872571 100644 --- a/crates/sequencer/src/block_template/worker.rs +++ b/crates/sequencer/src/block_template/worker.rs @@ -10,7 +10,7 @@ use strata_eectl::engine::ExecEngineCtl; use strata_ol_chain_types::{ verify_sequencer_signature, L2BlockBundle, L2BlockHeader, L2BlockId, L2Header, }; -use strata_primitives::params::{Params, RollupParams}; +use strata_params::{Params, RollupParams}; use strata_status::StatusChannel; use strata_storage::NodeStorage; use strata_tasks::ShutdownGuard; diff --git a/crates/sequencer/src/checkpoint/helper.rs b/crates/sequencer/src/checkpoint/helper.rs index d251bdc6da..0cf5e3677c 100644 --- a/crates/sequencer/src/checkpoint/helper.rs +++ b/crates/sequencer/src/checkpoint/helper.rs @@ -2,7 +2,7 @@ use strata_checkpoint_types::SignedCheckpoint; use strata_ol_chain_types::verify_sequencer_signature; -use strata_primitives::params::Params; +use strata_params::Params; /// Verify checkpoint has correct signature from sequencer. pub fn verify_checkpoint_sig(signed_checkpoint: &SignedCheckpoint, params: &Params) -> bool { diff --git a/crates/sequencer/src/duty/extractor.rs b/crates/sequencer/src/duty/extractor.rs index a6bec74e9b..4b3bd7edfc 100644 --- a/crates/sequencer/src/duty/extractor.rs +++ b/crates/sequencer/src/duty/extractor.rs @@ -2,7 +2,7 @@ use strata_db::types::CheckpointConfStatus; use strata_ol_chain_types::{L2BlockId, L2Header}; -use strata_primitives::params::Params; +use strata_params::Params; use strata_state::client_state::ClientState; use strata_storage::L2BlockManager; use tracing::*; diff --git a/crates/sequencer/src/duty/types.rs b/crates/sequencer/src/duty/types.rs index 95e3092c94..0845367019 100644 --- a/crates/sequencer/src/duty/types.rs +++ b/crates/sequencer/src/duty/types.rs @@ -164,7 +164,7 @@ mod tests { use bitcoin::{bip32::Xpriv, Network}; use strata_key_derivation::sequencer::SequencerKeys; - use strata_primitives::buf::Buf32; + use strata_identifiers::Buf32; use zeroize::Zeroize; use super::*; diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index 4a59adb74f..76c3c014f4 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -12,8 +12,10 @@ workspace = true [dependencies] strata-asm-common.workspace = true strata-asm-stf.workspace = true -strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true +strata-identifiers.workspace = true +strata-ol-chain-types.workspace = true strata-primitives.workspace = true anyhow.workspace = true diff --git a/crates/state/src/exec_env.rs b/crates/state/src/exec_env.rs index 62ef69dce8..24c8163f26 100644 --- a/crates/state/src/exec_env.rs +++ b/crates/state/src/exec_env.rs @@ -4,13 +4,15 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use strata_primitives::{buf::Buf32, l1::payload::BlobSpec}; -use crate::{bridge_ops, exec_update, forced_inclusion, state_queue::StateQueue}; +use strata_ol_chain_types::{DepositIntent, StateQueue, UpdateInput}; + +use crate::forced_inclusion; #[derive(Debug, Clone, Eq, PartialEq, BorshDeserialize, BorshSerialize)] pub struct ExecEnvState { /// The last processed exec update, which we've checked to be valid. We may /// not have seen its DA blobs on the L1 yet. - last_update_input: exec_update::UpdateInput, + last_update_input: UpdateInput, /// Current state root. cur_state: Buf32, @@ -25,7 +27,7 @@ pub struct ExecEnvState { /// an update yet. The sequencer should be processing these as soon as /// possible. // TODO make this not pub - pub pending_deposits: StateQueue, + pub pending_deposits: StateQueue, /// Forced inclusions that have been accepted by the CL but not processed by /// a CL payload yet. @@ -37,7 +39,7 @@ pub struct ExecEnvState { impl ExecEnvState { /// Constructs an env state from a starting input and the a state root, /// without producing any blobs, deposits, forced inclusions, etc. - pub fn from_base_input(base_input: exec_update::UpdateInput, state: Buf32) -> Self { + pub fn from_base_input(base_input: UpdateInput, state: Buf32) -> Self { Self { last_update_input: base_input, cur_state: state, @@ -55,18 +57,18 @@ impl ExecEnvState { &self.cur_state } - pub fn pending_deposits(&self) -> &StateQueue { + pub fn pending_deposits(&self) -> &StateQueue { &self.pending_deposits } - pub fn pending_deposits_mut(&mut self) -> &mut StateQueue { + pub fn pending_deposits_mut(&mut self) -> &mut StateQueue { &mut self.pending_deposits } } impl<'a> Arbitrary<'a> for ExecEnvState { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let inp = exec_update::UpdateInput::arbitrary(u)?; + let inp = UpdateInput::arbitrary(u)?; let state = Buf32::arbitrary(u)?; Ok(Self::from_base_input(inp, state)) } diff --git a/crates/state/src/forced_inclusion.rs b/crates/state/src/forced_inclusion.rs index 30b4acaa2e..52f042c38e 100644 --- a/crates/state/src/forced_inclusion.rs +++ b/crates/state/src/forced_inclusion.rs @@ -4,7 +4,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; -use strata_asm_types::L1Tx; +use strata_btc_types::L1Tx; #[derive(Clone, Debug, Eq, PartialEq, Arbitrary, BorshDeserialize, BorshSerialize)] pub struct ForcedInclusion { diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index cb276640a0..4c1f4d1655 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -7,15 +7,12 @@ //! reusing any Reth types. pub mod asm_state; -pub mod bridge_ops; pub mod bridge_state; pub mod client_state; pub mod exec_env; -pub mod exec_update; pub mod forced_inclusion; pub mod operation; pub mod prelude; -pub mod state_queue; use std::{boxed::Box, vec::Vec}; diff --git a/crates/state/src/operation.rs b/crates/state/src/operation.rs index 6dfe86c082..db92c96cab 100644 --- a/crates/state/src/operation.rs +++ b/crates/state/src/operation.rs @@ -5,7 +5,7 @@ use arbitrary::Arbitrary; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use strata_checkpoint_types::Checkpoint; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use crate::client_state::{CheckpointL1Ref, ClientState}; diff --git a/crates/state/src/prelude.rs b/crates/state/src/prelude.rs index 2180bc71f3..11d92300cf 100644 --- a/crates/state/src/prelude.rs +++ b/crates/state/src/prelude.rs @@ -1,3 +1,2 @@ -pub use strata_primitives::l2::L2BlockId; - -pub use crate::state_queue::StateQueue; +pub use strata_ol_chain_types::StateQueue; +pub use strata_identifiers::L2BlockId; diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index d6e116485a..4f7f1b6d78 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -8,8 +8,10 @@ workspace = true [dependencies] strata-asm-types.workspace = true +strata-btc-types.workspace = true strata-checkpoint-types.workspace = true strata-db.workspace = true +strata-identifiers.workspace = true strata-ol-chain-types.workspace = true strata-ol-chainstate-types.workspace = true strata-primitives.workspace = true diff --git a/crates/storage/src/managers/chainstate.rs b/crates/storage/src/managers/chainstate.rs index 04e4147c4f..46b3754a5b 100644 --- a/crates/storage/src/managers/chainstate.rs +++ b/crates/storage/src/managers/chainstate.rs @@ -9,7 +9,7 @@ use strata_db::{ }; use strata_ol_chain_types::L2BlockId; use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use threadpool::ThreadPool; use tracing::*; diff --git a/crates/storage/src/managers/checkpoint.rs b/crates/storage/src/managers/checkpoint.rs index c20dffdcc9..9e6aa26474 100644 --- a/crates/storage/src/managers/checkpoint.rs +++ b/crates/storage/src/managers/checkpoint.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use strata_checkpoint_types::EpochSummary; use strata_db::{traits::CheckpointDatabase, types::CheckpointEntry, DbResult}; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use threadpool::ThreadPool; use crate::{cache, ops}; diff --git a/crates/storage/src/managers/l1.rs b/crates/storage/src/managers/l1.rs index 36028e976f..cde6f6ab3b 100644 --- a/crates/storage/src/managers/l1.rs +++ b/crates/storage/src/managers/l1.rs @@ -1,8 +1,9 @@ use std::sync::Arc; -use strata_asm_types::{L1BlockManifest, L1Tx, L1TxRef}; +use strata_asm_types::L1TxRef; +use strata_btc_types::{L1BlockManifest, L1Tx}; use strata_db::{traits::L1Database, DbError, DbResult}; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use threadpool::ThreadPool; use tracing::error; diff --git a/crates/storage/src/ops/chainstate.rs b/crates/storage/src/ops/chainstate.rs index 29640b433b..b8cca199a4 100644 --- a/crates/storage/src/ops/chainstate.rs +++ b/crates/storage/src/ops/chainstate.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use strata_db::chainstate::*; use strata_ol_chainstate_types::{Chainstate, WriteBatch}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::exec::*; diff --git a/crates/storage/src/ops/checkpoint.rs b/crates/storage/src/ops/checkpoint.rs index a7cfeb510e..80955e96b3 100644 --- a/crates/storage/src/ops/checkpoint.rs +++ b/crates/storage/src/ops/checkpoint.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use strata_checkpoint_types::EpochSummary; use strata_db::{traits::*, types::CheckpointEntry}; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use crate::exec::*; diff --git a/crates/storage/src/ops/l1.rs b/crates/storage/src/ops/l1.rs index 6077de1e80..076819bf85 100644 --- a/crates/storage/src/ops/l1.rs +++ b/crates/storage/src/ops/l1.rs @@ -2,9 +2,10 @@ use std::sync::Arc; -use strata_asm_types::{L1BlockManifest, L1Tx, L1TxRef}; +use strata_asm_types::L1TxRef; +use strata_btc_types::{L1BlockManifest, L1Tx}; use strata_db::traits::*; -use strata_primitives::l1::L1BlockId; +use strata_identifiers::L1BlockId; use crate::exec::*; diff --git a/crates/storage/src/ops/l1tx_broadcast.rs b/crates/storage/src/ops/l1tx_broadcast.rs index dbef46bfc6..2a65804af7 100644 --- a/crates/storage/src/ops/l1tx_broadcast.rs +++ b/crates/storage/src/ops/l1tx_broadcast.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use strata_db::{traits::*, types::L1TxEntry, DbResult}; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::exec::*; diff --git a/crates/storage/src/ops/writer.rs b/crates/storage/src/ops/writer.rs index 0b49e01a4e..db728ecf3a 100644 --- a/crates/storage/src/ops/writer.rs +++ b/crates/storage/src/ops/writer.rs @@ -7,7 +7,7 @@ use strata_db::{ types::{BundledPayloadEntry, IntentEntry}, DbResult, }; -use strata_primitives::buf::Buf32; +use strata_identifiers::Buf32; use crate::exec::*; diff --git a/crates/sync/src/client.rs b/crates/sync/src/client.rs index 098a68e960..5bc8b1daa8 100644 --- a/crates/sync/src/client.rs +++ b/crates/sync/src/client.rs @@ -2,7 +2,7 @@ use std::cmp::min; use futures::stream::{self, Stream, StreamExt}; use strata_ol_chain_types::{L2BlockBundle, L2BlockId}; -use strata_primitives::l2::L2BlockCommitment; +use strata_identifiers::L2BlockCommitment; use strata_rpc_api::StrataApiClient; use tracing::error; diff --git a/crates/sync/src/worker.rs b/crates/sync/src/worker.rs index 2ea26317a4..2c6bffffaf 100644 --- a/crates/sync/src/worker.rs +++ b/crates/sync/src/worker.rs @@ -7,7 +7,7 @@ use futures::StreamExt; use strata_common::{check_and_pause_debug_async, WorkerType}; use strata_consensus_logic::{csm::message::ForkChoiceMessage, sync_manager::SyncManager}; use strata_ol_chain_types::{L2BlockBundle, L2Header}; -use strata_primitives::epoch::EpochCommitment; +use strata_identifiers::EpochCommitment; use strata_status::ChainSyncStatusUpdate; use strata_storage::NodeStorage; use tokio::sync::watch; diff --git a/crates/test-utils/evm-ee/src/lib.rs b/crates/test-utils/evm-ee/src/lib.rs index 9b43c8d2fc..da19c311ca 100644 --- a/crates/test-utils/evm-ee/src/lib.rs +++ b/crates/test-utils/evm-ee/src/lib.rs @@ -8,7 +8,7 @@ use strata_ol_chain_types::{ L1Segment, L2Block, L2BlockBody, L2BlockHeader, L2Header, SignedL2BlockHeader, }; use strata_ol_chainstate_types::Chainstate; -use strata_primitives::buf::{Buf32, Buf64}; +use strata_identifiers::{Buf32, Buf64}; use strata_proofimpl_evm_ee_stf::{ executor::process_block, primitives::{EvmEeProofInput, EvmEeProofOutput}, diff --git a/crates/util/python-utils/src/schnorr.rs b/crates/util/python-utils/src/schnorr.rs index 82c7b6f6de..4354b61131 100644 --- a/crates/util/python-utils/src/schnorr.rs +++ b/crates/util/python-utils/src/schnorr.rs @@ -5,7 +5,7 @@ use secp256k1::{schnorr::Signature, Keypair, SecretKey, SECP256K1}; use strata_crypto::{ sign_schnorr_sig as sign_schnorr_sig_inner, verify_schnorr_sig as verify_schnorr_sig_inner, }; -use strata_primitives::buf::{Buf32, Buf64}; +use strata_identifiers::{Buf32, Buf64}; /// Signs a message using the Schnorr signature scheme. /// diff --git a/crates/zkvm/hosts/src/lib.rs b/crates/zkvm/hosts/src/lib.rs index 23871ccf15..67981dbf6c 100644 --- a/crates/zkvm/hosts/src/lib.rs +++ b/crates/zkvm/hosts/src/lib.rs @@ -1,6 +1,6 @@ //! ZKVM hosts for the Alpen codebase. -use strata_primitives::proof::{ProofKey, ProofZkVm}; +use strata_crypto::proof_vk::{ProofKey, ProofZkVm}; use zkaleido::{VerifyingKey, ZkVmVkProvider}; pub mod native; diff --git a/crates/zkvm/hosts/src/native.rs b/crates/zkvm/hosts/src/native.rs index c624861ecf..1f346de5ce 100644 --- a/crates/zkvm/hosts/src/native.rs +++ b/crates/zkvm/hosts/src/native.rs @@ -1,4 +1,4 @@ -use strata_primitives::proof::ProofContext; +use strata_crypto::proof_vk::ProofContext; use strata_proofimpl_checkpoint::program::CheckpointProgram; use strata_proofimpl_cl_stf::program::ClStfProgram; use strata_proofimpl_evm_ee_stf::program::EvmEeProgram; diff --git a/crates/zkvm/hosts/src/risc0.rs b/crates/zkvm/hosts/src/risc0.rs index 191c925e19..4a9e6e0fae 100644 --- a/crates/zkvm/hosts/src/risc0.rs +++ b/crates/zkvm/hosts/src/risc0.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use strata_primitives::proof::ProofContext; +use strata_crypto::proof_vk::ProofContext; #[cfg(feature = "risc0-builder")] use strata_risc0_guest_builder::{ GUEST_RISC0_CHECKPOINT_ELF, GUEST_RISC0_CL_STF_ELF, GUEST_RISC0_EVM_EE_STF_ELF, diff --git a/crates/zkvm/hosts/src/sp1.rs b/crates/zkvm/hosts/src/sp1.rs index d7fa60c5a0..8723e7038a 100644 --- a/crates/zkvm/hosts/src/sp1.rs +++ b/crates/zkvm/hosts/src/sp1.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use strata_primitives::proof::ProofContext; +use strata_crypto::proof_vk::ProofContext; #[cfg(feature = "sp1-builder")] use strata_sp1_guest_builder::*; use zkaleido_sp1_host::SP1Host;