use alloy_primitives::{Bytes, U256};
use blueprint_client_tangle::TangleClient;
use blueprint_std::format;
use blueprint_std::string::String;
use blueprint_std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AggregationError {
#[error("Client error: {0}")]
Client(String),
#[error("Threshold not met: got {0} signers, need {1}")]
ThresholdNotMet(usize, usize),
#[error("Invalid BLS signature")]
InvalidSignature,
#[error("Contract call failed: {0}")]
ContractError(String),
#[error("Missing BLS key for operator at index {0}")]
MissingOperatorKey(usize),
}
#[derive(Debug, Clone, Default)]
pub struct SignerBitmap(pub U256);
impl SignerBitmap {
pub fn new() -> Self {
Self(U256::ZERO)
}
pub fn from_indices(indices: &[usize]) -> Self {
let mut bitmap = U256::ZERO;
for &idx in indices {
bitmap |= U256::from(1u64) << idx;
}
Self(bitmap)
}
pub fn is_signer(&self, index: usize) -> bool {
(self.0 >> index) & U256::from(1u64) == U256::from(1u64)
}
pub fn add_signer(&mut self, index: usize) {
self.0 |= U256::from(1u64) << index;
}
pub fn remove_signer(&mut self, index: usize) {
self.0 &= !(U256::from(1u64) << index);
}
pub fn count_signers(&self) -> usize {
let mut count = 0;
let mut bitmap = self.0;
while bitmap > U256::ZERO {
if bitmap & U256::from(1u64) == U256::from(1u64) {
count += 1;
}
bitmap >>= 1;
}
count
}
pub fn as_u256(&self) -> U256 {
self.0
}
}
#[derive(Debug, Clone)]
pub struct G1Point {
pub x: U256,
pub y: U256,
}
impl G1Point {
pub fn new(x: U256, y: U256) -> Self {
Self { x, y }
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 64 {
return None;
}
let x = U256::from_be_slice(&bytes[0..32]);
let y = U256::from_be_slice(&bytes[32..64]);
Some(Self { x, y })
}
pub fn to_array(&self) -> [U256; 2] {
[self.x, self.y]
}
}
#[derive(Debug, Clone)]
pub struct G2Point {
pub x0: U256,
pub x1: U256,
pub y0: U256,
pub y1: U256,
}
impl G2Point {
pub fn new(x0: U256, x1: U256, y0: U256, y1: U256) -> Self {
Self { x0, x1, y0, y1 }
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 128 {
return None;
}
let x0 = U256::from_be_slice(&bytes[0..32]);
let x1 = U256::from_be_slice(&bytes[32..64]);
let y0 = U256::from_be_slice(&bytes[64..96]);
let y1 = U256::from_be_slice(&bytes[96..128]);
Some(Self { x0, x1, y0, y1 })
}
pub fn to_array(&self) -> [U256; 4] {
[self.x0, self.x1, self.y0, self.y1]
}
}
#[derive(Debug, Clone)]
pub struct AggregatedResult {
pub service_id: u64,
pub call_id: u64,
pub output: Bytes,
pub signer_bitmap: SignerBitmap,
pub signature: G1Point,
pub pubkey: G2Point,
}
impl AggregatedResult {
pub fn new(
service_id: u64,
call_id: u64,
output: Bytes,
signer_bitmap: SignerBitmap,
signature: G1Point,
pubkey: G2Point,
) -> Self {
Self {
service_id,
call_id,
output,
signer_bitmap,
signature,
pubkey,
}
}
pub async fn submit(&self, client: &Arc<TangleClient>) -> Result<(), AggregationError> {
blueprint_core::debug!(
target: "tangle-aggregation",
"Submitting aggregated result for service {} call {} with {} signers",
self.service_id,
self.call_id,
self.signer_bitmap.count_signers()
);
let result = client
.submit_aggregated_result(
self.service_id,
self.call_id,
self.output.clone(),
self.signer_bitmap.as_u256(),
self.signature.to_array(),
self.pubkey.to_array(),
)
.await
.map_err(|e| {
AggregationError::ContractError(format!("Failed to submit aggregated result: {e}"))
})?;
if result.success {
blueprint_core::info!(
target: "tangle-aggregation",
"Successfully submitted aggregated result for service {} call {} with {} signers: tx_hash={:?}",
self.service_id,
self.call_id,
self.signer_bitmap.count_signers(),
result.tx_hash
);
Ok(())
} else {
Err(AggregationError::ContractError(format!(
"Transaction reverted for service {} call {}: tx_hash={:?}",
self.service_id, self.call_id, result.tx_hash
)))
}
}
}
pub const JOB_INDEX_KEY: &str = "tangle.job_index";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_signer_bitmap() {
let mut bitmap = SignerBitmap::new();
assert_eq!(bitmap.count_signers(), 0);
bitmap.add_signer(0);
bitmap.add_signer(2);
bitmap.add_signer(5);
assert!(bitmap.is_signer(0));
assert!(!bitmap.is_signer(1));
assert!(bitmap.is_signer(2));
assert!(!bitmap.is_signer(3));
assert!(!bitmap.is_signer(4));
assert!(bitmap.is_signer(5));
assert_eq!(bitmap.count_signers(), 3);
bitmap.remove_signer(2);
assert!(!bitmap.is_signer(2));
assert_eq!(bitmap.count_signers(), 2);
}
#[test]
fn test_signer_bitmap_from_indices() {
let bitmap = SignerBitmap::from_indices(&[1, 3, 7]);
assert!(!bitmap.is_signer(0));
assert!(bitmap.is_signer(1));
assert!(!bitmap.is_signer(2));
assert!(bitmap.is_signer(3));
assert!(bitmap.is_signer(7));
assert_eq!(bitmap.count_signers(), 3);
}
#[test]
fn test_signer_bitmap_empty_indices() {
let bitmap = SignerBitmap::from_indices(&[]);
assert_eq!(bitmap.count_signers(), 0);
assert_eq!(bitmap.as_u256(), U256::ZERO);
}
#[test]
fn test_signer_bitmap_large_indices() {
let bitmap = SignerBitmap::from_indices(&[0, 100, 200, 255]);
assert!(bitmap.is_signer(0));
assert!(bitmap.is_signer(100));
assert!(bitmap.is_signer(200));
assert!(bitmap.is_signer(255));
assert!(!bitmap.is_signer(50));
assert_eq!(bitmap.count_signers(), 4);
}
#[test]
fn test_signer_bitmap_duplicate_indices() {
let bitmap = SignerBitmap::from_indices(&[1, 1, 1, 2]);
assert_eq!(bitmap.count_signers(), 2);
}
#[test]
fn test_signer_bitmap_as_u256() {
let bitmap = SignerBitmap::from_indices(&[0, 1, 2]);
assert_eq!(bitmap.as_u256(), U256::from(7u64));
}
#[test]
fn test_signer_bitmap_default() {
let bitmap = SignerBitmap::default();
assert_eq!(bitmap.count_signers(), 0);
assert_eq!(bitmap.as_u256(), U256::ZERO);
}
#[test]
fn test_g1_point_new() {
let x = U256::from(123u64);
let y = U256::from(456u64);
let point = G1Point::new(x, y);
assert_eq!(point.x, x);
assert_eq!(point.y, y);
}
#[test]
fn test_g1_point_from_bytes() {
let mut bytes = [0u8; 64];
bytes[31] = 1;
bytes[63] = 2;
let point = G1Point::from_bytes(&bytes).expect("should parse 64 bytes");
assert_eq!(point.x, U256::from(1u64));
assert_eq!(point.y, U256::from(2u64));
}
#[test]
fn test_g1_point_from_bytes_invalid_length() {
let bytes = [0u8; 32]; assert!(G1Point::from_bytes(&bytes).is_none());
let bytes = [0u8; 128]; assert!(G1Point::from_bytes(&bytes).is_none());
}
#[test]
fn test_g1_point_to_array() {
let x = U256::from(100u64);
let y = U256::from(200u64);
let point = G1Point::new(x, y);
let arr = point.to_array();
assert_eq!(arr[0], x);
assert_eq!(arr[1], y);
}
#[test]
fn test_g2_point_new() {
let x0 = U256::from(1u64);
let x1 = U256::from(2u64);
let y0 = U256::from(3u64);
let y1 = U256::from(4u64);
let point = G2Point::new(x0, x1, y0, y1);
assert_eq!(point.x0, x0);
assert_eq!(point.x1, x1);
assert_eq!(point.y0, y0);
assert_eq!(point.y1, y1);
}
#[test]
fn test_g2_point_from_bytes() {
let mut bytes = [0u8; 128];
bytes[31] = 1;
bytes[63] = 2;
bytes[95] = 3;
bytes[127] = 4;
let point = G2Point::from_bytes(&bytes).expect("should parse 128 bytes");
assert_eq!(point.x0, U256::from(1u64));
assert_eq!(point.x1, U256::from(2u64));
assert_eq!(point.y0, U256::from(3u64));
assert_eq!(point.y1, U256::from(4u64));
}
#[test]
fn test_g2_point_from_bytes_invalid_length() {
let bytes = [0u8; 64]; assert!(G2Point::from_bytes(&bytes).is_none());
let bytes = [0u8; 256]; assert!(G2Point::from_bytes(&bytes).is_none());
}
#[test]
fn test_g2_point_to_array() {
let x0 = U256::from(10u64);
let x1 = U256::from(20u64);
let y0 = U256::from(30u64);
let y1 = U256::from(40u64);
let point = G2Point::new(x0, x1, y0, y1);
let arr = point.to_array();
assert_eq!(arr[0], x0);
assert_eq!(arr[1], x1);
assert_eq!(arr[2], y0);
assert_eq!(arr[3], y1);
}
#[test]
fn test_aggregated_result_new() {
let service_id = 1u64;
let call_id = 42u64;
let output = Bytes::from(vec![1, 2, 3, 4]);
let signer_bitmap = SignerBitmap::from_indices(&[0, 1, 2]);
let signature = G1Point::new(U256::from(100u64), U256::from(200u64));
let pubkey = G2Point::new(
U256::from(1u64),
U256::from(2u64),
U256::from(3u64),
U256::from(4u64),
);
let result = AggregatedResult::new(
service_id,
call_id,
output.clone(),
signer_bitmap.clone(),
signature.clone(),
pubkey.clone(),
);
assert_eq!(result.service_id, service_id);
assert_eq!(result.call_id, call_id);
assert_eq!(result.output, output);
assert_eq!(result.signer_bitmap.count_signers(), 3);
}
}