mirror of
https://github.com/bitcoin/bips.git
synced 2026-04-27 16:38:39 +00:00
596 lines
18 KiB
Rust
596 lines
18 KiB
Rust
|
|
use std::collections::HashMap;
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use log::debug;
|
||
|
|
|
||
|
|
// Add imports for the unified keypair
|
||
|
|
use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey};
|
||
|
|
use bitcoinpqc::{KeyPair, Algorithm};
|
||
|
|
|
||
|
|
/// Enum representing the type of leaf script to create
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum LeafScriptType {
|
||
|
|
/// Script requires only SLH-DSA signature
|
||
|
|
SlhDsaOnly,
|
||
|
|
/// Script requires only Schnorr signature
|
||
|
|
SchnorrOnly,
|
||
|
|
/// Script requires both Schnorr and SLH-DSA signatures (in that order)
|
||
|
|
ConcatenatedSchnorrAndSlhDsaSameLeaf,
|
||
|
|
/// Leaves of TapTree are mixed. Some leaves are locked using Schnorr and others are locked using SLH-DSA
|
||
|
|
Mixed,
|
||
|
|
/// Script type is not applicable
|
||
|
|
NotApplicable,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl LeafScriptType {
|
||
|
|
/// Check if this script type uses SLH-DSA
|
||
|
|
pub fn uses_slh_dsa(&self) -> bool {
|
||
|
|
matches!(self, LeafScriptType::SlhDsaOnly | LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this script type uses Schnorr
|
||
|
|
pub fn uses_schnorr(&self) -> bool {
|
||
|
|
matches!(self, LeafScriptType::SchnorrOnly | LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this script type requires both signature types
|
||
|
|
pub fn requires_both(&self) -> bool {
|
||
|
|
matches!(self, LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if TapTree uses Schnorr for some leaves and SLH-DSA for others
|
||
|
|
pub fn uses_mixed(&self) -> bool {
|
||
|
|
matches!(self, LeafScriptType::Mixed)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this script type is not applicable
|
||
|
|
pub fn is_not_applicable(&self) -> bool {
|
||
|
|
matches!(self, LeafScriptType::NotApplicable)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Convert to string representation for serialization
|
||
|
|
pub fn to_string(&self) -> String {
|
||
|
|
match self {
|
||
|
|
LeafScriptType::SlhDsaOnly => "SLH_DSA_ONLY".to_string(),
|
||
|
|
LeafScriptType::SchnorrOnly => "SCHNORR_ONLY".to_string(),
|
||
|
|
LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf => "CONCATENATED_SCHNORR_AND_SLH_DSA".to_string(),
|
||
|
|
LeafScriptType::Mixed => "MIXED".to_string(),
|
||
|
|
LeafScriptType::NotApplicable => "NOT_APPLICABLE".to_string(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Parse from string representation
|
||
|
|
pub fn from_string(s: &str) -> Self {
|
||
|
|
match s {
|
||
|
|
"SLH_DSA_ONLY" => LeafScriptType::SlhDsaOnly,
|
||
|
|
"SCHNORR_ONLY" => LeafScriptType::SchnorrOnly,
|
||
|
|
"CONCATENATED_SCHNORR_AND_SLH_DSA" => LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf,
|
||
|
|
"MIXED" => LeafScriptType::Mixed,
|
||
|
|
_ => LeafScriptType::NotApplicable,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize)]
|
||
|
|
pub struct TestVectors {
|
||
|
|
pub version: u32,
|
||
|
|
#[serde(rename = "test_vectors")]
|
||
|
|
pub test_vectors: Vec<TestVector>,
|
||
|
|
#[serde(skip, default = "HashMap::new")]
|
||
|
|
pub test_vector_map: HashMap<String, TestVector>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl<'de> Deserialize<'de> for TestVectors {
|
||
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||
|
|
where
|
||
|
|
D: serde::Deserializer<'de>,
|
||
|
|
{
|
||
|
|
#[derive(Deserialize)]
|
||
|
|
struct Helper {
|
||
|
|
version: u32,
|
||
|
|
#[serde(rename = "test_vectors")]
|
||
|
|
test_vectors: Vec<TestVector>,
|
||
|
|
}
|
||
|
|
|
||
|
|
let helper = Helper::deserialize(deserializer)?;
|
||
|
|
|
||
|
|
let mut test_vector_map = HashMap::new();
|
||
|
|
for test in helper.test_vectors.iter() {
|
||
|
|
test_vector_map.insert(test.id.clone(), test.clone());
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(TestVectors {
|
||
|
|
version: helper.version,
|
||
|
|
test_vectors: helper.test_vectors,
|
||
|
|
test_vector_map,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct TestVector {
|
||
|
|
pub id: String,
|
||
|
|
pub objective: String,
|
||
|
|
pub given: TestVectorGiven,
|
||
|
|
pub intermediary: TestVectorIntermediary,
|
||
|
|
pub expected: TestVectorExpected,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct TestVectorGiven {
|
||
|
|
|
||
|
|
#[serde(rename = "internalPubkey")]
|
||
|
|
pub internal_pubkey: Option<String>,
|
||
|
|
|
||
|
|
#[serde(rename = "scriptTree")]
|
||
|
|
pub script_tree: Option<TVScriptTree>,
|
||
|
|
|
||
|
|
#[serde(rename = "scriptInputs")]
|
||
|
|
pub script_inputs: Option<Vec<String>>,
|
||
|
|
#[serde(rename = "scriptHex")]
|
||
|
|
pub script_hex: Option<String>,
|
||
|
|
#[serde(rename = "controlBlock")]
|
||
|
|
pub control_block: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct TestVectorIntermediary {
|
||
|
|
|
||
|
|
#[serde(default)]
|
||
|
|
#[serde(rename = "leafHashes")]
|
||
|
|
pub leaf_hashes: Vec<String>,
|
||
|
|
#[serde(rename = "merkleRoot")]
|
||
|
|
pub merkle_root: Option<String>
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct TestVectorExpected {
|
||
|
|
#[serde(rename = "scriptPubKey")]
|
||
|
|
pub script_pubkey: Option<String>,
|
||
|
|
#[serde(rename = "bip350Address")]
|
||
|
|
pub bip350_address: Option<String>,
|
||
|
|
#[serde(default)]
|
||
|
|
#[serde(rename = "scriptPathControlBlocks")]
|
||
|
|
pub script_path_control_blocks: Option<Vec<String>>,
|
||
|
|
#[serde(rename = "error")]
|
||
|
|
pub error: Option<String>,
|
||
|
|
#[serde(rename = "address")]
|
||
|
|
pub address: Option<String>,
|
||
|
|
#[serde(default)]
|
||
|
|
pub witness: Option<String>
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||
|
|
pub struct TVScriptLeaf {
|
||
|
|
pub id: u8,
|
||
|
|
pub script: String,
|
||
|
|
#[serde(rename = "leafVersion")]
|
||
|
|
pub leaf_version: u8,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Taproot script trees are binary trees, so each branch should have exactly two children.
|
||
|
|
#[derive(Debug, Serialize, Clone)]
|
||
|
|
pub enum TVScriptTree {
|
||
|
|
Leaf(TVScriptLeaf),
|
||
|
|
Branch {
|
||
|
|
|
||
|
|
// Box is used because Rust needs to know the exact size of types at compile time.
|
||
|
|
// Without it, we'd have an infinitely size recursive type.
|
||
|
|
// The enum itself is on the stack, but the Box fields within the Branch variant store pointers to heap-allocated ScriptTree values.
|
||
|
|
left: Box<TVScriptTree>,
|
||
|
|
right: Box<TVScriptTree>,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add custom deserialize implementation
|
||
|
|
impl<'de> Deserialize<'de> for TVScriptTree {
|
||
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||
|
|
where
|
||
|
|
D: serde::Deserializer<'de>,
|
||
|
|
{
|
||
|
|
#[derive(Deserialize)]
|
||
|
|
#[serde(untagged)]
|
||
|
|
enum Helper {
|
||
|
|
Leaf(TVScriptLeaf),
|
||
|
|
Branch(Vec<TVScriptTree>),
|
||
|
|
}
|
||
|
|
|
||
|
|
match Helper::deserialize(deserializer)? {
|
||
|
|
Helper::Leaf(leaf) => Ok(TVScriptTree::Leaf(leaf)),
|
||
|
|
Helper::Branch(v) => {
|
||
|
|
assert!(v.len() == 2, "Branch must have exactly two children");
|
||
|
|
let mut iter = v.into_iter();
|
||
|
|
Ok(TVScriptTree::Branch {
|
||
|
|
left: Box::new(iter.next().unwrap()),
|
||
|
|
right: Box::new(iter.next().unwrap()),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add this enum before the TVScriptTree implementation
|
||
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||
|
|
pub enum Direction {
|
||
|
|
Left,
|
||
|
|
Right,
|
||
|
|
Root,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl TVScriptTree {
|
||
|
|
/// Implements a "post-order" traversal as follows: left, right, branch
|
||
|
|
pub fn traverse_with_depth<F: FnMut(&TVScriptTree, u8, Direction)>(&self, depth: u8, direction: Direction, f: &mut F) {
|
||
|
|
match self {
|
||
|
|
TVScriptTree::Branch { left, right } => {
|
||
|
|
|
||
|
|
right.traverse_with_depth(depth, Direction::Right, f); // Pass Right for right subtree
|
||
|
|
left.traverse_with_depth(depth, Direction::Left, f); // Pass Left for left subtree
|
||
|
|
f(self, depth, direction); // Pass the current node's direction
|
||
|
|
}
|
||
|
|
TVScriptTree::Leaf { .. } => {
|
||
|
|
f(self, depth, direction);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Traverses the tree visiting right subtree leaves first, then left subtree leaves.
|
||
|
|
/// Depth increases by 1 at each branch level.
|
||
|
|
/*
|
||
|
|
root (depth 0)
|
||
|
|
/ \
|
||
|
|
L0 (depth 1) (subtree) (depth 1)
|
||
|
|
/ \
|
||
|
|
L1 (depth 2) L2 (depth 2)
|
||
|
|
|
||
|
|
The new traversal will visit:
|
||
|
|
L1 at depth 2 -> L2 at depth 2 -> L0 at depth 1
|
||
|
|
*/
|
||
|
|
pub fn traverse_with_right_subtree_first<F: FnMut(&TVScriptTree, u8, Direction)>(&self, depth: u8, direction: Direction, f: &mut F) {
|
||
|
|
match self {
|
||
|
|
TVScriptTree::Branch { left, right } => {
|
||
|
|
let next_depth = depth + 1;
|
||
|
|
// Visit right subtree first
|
||
|
|
right.traverse_with_right_subtree_first(next_depth, Direction::Right, f);
|
||
|
|
// Then visit left subtree
|
||
|
|
left.traverse_with_right_subtree_first(next_depth, Direction::Left, f);
|
||
|
|
}
|
||
|
|
TVScriptTree::Leaf { .. } => {
|
||
|
|
f(self, depth, direction);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::fmt::Display for Direction {
|
||
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||
|
|
match self {
|
||
|
|
Direction::Left => write!(f, "L"),
|
||
|
|
Direction::Right => write!(f, "R"),
|
||
|
|
Direction::Root => write!(f, "Root"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct ScriptTreeHashCache {
|
||
|
|
pub leaf_hashes: HashMap<String, String>,
|
||
|
|
pub branch_hashes: HashMap<u8, String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ScriptTreeHashCache {
|
||
|
|
pub fn new() -> Self {
|
||
|
|
Self {
|
||
|
|
leaf_hashes: HashMap::new(),
|
||
|
|
branch_hashes: HashMap::new(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn set_leaf_hash(&mut self, branch_id: u8, direction: Direction, hash: String) {
|
||
|
|
let key = format!("{branch_id}_{direction}");
|
||
|
|
debug!("set_leaf_hash: key: {}, hash: {}", key, hash);
|
||
|
|
self.leaf_hashes.insert(key, hash);
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn set_branch_hash(&mut self, branch_id: u8, hash: String) {
|
||
|
|
self.branch_hashes.insert(branch_id, hash);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn serialize_hex<S>(bytes: &Vec<u8>, s: S) -> Result<S::Ok, S::Error>
|
||
|
|
where
|
||
|
|
S: serde::Serializer,
|
||
|
|
{
|
||
|
|
s.serialize_str(&hex::encode(bytes))
|
||
|
|
}
|
||
|
|
|
||
|
|
fn deserialize_hex<'de, D>(d: D) -> Result<Vec<u8>, D::Error>
|
||
|
|
where
|
||
|
|
D: serde::Deserializer<'de>,
|
||
|
|
{
|
||
|
|
let s = String::deserialize(d)?;
|
||
|
|
hex::decode(s).map_err(serde::de::Error::custom)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct SpendDetails {
|
||
|
|
pub tx_hex: String,
|
||
|
|
#[serde(serialize_with = "serialize_hex")]
|
||
|
|
#[serde(deserialize_with = "deserialize_hex")]
|
||
|
|
pub sighash: Vec<u8>,
|
||
|
|
#[serde(serialize_with = "serialize_hex")]
|
||
|
|
#[serde(deserialize_with = "deserialize_hex")]
|
||
|
|
pub sig_bytes: Vec<u8>,
|
||
|
|
#[serde(serialize_with = "serialize_hex")]
|
||
|
|
#[serde(deserialize_with = "deserialize_hex")]
|
||
|
|
pub derived_witness_vec: Vec<u8>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::process::Termination for SpendDetails {
|
||
|
|
fn report(self) -> std::process::ExitCode {
|
||
|
|
if let Ok(json) = serde_json::to_string_pretty(&self) {
|
||
|
|
println!("{}", json);
|
||
|
|
} else {
|
||
|
|
println!("{:?}", self);
|
||
|
|
}
|
||
|
|
std::process::ExitCode::SUCCESS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct UtxoReturn {
|
||
|
|
|
||
|
|
pub script_pubkey_hex: String,
|
||
|
|
pub bech32m_address: String,
|
||
|
|
pub bitcoin_network: bitcoin::Network,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::process::Termination for UtxoReturn {
|
||
|
|
fn report(self) -> std::process::ExitCode {
|
||
|
|
if let Ok(json) = serde_json::to_string_pretty(&self) {
|
||
|
|
println!("{}", json);
|
||
|
|
} else {
|
||
|
|
println!("{:?}", self);
|
||
|
|
}
|
||
|
|
std::process::ExitCode::SUCCESS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct TaptreeReturn {
|
||
|
|
pub leaf_script_priv_keys_hex: Vec<String>, // Changed to support multiple private keys
|
||
|
|
pub leaf_script_hex: String,
|
||
|
|
pub tree_root_hex: String,
|
||
|
|
pub control_block_hex: String,
|
||
|
|
/// The script type of the leaf being returned (needed for spending)
|
||
|
|
pub leaf_script_type: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::process::Termination for TaptreeReturn {
|
||
|
|
fn report(self) -> std::process::ExitCode {
|
||
|
|
if let Ok(json) = serde_json::to_string_pretty(&self) {
|
||
|
|
println!("{}", json);
|
||
|
|
} else {
|
||
|
|
println!("{:?}", self);
|
||
|
|
}
|
||
|
|
std::process::ExitCode::SUCCESS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct ConstructionReturn {
|
||
|
|
pub taptree_return: TaptreeReturn,
|
||
|
|
pub utxo_return: UtxoReturn,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::process::Termination for ConstructionReturn {
|
||
|
|
fn report(self) -> std::process::ExitCode {
|
||
|
|
if let Ok(json) = serde_json::to_string_pretty(&self) {
|
||
|
|
println!("{}", json);
|
||
|
|
} else {
|
||
|
|
println!("{:?}", self);
|
||
|
|
}
|
||
|
|
std::process::ExitCode::SUCCESS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A unified keypair that can contain either a Schnorr keypair or an SLH-DSA keypair
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub enum UnifiedKeypair {
|
||
|
|
Schnorr(SecretKey, XOnlyPublicKey),
|
||
|
|
SlhDsa(KeyPair),
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A container for multiple keypairs that can be used in a single leaf script
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct MultiKeypair {
|
||
|
|
pub schnorr_keypair: Option<UnifiedKeypair>,
|
||
|
|
pub slh_dsa_keypair: Option<UnifiedKeypair>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl MultiKeypair {
|
||
|
|
/// Create a new MultiKeypair with only a Schnorr keypair
|
||
|
|
pub fn new_schnorr_only(schnorr_keypair: UnifiedKeypair) -> Self {
|
||
|
|
Self {
|
||
|
|
schnorr_keypair: Some(schnorr_keypair),
|
||
|
|
slh_dsa_keypair: None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a new MultiKeypair with only an SLH-DSA keypair
|
||
|
|
pub fn new_slh_dsa_only(slh_dsa_keypair: UnifiedKeypair) -> Self {
|
||
|
|
Self {
|
||
|
|
schnorr_keypair: None,
|
||
|
|
slh_dsa_keypair: Some(slh_dsa_keypair),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a new MultiKeypair with both keypairs
|
||
|
|
pub fn new_combined(schnorr_keypair: UnifiedKeypair, slh_dsa_keypair: UnifiedKeypair) -> Self {
|
||
|
|
Self {
|
||
|
|
schnorr_keypair: Some(schnorr_keypair),
|
||
|
|
slh_dsa_keypair: Some(slh_dsa_keypair),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get all secret key bytes for serialization (in order: schnorr, then slh_dsa if present)
|
||
|
|
pub fn secret_key_bytes(&self) -> Vec<Vec<u8>> {
|
||
|
|
let mut result = Vec::new();
|
||
|
|
if let Some(ref schnorr) = self.schnorr_keypair {
|
||
|
|
result.push(schnorr.secret_key_bytes());
|
||
|
|
}
|
||
|
|
if let Some(ref slh_dsa) = self.slh_dsa_keypair {
|
||
|
|
result.push(slh_dsa.secret_key_bytes());
|
||
|
|
}
|
||
|
|
result
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get all public key bytes for script construction (in order: schnorr, then slh_dsa if present)
|
||
|
|
pub fn public_key_bytes(&self) -> Vec<Vec<u8>> {
|
||
|
|
let mut result = Vec::new();
|
||
|
|
if let Some(ref schnorr) = self.schnorr_keypair {
|
||
|
|
result.push(schnorr.public_key_bytes());
|
||
|
|
}
|
||
|
|
if let Some(ref slh_dsa) = self.slh_dsa_keypair {
|
||
|
|
result.push(slh_dsa.public_key_bytes());
|
||
|
|
}
|
||
|
|
result
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this contains a Schnorr keypair
|
||
|
|
pub fn has_schnorr(&self) -> bool {
|
||
|
|
self.schnorr_keypair.is_some()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this contains an SLH-DSA keypair
|
||
|
|
pub fn has_slh_dsa(&self) -> bool {
|
||
|
|
self.slh_dsa_keypair.is_some()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the Schnorr keypair if present
|
||
|
|
pub fn schnorr_keypair(&self) -> Option<&UnifiedKeypair> {
|
||
|
|
self.schnorr_keypair.as_ref()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the SLH-DSA keypair if present
|
||
|
|
pub fn slh_dsa_keypair(&self) -> Option<&UnifiedKeypair> {
|
||
|
|
self.slh_dsa_keypair.as_ref()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Information about a single leaf in a mixed-type tree
|
||
|
|
/// Used when different leaves in the same tree use different algorithms
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
pub struct MixedLeafInfo {
|
||
|
|
/// The leaf index in the tree
|
||
|
|
pub leaf_index: u32,
|
||
|
|
/// The script type for this specific leaf
|
||
|
|
pub leaf_script_type: LeafScriptType,
|
||
|
|
/// The keypairs for this leaf
|
||
|
|
pub keypairs: MultiKeypair,
|
||
|
|
/// The script for this leaf
|
||
|
|
pub script: Vec<u8>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl MixedLeafInfo {
|
||
|
|
/// Create a new MixedLeafInfo for a Schnorr-only leaf
|
||
|
|
pub fn new_schnorr(leaf_index: u32, keypairs: MultiKeypair, script: Vec<u8>) -> Self {
|
||
|
|
Self {
|
||
|
|
leaf_index,
|
||
|
|
leaf_script_type: LeafScriptType::SchnorrOnly,
|
||
|
|
keypairs,
|
||
|
|
script,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a new MixedLeafInfo for an SLH-DSA-only leaf
|
||
|
|
pub fn new_slh_dsa(leaf_index: u32, keypairs: MultiKeypair, script: Vec<u8>) -> Self {
|
||
|
|
Self {
|
||
|
|
leaf_index,
|
||
|
|
leaf_script_type: LeafScriptType::SlhDsaOnly,
|
||
|
|
keypairs,
|
||
|
|
script,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a new MixedLeafInfo for a combined Schnorr+SLH-DSA leaf
|
||
|
|
pub fn new_combined(leaf_index: u32, keypairs: MultiKeypair, script: Vec<u8>) -> Self {
|
||
|
|
Self {
|
||
|
|
leaf_index,
|
||
|
|
leaf_script_type: LeafScriptType::ConcatenatedSchnorrAndSlhDsaSameLeaf,
|
||
|
|
keypairs,
|
||
|
|
script,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the secret key bytes for this leaf
|
||
|
|
pub fn secret_key_bytes(&self) -> Vec<Vec<u8>> {
|
||
|
|
self.keypairs.secret_key_bytes()
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the public key bytes for this leaf
|
||
|
|
pub fn public_key_bytes(&self) -> Vec<Vec<u8>> {
|
||
|
|
self.keypairs.public_key_bytes()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl UnifiedKeypair {
|
||
|
|
/// Create a new Schnorr keypair
|
||
|
|
pub fn new_schnorr(secret_key: SecretKey, public_key: XOnlyPublicKey) -> Self {
|
||
|
|
UnifiedKeypair::Schnorr(secret_key, public_key)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a new SLH-DSA keypair
|
||
|
|
pub fn new_slh_dsa(keypair: KeyPair) -> Self {
|
||
|
|
UnifiedKeypair::SlhDsa(keypair)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the secret key bytes for serialization
|
||
|
|
pub fn secret_key_bytes(&self) -> Vec<u8> {
|
||
|
|
match self {
|
||
|
|
UnifiedKeypair::Schnorr(secret_key, _) => secret_key.secret_bytes().to_vec(),
|
||
|
|
UnifiedKeypair::SlhDsa(keypair) => keypair.secret_key.bytes.clone(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the public key bytes for script construction
|
||
|
|
pub fn public_key_bytes(&self) -> Vec<u8> {
|
||
|
|
match self {
|
||
|
|
UnifiedKeypair::Schnorr(_, public_key) => public_key.serialize().to_vec(),
|
||
|
|
UnifiedKeypair::SlhDsa(keypair) => keypair.public_key.bytes.clone(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the algorithm type
|
||
|
|
pub fn algorithm(&self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
UnifiedKeypair::Schnorr(_, _) => "Schnorr",
|
||
|
|
UnifiedKeypair::SlhDsa(_) => "SLH-DSA",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this is a Schnorr keypair
|
||
|
|
pub fn is_schnorr(&self) -> bool {
|
||
|
|
matches!(self, UnifiedKeypair::Schnorr(_, _))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if this is an SLH-DSA keypair
|
||
|
|
pub fn is_slh_dsa(&self) -> bool {
|
||
|
|
matches!(self, UnifiedKeypair::SlhDsa(_))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the underlying Schnorr keypair if this is a Schnorr keypair
|
||
|
|
pub fn as_schnorr(&self) -> Option<(&SecretKey, &XOnlyPublicKey)> {
|
||
|
|
match self {
|
||
|
|
UnifiedKeypair::Schnorr(secret_key, public_key) => Some((secret_key, public_key)),
|
||
|
|
_ => None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get the underlying SLH-DSA keypair if this is an SLH-DSA keypair
|
||
|
|
pub fn as_slh_dsa(&self) -> Option<&KeyPair> {
|
||
|
|
match self {
|
||
|
|
UnifiedKeypair::SlhDsa(keypair) => Some(keypair),
|
||
|
|
_ => None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|