use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::schnorr::{Signature, TapTweak};
use bitcoin::taproot::{TapBranchHash, TapLeafHash, TapTweakHash};
use bitcoin::{Address, Network, Script, Transaction, TxIn, TxOut, Witness};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::crypto::utils::constant_time_eq;
use crate::crypto::random::secure_random_bytes;
use crate::web5::identity::DID;
use crate::web5::anchoring::AnchorData;
use crate::web5::schnorr_aggregation::{AggregationMode, SignableInput, SignatureAggregator};
use serde::{Serialize, Deserialize};
pub const TAPROOT_SILENT_LEAF: u8 = 0xc0;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Outcome {
pub id: String,
pub value: Vec<u8>,
pub probability: f64,
pub payout_ratio: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Oracle {
pub did: DID,
pub public_key: PublicKey,
pub endpoints: Vec<String>,
pub supports_silent_leaf: bool,
pub attestation_timestamp: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct AdaptorSignature {
pub adapted_signature: Vec<u8>,
pub outcome: Outcome,
pub adaptor_point: PublicKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DLCConfig {
pub oracle: Oracle,
pub outcomes: Vec<Outcome>,
pub participants: Vec<PublicKey>,
pub maturity_time: u64,
pub collateral: u64,
pub network: Network,
pub use_taproot: bool,
pub use_silent_leaf: bool,
pub use_signature_aggregation: bool,
}
#[derive(Debug, Clone)]
pub struct TaprootDLC {
pub config: DLCConfig,
pub contract_id: String,
pub funding_transaction: Option<Transaction>,
pub contract_output_script: Script,
pub adaptor_signatures: HashMap<String, AdaptorSignature>,
pub merkle_tree: TapBranchHash,
pub taproot_spend_key: PublicKey,
pub created_at: u64,
pub aggregator: Option<SignatureAggregator>,
pub attested_outcome: Option<String>,
}
const SECURE_RANDOM_SOURCE: &str = "OsRng"; const USE_CONSTANT_TIME: bool = true; const USER_SELF_SOVEREIGNTY: bool = true; const PERMISSIONLESS_OPERATION: bool = true;
pub fn create_indistinguishable_output(internal_key: &PublicKey, merkle_root: Option<TapBranchHash>, network: Network) -> Address {
let secp = Secp256k1::new();
let tweaked_key = match merkle_root {
Some(root) => {
internal_key.tap_tweak(&secp, root)
},
None => {
let key_bytes = internal_key.serialize();
let mut hasher = sha256::Hash::engine();
hasher.input(&key_bytes);
hasher.input(&[0xc0]); let dummy_hash = sha256::Hash::from_engine(hasher);
let dummy_branch = TapBranchHash::from_inner(dummy_hash.into_inner());
internal_key.tap_tweak(&secp, dummy_branch)
}
};
Address::p2tr(
&secp,
tweaked_key.0, tweaked_key.1, network )
}
pub fn generate_taproot_commitment(_data: &[u8], salt: &[u8; 32]) -> TapBranchHash {
let mut engine = sha256::Hash::engine();
engine.input(b"taproot_commitment");
engine.input(salt);
engine.input(data);
let hash = sha256::Hash::from_engine(engine);
TapBranchHash::from_inner(hash.into_inner())
}
impl TaprootDLC {
pub fn new(config: DLCConfig) -> Result<Self, &'static str> {
if config.outcomes.is_empty() {
return Err("At least one outcome must be specified");
}
if config.participants.len() < 2 {
return Err("At least two participants required");
}
let mut random_bytes = [0u8; 32];
secure_random_bytes(&mut random_bytes).map_err(|_| "Failed to generate secure randomness")?;
let contract_id = hex::encode(random_bytes);
if !PERMISSIONLESS_OPERATION {
return Err("Permissionless operation disabled - violates Bitcoin principles");
}
if !USER_SELF_SOVEREIGNTY {
return Err("User self-sovereignty disabled - violates Bitcoin principles");
}
let secp = Secp256k1::new();
let mut outcome_scripts = Vec::new();
for outcome in &config.outcomes {
let script = Self::create_outcome_script(&outcome, &config.participants, &config.oracle)?;
outcome_scripts.push(script);
}
let leaf_version = if config.use_silent_leaf { TAPROOT_SILENT_LEAF } else { TAPROOT_VER_LEAF };
let tap_leaves: Vec<(TapLeafHash, Script)> = outcome_scripts.iter()
.map(|script| (TapLeafHash::from_script(script, leaf_version), script.clone()))
.collect();
let merkle_tree = Self::compute_merkle_tree(&tap_leaves)?;
let internal_key = Self::combine_participant_keys(&config.participants, &secp)?;
let taproot_spend_key = internal_key.tap_tweak(&secp, merkle_tree);
let taproot_address = Address::p2tr(&secp, taproot_spend_key.0, taproot_spend_key.1, config.network);
let contract_output_script = taproot_address.script_pubkey();
let now = SystemTime::now().duration_since(UNIX_EPOCH)
.map_err(|_| "Clock error")?
.as_secs();
let aggregator = if config.use_signature_aggregation {
Some(SignatureAggregator::new(AggregationMode::CrossInput))
} else {
None
};
Ok(Self {
config,
contract_id,
funding_transaction: None,
contract_output_script,
adaptor_signatures: HashMap::new(),
merkle_tree,
taproot_spend_key: taproot_spend_key.0,
created_at: now,
aggregator,
attested_outcome: None,
})
}
fn create_outcome_script(
outcome: &Outcome,
participants: &[PublicKey],
oracle: &Oracle
) -> Result<Script, &'static str> {
let mut script_builder = bitcoin::blockdata::script::Builder::new();
script_builder = script_builder
.push_slice(&oracle.public_key.serialize())
.push_opcode(bitcoin::blockdata::opcodes::all::OP_CHECKSIG);
for pubkey in participants {
script_builder = script_builder
.push_slice(&pubkey.serialize())
.push_opcode(bitcoin::blockdata::opcodes::all::OP_CHECKSIGADD);
}
script_builder = script_builder
.push_int(participants.len() as i64)
.push_opcode(bitcoin::blockdata::opcodes::all::OP_EQUAL);
script_builder = script_builder
.push_slice(&outcome.value)
.push_opcode(bitcoin::blockdata::opcodes::all::OP_DROP);
Ok(script_builder.into_script())
}
fn compute_merkle_tree(
tap_leaves: &[(TapLeafHash, Script)]
) -> Result<TapBranchHash, &'static str> {
if tap_leaves.is_empty() {
return Err("No tap leaves provided");
}
if tap_leaves.len() == 1 {
return Ok(TapBranchHash::from_leaf_hash(tap_leaves[0].0));
}
let mut branches = Vec::new();
for i in (0..tap_leaves.len()).step_by(2) {
if i + 1 < tap_leaves.len() {
let branch = TapBranchHash::from_node_hashes(
tap_leaves[i].0,
tap_leaves[i + 1].0
);
branches.push(branch);
} else {
branches.push(TapBranchHash::from_leaf_hash(tap_leaves[i].0));
}
}
while branches.len() > 1 {
let mut new_branches = Vec::new();
for i in (0..branches.len()).step_by(2) {
if i + 1 < branches.len() {
let branch = TapBranchHash::from_node_hashes(
branches[i],
branches[i + 1]
);
new_branches.push(branch);
} else {
new_branches.push(branches[i]);
}
}
branches = new_branches;
}
Ok(branches[0])
}
fn combine_participant_keys(
participants: &[PublicKey],
secp: &Secp256k1<bitcoin::secp256k1::All>
) -> Result<PublicKey, &'static str> {
if participants.is_empty() {
return Err("No participant keys provided");
}
if participants.len() == 1 {
return Ok(participants[0]);
}
let mut combined_key = participants[0];
for i in 1..participants.len() {
combined_key = PublicKey::from_combination(secp, &[combined_key, participants[i]])
.map_err(|_| "Failed to combine public keys")?;
}
Ok(combined_key)
}
pub fn create_adaptor_signature(
&self,
outcome: &Outcome,
private_key: &SecretKey,
message: &Message,
secp: &Secp256k1<bitcoin::secp256k1::All>,
) -> Result<AdaptorSignature, &'static str> {
let adaptor_scalar = if USE_CONSTANT_TIME {
SecretKey::new(&mut rand::rngs::OsRng)
} else {
SecretKey::new(&mut rand::thread_rng())
};
let adaptor_point = PublicKey::from_secret_key(secp, &adaptor_scalar);
let adaptor_nonce = SecretKey::new(&mut rand::thread_rng());
let signature = secp.sign_schnorr_with_nonce(message, private_key, &adaptor_nonce);
let adapted_signature = signature.as_ref().to_vec();
Ok(AdaptorSignature {
adapted_signature,
outcome: outcome.clone(),
adaptor_point,
})
}
pub fn add_adaptor_signature(&mut self, outcome_id: String, signature: AdaptorSignature) {
self.adaptor_signatures.insert(outcome_id, signature);
}
pub fn verify_adaptor_signature(
&self,
adaptor_sig: &AdaptorSignature,
pubkey: &PublicKey,
message: &Message,
secp: &Secp256k1<bitcoin::secp256k1::All>,
) -> bool {
if let Ok(signature) = Signature::from_slice(&adaptor_sig.adapted_signature) {
secp.verify_schnorr(&signature, message, pubkey).is_ok()
} else {
false
}
}
pub fn execute(
&mut self,
outcome_id: &str,
oracle__signature: &[u8],
participant_signatures: &[Vec<u8>],
) -> Result<Transaction, &'static str> {
let adaptor_sig = self.adaptor_signatures.get(outcome_id)
.ok_or("Unknown outcome")?;
if !self.verify_oracle_signature(outcome_id, oracle_signature)? {
return Err("Invalid oracle signature");
}
self.attested_outcome = Some(outcome_id.to_string());
let settlement_tx = self.create_settlement_transaction(outcome_id, participant_signatures)?;
if let Some(aggregator) = &self.aggregator {
let signable_inputs = self.prepare_signable_inputs(&settlement_tx, participant_signatures)?;
let aggregated_witnesses = aggregator.sign_transaction(&settlement_tx, &signable_inputs)
.map_err(|_| "Signature aggregation failed")?;
let final_tx = self.apply_witness_data(settlement_tx, aggregated_witnesses)?;
return Ok(final_tx);
}
Ok(settlement_tx)
}
pub fn anchor_to_web5_did(&self, did: &DID) -> Result<AnchorData, &'static str> {
let anchor_data = AnchorData {
did: did.clone(),
contract_id: self.contract_id.clone(),
timestamp: self.created_at,
data_type: "dlc".to_string(),
commitment: hex::encode(self.merkle_tree),
additional_data: Some(serde_json::to_string(&self.config.outcomes).unwrap_or_default()),
};
Ok(anchor_data)
}
pub fn verify_oracle_signature(&self, outcome_id: &str, _signature: &[u8]) -> Result<bool, &'static str> {
let secp = Secp256k1::new();
let outcome = self.config.outcomes.iter()
.find(|o| o.id == outcome_id)
.ok_or("Unknown outcome")?;
let message_str = format!("{}.{}:{}",
self.contract_id,
outcome_id,
hex::encode(&outcome.value));
let msg = Message::from_hashed_data::<bitcoin::hashes::sha256::Hash>(message_str.as_bytes());
let oracle_sig = Signature::from_slice(signature)
.map_err(|_| "Invalid signature format")?;
if USE_CONSTANT_TIME {
let verification_result = secp.verify_schnorr(&oracle_sig, &msg, &self.config.oracle.public_key);
if verification_result.is_ok() {
return Ok(constant_time_eq(
&[1u8], &[if verification_result.is_ok() { 1u8 } else { 0u8 }] ));
}
return Ok(false);
} else {
let verification_result = secp.verify_schnorr(&oracle_sig, &msg, &self.config.oracle.public_key);
Ok(verification_result.is_ok())
}
}
fn create_settlement_transaction(
&self,
outcome_id: &str,
participant_signatures: &[Vec<u8>],
) -> Result<Transaction, &'static str> {
let funding_tx = self.funding_transaction.as_ref()
.ok_or("No funding transaction available")?;
let outcome = self.config.outcomes.iter()
.find(|o| o.id == outcome_id)
.ok_or("Unknown outcome")?;
let payouts = self.calculate_payouts(outcome);
let mut outputs = Vec::new();
for (pubkey, amount) in payouts {
let address = Address::p2tr_tweaked(
bitcoin::XOnlyPublicKey::from_slice(&pubkey.serialize()).unwrap(),
self.config.network
);
outputs.push(TxOut {
value: amount,
script_pubkey: address.script_pubkey(),
});
}
let mut inputs = Vec::new();
for (i, output) in funding_tx.output.iter().enumerate() {
if output.script_pubkey == self.contract_output_script {
inputs.push(TxIn {
previous_output: bitcoin::OutPoint {
txid: funding_tx.txid(),
vout: i as u32,
},
script_sig: Script::new(),
sequence: 0xFFFFFFFE, witness: Witness::new(),
});
}
}
let settlement_tx = Transaction {
version: 2,
lock_time: self.config.maturity_time as u32,
input: inputs,
output: outputs,
};
Ok(settlement_tx)
}
fn calculate_payouts(&self, outcome: &Outcome) -> Vec<(PublicKey, u64)> {
let total_collateral = self.config.collateral;
let mut payouts = Vec::new();
if outcome.payout_ratio > 0.0 {
payouts.push((self.config.participants[0], total_collateral));
} else {
payouts.push((self.config.participants[1], total_collateral));
}
payouts
}
fn prepare_signable_inputs(
&self,
transaction: &Transaction,
signatures: &[Vec<u8>],
) -> Result<Vec<SignableInput>, &'static str> {
let mut signable_inputs = Vec::new();
let secp = Secp256k1::new();
let sk = SecretKey::new(&mut rand::thread_rng());
let pk = PublicKey::from_secret_key(&secp, &sk);
for (i, input) in transaction.input.iter().enumerate() {
signable_inputs.push(SignableInput {
index: i,
public_key: pk,
private_key: sk,
value: self.config.collateral,
script: self.contract_output_script.clone(),
sighash_type: 1, });
}
Ok(signable_inputs)
}
fn apply_witness_data(
&self,
mut transaction: Transaction,
witnesses: HashMap<usize, Witness>,
) -> Result<Transaction, &'static str> {
for (index, witness) in witnesses {
if index < transaction.input.len() {
transaction.input[index].witness = witness;
} else {
return Err("Invalid witness index");
}
}
Ok(transaction)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::secp256k1::{Secp256k1, SecretKey};
#[test]
fn test_create_taproot_dlc() {
let secp = Secp256k1::new();
let alice_sk = SecretKey::new(&mut rand::thread_rng());
let alice_pk = PublicKey::from_secret_key(&secp, &alice_sk);
let bob_sk = SecretKey::new(&mut rand::thread_rng());
let bob_pk = PublicKey::from_secret_key(&secp, &bob_sk);
let oracle_sk = SecretKey::new(&mut rand::thread_rng());
let oracle_pk = PublicKey::from_secret_key(&secp, &oracle_sk);
let oracle = Oracle {
did: DID::new("did:web5:example").unwrap(),
public_key: oracle_pk,
endpoints: vec!["https://oracle.example.com".to_string()],
supports_silent_leaf: true,
attestation_timestamp: None,
};
let outcomes = vec![
Outcome {
id: "win".to_string(),
value: vec![1],
probability: 0.5,
payout_ratio: 2.0,
},
Outcome {
id: "lose".to_string(),
value: vec![0],
probability: 0.5,
payout_ratio: 0.0,
},
];
let config = DLCConfig {
oracle,
outcomes,
participants: vec![alice_pk, bob_pk],
maturity_time: 0,
collateral: 100000,
network: Network::Testnet,
use_taproot: true,
use_silent_leaf: true,
use_signature_aggregation: true,
};
let dlc_result = TaprootDLC::new(config);
assert!(dlc_result.is_ok());
let dlc = dlc_result.unwrap();
assert!(!dlc.contract_id.is_empty());
assert!(dlc.created_at > 0);
assert!(!dlc.contract_output_script.is_empty());
}
}