use crate::protocol::ark::{ArkManager, VUtxoDescriptor, VtxoTreeNode};
use crate::protocol::bitvm::{BitVmChallenge, BitVmManager};
use crate::{ConclaveError, ConclaveResult};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChallengePhase {
None,
Commitment,
Challenge,
ResolvedPenalty,
ResolvedRelease,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitVm2ChallengeStatus {
pub phase: ChallengePhase,
pub commitment_txid: Option<String>,
pub challenge_txid: Option<String>,
pub challenge_block: Option<u64>,
pub resolution: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitVm2ForfeitTransaction {
pub vutxo: VUtxoDescriptor,
pub tree_root: String,
pub commitment_hash: [u8; 32],
pub challenge_window: u32,
pub csv_delay: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitVm2Commitment {
pub state_root_hash: [u8; 32],
pub vtxo_count: u32,
pub merkle_root: [u8; 32],
pub taproot_internal_key: [u8; 32],
pub block_height: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitVm2ChallengeResponse {
pub tap_index: u32,
pub snark_proof: Vec<u8>,
pub witness: Vec<Vec<u8>>,
pub expected_output_hash: [u8; 32],
}
pub struct BitVm2Orchestrator {
ark_manager: Arc<ArkManager>,
#[allow(dead_code)]
bitvm_manager: Arc<BitVmManager>,
active_challenges: std::collections::HashMap<String, BitVm2ChallengeStatus>,
}
impl BitVm2Orchestrator {
pub fn new(ark_manager: Arc<ArkManager>, bitvm_manager: Arc<BitVmManager>) -> Self {
Self {
ark_manager,
bitvm_manager,
active_challenges: std::collections::HashMap::new(),
}
}
pub fn create_forfeit_with_commitment(
&self,
vutxo: VUtxoDescriptor,
vtxo_tree: VtxoTreeNode,
state_root_hash: [u8; 32],
taproot_internal_key: [u8; 32],
) -> ConclaveResult<BitVm2ForfeitTransaction> {
let merkle_root = self.calculate_tree_root(&vtxo_tree)?;
let mut commitment_data = Vec::new();
commitment_data.extend_from_slice(&state_root_hash);
commitment_data.extend_from_slice(&merkle_root);
commitment_data.extend_from_slice(&taproot_internal_key);
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(&commitment_data);
let commitment_hash = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&commitment_hash);
Ok(BitVm2ForfeitTransaction {
vutxo,
tree_root: hex::encode(merkle_root),
commitment_hash: hash,
challenge_window: 42, csv_delay: 144, })
}
pub fn post_commitment(&mut self, commitment: BitVm2Commitment) -> ConclaveResult<String> {
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(commitment.state_root_hash);
hasher.update(commitment.block_height.to_le_bytes());
let commitment_id = hex::encode(hasher.finalize());
self.active_challenges.insert(
commitment_id.clone(),
BitVm2ChallengeStatus {
phase: ChallengePhase::Commitment,
commitment_txid: None,
challenge_txid: None,
challenge_block: None,
resolution: None,
},
);
Ok(commitment_id)
}
pub fn challenge_commitment(
&mut self,
commitment_id: &str,
response: BitVm2ChallengeResponse,
) -> ConclaveResult<()> {
let status = self
.active_challenges
.get_mut(commitment_id)
.ok_or(ConclaveError::InvalidPayload)?;
if status.phase != ChallengePhase::Commitment {
return Err(ConclaveError::InvalidPayload);
}
if response.tap_index >= 364 {
return Err(ConclaveError::InvalidPayload);
}
let _bitvm_challenge = BitVmChallenge {
challenge_hash: response.expected_output_hash,
tap_index: response.tap_index,
total_taps: 364,
};
status.phase = ChallengePhase::Challenge;
status.challenge_txid = Some(hex::encode(
response
.snark_proof
.iter()
.take(32)
.cloned()
.collect::<Vec<_>>(),
));
status.challenge_block = Some(0);
Ok(())
}
pub fn resolve_challenge(
&mut self,
commitment_id: &str,
operator_punished: bool,
_block_height: u64,
) -> ConclaveResult<()> {
let status = self
.active_challenges
.get_mut(commitment_id)
.ok_or(ConclaveError::InvalidPayload)?;
if status.phase != ChallengePhase::Challenge {
return Err(ConclaveError::InvalidPayload);
}
if operator_punished {
status.phase = ChallengePhase::ResolvedPenalty;
status.resolution = Some("Operator punished - forfeit released".to_string());
} else {
status.phase = ChallengePhase::ResolvedRelease;
status.resolution = Some("Challenge rejected - commitment valid".to_string());
}
Ok(())
}
pub fn get_challenge_status(
&self,
commitment_id: &str,
) -> ConclaveResult<BitVm2ChallengeStatus> {
self.active_challenges
.get(commitment_id)
.cloned()
.ok_or(ConclaveError::InvalidPayload)
}
pub fn is_within_challenge_window(
&self,
commitment_id: &str,
_current_block: u64,
) -> ConclaveResult<bool> {
let status = self.get_challenge_status(commitment_id)?;
if status.phase != ChallengePhase::Commitment {
return Ok(false);
}
Ok(true)
}
pub fn sign_forfeit(
&self,
forfeit_tx: &BitVm2ForfeitTransaction,
derivation_path: &str,
) -> ConclaveResult<String> {
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(forfeit_tx.commitment_hash);
hasher.update(forfeit_tx.vutxo.vutxo_id.as_bytes());
let mut tx_hash = [0u8; 32];
tx_hash.copy_from_slice(&hasher.finalize());
self.ark_manager
.sign_forfeit_transaction(tx_hash, derivation_path)
}
fn calculate_tree_root(&self, tree: &VtxoTreeNode) -> ConclaveResult<[u8; 32]> {
if tree.is_leaf {
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(tree.tx_id.as_bytes());
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
Ok(hash)
} else {
let left_hash =
self.calculate_tree_root(tree.left.as_ref().ok_or(ConclaveError::InvalidPayload)?)?;
let right_hash = self
.calculate_tree_root(tree.right.as_ref().ok_or(ConclaveError::InvalidPayload)?)?;
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(left_hash);
hasher.update(right_hash);
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
Ok(hash)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::enclave::cloud::CloudEnclave;
fn create_test_orchestrator() -> BitVm2Orchestrator {
let enclave = Arc::new(CloudEnclave::new("http://localhost".to_string()).unwrap());
let ark = Arc::new(ArkManager::new(enclave.clone()));
let bitvm = Arc::new(BitVmManager::new(enclave));
BitVm2Orchestrator::new(ark, bitvm)
}
#[test]
fn test_forfeit_with_commitment() {
let orch = create_test_orchestrator();
let vutxo = VUtxoDescriptor {
vutxo_id: "test_vutxo_1".to_string(),
amount: 100000,
derivation_index: 0,
address: "bc1q_test".to_string(),
};
let tree = VtxoTreeNode {
tx_id: "root".to_string(),
left: Some(Box::new(VtxoTreeNode {
tx_id: "left".to_string(),
left: None,
right: None,
is_leaf: true,
})),
right: Some(Box::new(VtxoTreeNode {
tx_id: "right".to_string(),
left: None,
right: None,
is_leaf: true,
})),
is_leaf: false,
};
let state_hash = [0u8; 32];
let taproot_key = [1u8; 32];
let forfeit = orch
.create_forfeit_with_commitment(vutxo.clone(), tree, state_hash, taproot_key)
.unwrap();
assert_eq!(forfeit.vutxo.vutxo_id, "test_vutxo_1");
assert_eq!(forfeit.challenge_window, 42);
assert_eq!(forfeit.csv_delay, 144);
}
#[test]
fn test_commitment_lifecycle() {
let mut orch = create_test_orchestrator();
let commitment = BitVm2Commitment {
state_root_hash: [2u8; 32],
vtxo_count: 100,
merkle_root: [3u8; 32],
taproot_internal_key: [4u8; 32],
block_height: 850000,
};
let commitment_id = orch.post_commitment(commitment).unwrap();
assert!(!commitment_id.is_empty());
let status = orch.get_challenge_status(&commitment_id).unwrap();
assert_eq!(status.phase, ChallengePhase::Commitment);
let response = BitVm2ChallengeResponse {
tap_index: 100,
snark_proof: vec![5u8; 64],
witness: vec![vec![6u8; 32]],
expected_output_hash: [7u8; 32],
};
orch.challenge_commitment(&commitment_id, response).unwrap();
let status = orch.get_challenge_status(&commitment_id).unwrap();
assert_eq!(status.phase, ChallengePhase::Challenge);
orch.resolve_challenge(&commitment_id, true, 850100)
.unwrap();
let status = orch.get_challenge_status(&commitment_id).unwrap();
assert_eq!(status.phase, ChallengePhase::ResolvedPenalty);
}
#[test]
fn test_invalid_challenge() {
let mut orch = create_test_orchestrator();
let response = BitVm2ChallengeResponse {
tap_index: 400, snark_proof: vec![0u8; 64],
witness: vec![],
expected_output_hash: [0u8; 32],
};
let commitment = BitVm2Commitment {
state_root_hash: [0u8; 32],
vtxo_count: 10,
merkle_root: [0u8; 32],
taproot_internal_key: [0u8; 32],
block_height: 850000,
};
let commitment_id = orch.post_commitment(commitment).unwrap();
let result = orch.challenge_commitment(&commitment_id, response);
assert!(result.is_err());
}
}