use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::hash::Hash;
use crate::tagged_hash::csv_tagged_hash;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DAGNode {
pub node_id: Hash,
pub bytecode: Vec<u8>,
pub signatures: Vec<Vec<u8>>,
pub witnesses: Vec<Vec<u8>>,
pub parents: Vec<Hash>,
}
impl DAGNode {
pub fn new(
node_id: Hash,
bytecode: Vec<u8>,
signatures: Vec<Vec<u8>>,
witnesses: Vec<Vec<u8>>,
parents: Vec<Hash>,
) -> Self {
Self {
node_id,
bytecode,
signatures,
witnesses,
parents,
}
}
pub fn hash(&self) -> Hash {
let mut data = Vec::new();
data.extend_from_slice(self.node_id.as_bytes());
data.extend_from_slice(&self.bytecode);
for sig in &self.signatures {
data.extend_from_slice(sig);
}
for witness in &self.witnesses {
data.extend_from_slice(witness);
}
for parent in &self.parents {
data.extend_from_slice(parent.as_bytes());
}
Hash::new(csv_tagged_hash("dag-node", &data))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DAGSegment {
pub nodes: Vec<DAGNode>,
pub root_commitment: Hash,
}
impl DAGSegment {
pub fn new(nodes: Vec<DAGNode>, root_commitment: Hash) -> Self {
Self {
nodes,
root_commitment,
}
}
pub fn validate_structure(&self) -> Result<(), &'static str> {
let node_ids: alloc::collections::BTreeSet<_> =
self.nodes.iter().map(|n| n.node_id).collect();
for node in &self.nodes {
for parent in &node.parents {
if !node_ids.contains(parent) {
return Err("Parent node not found in DAG segment");
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_dag_node_creation() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02, 0x03],
vec![vec![0xAB; 64]],
vec![vec![0xCD; 32]],
vec![],
);
assert_eq!(node.bytecode, vec![0x01, 0x02, 0x03]);
}
#[test]
fn test_dag_node_hash() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![],
vec![],
vec![],
);
let hash = node.hash();
assert_eq!(hash.as_bytes().len(), 32);
}
#[test]
fn test_dag_segment_validation() {
let parent = DAGNode::new(Hash::new([1u8; 32]), vec![], vec![], vec![], vec![]);
let child = DAGNode::new(
Hash::new([2u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([1u8; 32])],
);
let segment = DAGSegment::new(vec![parent, child], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_segment_invalid_parent() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([99u8; 32])], );
let segment = DAGSegment::new(vec![node], Hash::zero());
assert!(segment.validate_structure().is_err());
}
#[test]
fn test_dag_node_hash_deterministic() {
let node1 = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02, 0x03],
vec![vec![0xAB; 64]],
vec![vec![0xCD; 32]],
vec![Hash::new([4u8; 32])],
);
let node2 = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02, 0x03],
vec![vec![0xAB; 64]],
vec![vec![0xCD; 32]],
vec![Hash::new([4u8; 32])],
);
assert_eq!(node1.hash(), node2.hash());
}
#[test]
fn test_dag_node_hash_differs_by_node_id() {
let node_a = DAGNode::new(Hash::new([1u8; 32]), vec![0x01], vec![], vec![], vec![]);
let node_b = DAGNode::new(Hash::new([2u8; 32]), vec![0x01], vec![], vec![], vec![]);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_differs_by_bytecode() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![],
vec![],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x03, 0x04],
vec![],
vec![],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_differs_by_signatures() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![vec![0xAA; 64]],
vec![],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![vec![0xBB; 64]],
vec![],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_differs_by_witnesses() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![vec![0xCC; 32]],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![vec![0xDD; 32]],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_differs_by_parents() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([10u8; 32])],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([20u8; 32])],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_segment_multi_parent_validation() {
let parent_a = DAGNode::new(Hash::new([1u8; 32]), vec![], vec![], vec![], vec![]);
let parent_b = DAGNode::new(Hash::new([2u8; 32]), vec![], vec![], vec![], vec![]);
let child = DAGNode::new(
Hash::new([3u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([1u8; 32]), Hash::new([2u8; 32])],
);
let segment = DAGSegment::new(vec![parent_a, parent_b, child], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_segment_multi_parent_missing_one() {
let parent_a = DAGNode::new(Hash::new([1u8; 32]), vec![], vec![], vec![], vec![]);
let child = DAGNode::new(
Hash::new([3u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([1u8; 32]), Hash::new([99u8; 32])],
);
let segment = DAGSegment::new(vec![parent_a, child], Hash::zero());
assert!(segment.validate_structure().is_err());
}
#[test]
fn test_dag_root_node_has_no_parents() {
let root = DAGNode::new(Hash::new([1u8; 32]), vec![0x01], vec![], vec![], vec![]);
assert!(root.parents.is_empty());
let segment = DAGSegment::new(vec![root.clone()], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_segment_empty_valid() {
let segment = DAGSegment::new(vec![], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_node_serialization_roundtrip() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02, 0x03],
vec![vec![0xAB; 64]],
vec![vec![0xCD; 32]],
vec![Hash::new([4u8; 32])],
);
let bytes = bincode::serialize(&node).unwrap();
let restored: DAGNode = bincode::deserialize(&bytes).unwrap();
assert_eq!(node, restored);
}
#[test]
fn test_dag_segment_serialization_roundtrip() {
let parent = DAGNode::new(Hash::new([1u8; 32]), vec![0x01], vec![], vec![], vec![]);
let child = DAGNode::new(
Hash::new([2u8; 32]),
vec![0x02],
vec![vec![0xAB; 64]],
vec![],
vec![Hash::new([1u8; 32])],
);
let segment = DAGSegment::new(vec![parent, child], Hash::new([99u8; 32]));
let bytes = bincode::serialize(&segment).unwrap();
let restored: DAGSegment = bincode::deserialize(&bytes).unwrap();
assert_eq!(segment, restored);
}
#[test]
fn test_dag_node_serialization_preserves_hash() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![vec![0xAB; 64]],
vec![],
vec![],
);
let original_hash = node.hash();
let bytes = bincode::serialize(&node).unwrap();
let restored: DAGNode = bincode::deserialize(&bytes).unwrap();
assert_eq!(original_hash, restored.hash());
}
#[test]
fn test_dag_segment_large_chain() {
let mut nodes = Vec::new();
for i in 0..100u8 {
let mut id = [0u8; 32];
id[0] = i + 1;
let parents = if i == 0 {
vec![]
} else {
let mut prev_id = [0u8; 32];
prev_id[0] = i;
vec![Hash::new(prev_id)]
};
let node = DAGNode::new(Hash::new(id), vec![i], vec![], vec![], parents);
nodes.push(node);
}
let segment = DAGSegment::new(nodes, Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_segment_large_diamond() {
let root = DAGNode::new(Hash::new([0u8; 32]), vec![], vec![], vec![], vec![]);
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([0u8; 32])],
);
let node_b = DAGNode::new(
Hash::new([2u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([0u8; 32])],
);
let leaf = DAGNode::new(
Hash::new([3u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([1u8; 32]), Hash::new([2u8; 32])],
);
let segment = DAGSegment::new(vec![root, node_a, node_b, leaf], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_segment_duplicate_node_ids_still_valid() {
let node_a = DAGNode::new(Hash::new([1u8; 32]), vec![0x01], vec![], vec![], vec![]);
let node_b = DAGNode::new(
Hash::new([1u8; 32]), vec![0x02],
vec![],
vec![],
vec![],
);
let child = DAGNode::new(
Hash::new([3u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([1u8; 32])],
);
let segment = DAGSegment::new(vec![node_a, node_b, child], Hash::zero());
assert!(segment.validate_structure().is_ok());
}
#[test]
fn test_dag_node_hash_bytecode_order_sensitive() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02, 0x03],
vec![],
vec![],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x03, 0x02, 0x01],
vec![],
vec![],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_signature_order_sensitive() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![vec![0xAA; 64], vec![0xBB; 64]],
vec![],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![vec![0xBB; 64], vec![0xAA; 64]],
vec![],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_witness_order_sensitive() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![vec![0xCC; 32], vec![0xDD; 32]],
vec![],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![vec![0xDD; 32], vec![0xCC; 32]],
vec![],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_node_hash_parent_order_sensitive() {
let node_a = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([10u8; 32]), Hash::new([20u8; 32])],
);
let node_b = DAGNode::new(
Hash::new([1u8; 32]),
vec![],
vec![],
vec![],
vec![Hash::new([20u8; 32]), Hash::new([10u8; 32])],
);
assert_ne!(node_a.hash(), node_b.hash());
}
#[test]
fn test_dag_complex_structure_with_signatures_and_witnesses() {
let root = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![vec![0xAA; 64]],
vec![vec![0xBB; 32]],
vec![],
);
let child = DAGNode::new(
Hash::new([2u8; 32]),
vec![0x03, 0x04],
vec![vec![0xCC; 64], vec![0xDD; 64]],
vec![vec![0xEE; 32]],
vec![Hash::new([1u8; 32])],
);
let segment = DAGSegment::new(vec![root, child], Hash::zero());
assert!(segment.validate_structure().is_ok());
assert_ne!(segment.nodes[0].hash(), segment.nodes[1].hash());
}
#[cfg(feature = "std")]
mod integration {
use super::*;
use crate::commitment::Commitment;
use crate::proof::ProofBundle;
use crate::seal::SealRef;
#[test]
fn test_dag_hash_used_in_commitment() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![vec![0xAB; 64]],
vec![],
vec![],
);
let dag_hash = node.hash();
let seal = SealRef::new(vec![0xAA; 16], Some(42)).unwrap();
let domain = [0xBB; 32];
let commitment =
Commitment::simple(Hash::new([2u8; 32]), Hash::zero(), dag_hash, &seal, domain);
assert_eq!(commitment.hash().as_bytes().len(), 32);
}
#[test]
fn test_dag_inside_proof_bundle_roundtrip() {
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01],
vec![vec![0xAB; 64]],
vec![],
vec![],
);
let segment = DAGSegment::new(vec![node], Hash::new([99u8; 32]));
let bundle = ProofBundle::new(
segment.clone(),
vec![vec![0xCC; 64]],
SealRef::new(vec![1, 2, 3], Some(42)).unwrap(),
crate::seal::AnchorRef::new(vec![4, 5, 6], 100, vec![]).unwrap(),
crate::proof::InclusionProof::new(vec![], Hash::zero(), 0).unwrap(),
crate::proof::FinalityProof::new(vec![], 6, false).unwrap(),
)
.unwrap();
let bytes = bundle.to_bytes().unwrap();
let restored = ProofBundle::from_bytes(&bytes).unwrap();
assert_eq!(bundle.transition_dag, restored.transition_dag);
}
#[test]
fn test_dag_in_verify_proof_pipeline() {
use secp256k1::{Message, Secp256k1, SecretKey};
let root_commitment = Hash::new([99u8; 32]);
let message: [u8; 32] = *root_commitment.as_bytes();
let secp = Secp256k1::new();
let secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng());
let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
let msg = Message::from_digest_slice(&message).unwrap();
let signature_ecdsa = secp.sign_ecdsa(&msg, &secret_key);
let sig_bytes = signature_ecdsa.serialize_compact();
let pubkey_bytes = public_key.serialize();
let mut signature = Vec::with_capacity(4 + pubkey_bytes.len() + sig_bytes.len());
signature.extend_from_slice(&(pubkey_bytes.len() as u32).to_le_bytes());
signature.extend_from_slice(&pubkey_bytes);
signature.extend_from_slice(&sig_bytes);
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![signature.clone()],
vec![],
vec![],
);
let segment = DAGSegment::new(vec![node], Hash::new([99u8; 32]));
let bundle = ProofBundle::new(
segment,
vec![signature],
SealRef::new(vec![1, 2, 3], Some(42)).unwrap(),
crate::seal::AnchorRef::new(vec![4, 5, 6], 100, vec![]).unwrap(),
crate::proof::InclusionProof::new(vec![0xDD; 32], Hash::new([10u8; 32]), 0)
.unwrap(),
crate::proof::FinalityProof::new(vec![], 6, false).unwrap(),
)
.unwrap();
let seal_registry = |_id: &[u8]| false;
assert!(crate::proof_verify::verify_proof(
&bundle,
seal_registry,
crate::signature::SignatureScheme::Secp256k1
)
.is_ok());
}
#[test]
fn test_dag_with_invalid_parent_fails_in_proof_bundle() {
use secp256k1::{Message, Secp256k1, SecretKey};
let root_commitment = Hash::zero();
let message: [u8; 32] = *root_commitment.as_bytes();
let secp = Secp256k1::new();
let secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng());
let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
let msg = Message::from_digest_slice(&message).unwrap();
let signature_ecdsa = secp.sign_ecdsa(&msg, &secret_key);
let sig_bytes = signature_ecdsa.serialize_compact();
let pubkey_bytes = public_key.serialize();
let mut signature = Vec::with_capacity(4 + pubkey_bytes.len() + sig_bytes.len());
signature.extend_from_slice(&(pubkey_bytes.len() as u32).to_le_bytes());
signature.extend_from_slice(&pubkey_bytes);
signature.extend_from_slice(&sig_bytes);
let node = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01],
vec![signature.clone()],
vec![],
vec![Hash::new([99u8; 32])], );
let segment = DAGSegment::new(vec![node], Hash::zero());
let bundle = ProofBundle::new(
segment,
vec![signature],
SealRef::new(vec![1, 2, 3], Some(42)).unwrap(),
crate::seal::AnchorRef::new(vec![4, 5, 6], 100, vec![]).unwrap(),
crate::proof::InclusionProof::new(vec![0xDD; 32], Hash::new([10u8; 32]), 0)
.unwrap(),
crate::proof::FinalityProof::new(vec![], 6, false).unwrap(),
)
.unwrap();
let seal_registry = |_id: &[u8]| false;
let result = crate::proof_verify::verify_proof(
&bundle,
seal_registry,
crate::signature::SignatureScheme::Secp256k1,
);
assert!(result.is_err());
}
#[test]
fn test_same_dag_produces_same_commitment_hash() {
fn build_dag() -> DAGSegment {
let root = DAGNode::new(
Hash::new([1u8; 32]),
vec![0x01, 0x02],
vec![vec![0xAA; 64]],
vec![vec![0xBB; 32]],
vec![],
);
let child = DAGNode::new(
Hash::new([2u8; 32]),
vec![0x03],
vec![vec![0xCC; 64]],
vec![],
vec![Hash::new([1u8; 32])],
);
DAGSegment::new(vec![root, child], Hash::new([3u8; 32]))
}
let dag_a = build_dag();
let dag_b = build_dag();
let seal = SealRef::new(vec![0xFF; 16], Some(1)).unwrap();
let domain = [0xEE; 32];
let commitment_a = Commitment::simple(
Hash::new([10u8; 32]),
Hash::zero(),
dag_a.root_commitment,
&seal,
domain,
);
let commitment_b = Commitment::simple(
Hash::new([10u8; 32]),
Hash::zero(),
dag_b.root_commitment,
&seal,
domain,
);
assert_eq!(commitment_a.hash(), commitment_b.hash());
}
}
}