1use anchor_lang::prelude::*;
2use arcium_client::{
3 idl::{
4 arcium::{
5 accounts::Cluster,
6 cpi::{accounts::InitComputationDefinition, init_computation_definition},
7 types::{
8 AccountArgument,
9 ArgumentList,
10 ArgumentRef,
11 CallbackInstruction,
12 CircuitSource,
13 ComputationDefinitionMeta,
14 ComputationSignature,
15 Parameter,
16 SetUnset,
17 },
18 },
19 BLSDomainSeparator,
20 },
21 pda::{CLOCK_PDA, FEE_POOL_PDA},
22};
23pub use solana_address_lookup_table_interface;
25use solana_alt_bn128_bls::Sha256Normalized;
26use traits::{InitCompDefAccs, QueueCompAccs};
27
28pub mod traits;
29
30pub mod prelude {
31 pub use super::*;
32 pub use arcium_client::idl::arcium::{
33 accounts::{ClockAccount, Cluster, ComputationDefinitionAccount, FeePool, MXEAccount},
34 program::Arcium,
35 types::{AccountArgument, ArgumentList, ArgumentRef},
36 ID_CONST as ARCIUM_PROG_ID,
37 };
38 pub use arcium_macros::{
39 arcium_callback,
40 arcium_program,
41 callback_accounts,
42 check_args,
43 init_computation_definition_accounts,
44 queue_computation_accounts,
45 };
46 pub use traits::CallbackCompAccs;
47 pub use ArgBuilder;
48 pub use LUT_PROGRAM_ID;
49}
50
51#[derive(AnchorSerialize, AnchorDeserialize)]
52pub struct SharedEncryptedStruct<const LEN: usize> {
53 pub encryption_key: [u8; 32],
54 pub nonce: u128,
55 pub ciphertexts: [[u8; 32]; LEN],
56}
57
58#[derive(AnchorSerialize, AnchorDeserialize)]
59pub struct MXEEncryptedStruct<const LEN: usize> {
60 pub nonce: u128,
61 pub ciphertexts: [[u8; 32]; LEN],
62}
63
64#[derive(AnchorSerialize, AnchorDeserialize)]
65pub struct EncDataStruct<const LEN: usize> {
66 pub ciphertexts: [[u8; 32]; LEN],
67}
68
69#[error_code]
70pub enum ArciumError {
71 AbortedComputation,
72 BLSSignatureVerificationFailed,
73 InvalidClusterBLSPublicKey,
74 InvalidComputationAccount,
75 MarkerForIdlBuildUsageNotAllowed,
76 OutputTooLarge,
77 TrustedDealer,
78 #[msg("Multi-transaction callbacks disabled; enable 'multi-tx-callbacks' feature")]
79 MultiTxCallbacksDisabled,
80 Serialization,
81 Router,
82 Circuit,
83 Inputs,
84 ProtocolInit,
85 ProtocolRun,
86 InvalidMAC,
87 ExpectedSentShare,
88 ExpectedFieldElement,
89 ExpectedAbort,
90 MalformedData,
91 ComputationFailed,
92 InternalError,
93 PreprocessingStreamError,
94 DivisionByZero,
95 NoSignature,
96 InvalidSignature,
97 PrimitiveError,
98 InvalidBatchLength,
99 QuadraticNonResidue,
100 BitConversionError,
101 ChannelClosed,
102 TimeoutElapsed,
103 KeysDigestMismatch,
104 X25519PubkeyMismatch,
105 Ed25519VerifyingKeyMismatch,
106 ElGamalPubkeyMismatch,
107 TooManyCorruptPeers,
108 NoPubkeysSet,
109 KeyRecoverySerialization,
110}
111
112#[derive(Debug, AnchorSerialize, AnchorDeserialize)]
113pub enum RawComputationOutputs<O: AnchorDeserialize + AnchorSerialize> {
114 Success(O),
115 Failure,
116}
117
118pub trait HasSize {
120 const SIZE: usize;
121}
122
123#[derive(Debug)]
125pub enum SignedComputationOutputs<O: HasSize + AnchorDeserialize + AnchorSerialize> {
126 Success(Vec<u8>, [u8; 64]),
127 Failure([u8; 64]),
128 MarkerForIdlBuildDoNotUseThis(O),
129}
130
131impl<O: HasSize + AnchorDeserialize + AnchorSerialize> AnchorDeserialize
132 for SignedComputationOutputs<O>
133{
134 fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
135 let variant = u8::deserialize_reader(reader)?;
136 match variant {
137 variant if variant == (BLSDomainSeparator::Success as u8) => {
138 let mut bytes = vec![0u8; O::SIZE];
139 reader.read_exact(&mut bytes)?;
140 let mut sig = [0u8; 64];
141 reader.read_exact(&mut sig)?;
142 Ok(SignedComputationOutputs::Success(bytes, sig))
143 }
144 variant if variant == (BLSDomainSeparator::Failure as u8) => {
145 let mut sig = [0u8; 64];
146 reader.read_exact(&mut sig)?;
147 Ok(SignedComputationOutputs::Failure(sig))
148 }
149 _ => Err(std::io::Error::new(
150 std::io::ErrorKind::InvalidData,
151 "Invalid SignedComputationOutputs variant",
152 )),
153 }
154 }
155}
156
157impl<O: HasSize + AnchorDeserialize + AnchorSerialize> AnchorSerialize
158 for SignedComputationOutputs<O>
159{
160 fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
161 match self {
162 SignedComputationOutputs::Success(bytes, sig) => {
163 if bytes.len() != O::SIZE {
164 return Err(std::io::Error::new(
165 std::io::ErrorKind::InvalidInput,
166 "SignedComputationOutputs payload must match O::SIZE",
167 ));
168 }
169 (BLSDomainSeparator::Success as u8).serialize(writer)?;
170 writer.write_all(bytes)?;
171 writer.write_all(sig)?;
172 }
173 SignedComputationOutputs::Failure(sig) => {
174 (BLSDomainSeparator::Failure as u8).serialize(writer)?;
175 writer.write_all(sig)?;
176 }
177 SignedComputationOutputs::MarkerForIdlBuildDoNotUseThis(o) => {
178 255u8.serialize(writer)?;
179 o.serialize(writer)?;
180 }
181 }
182 Ok(())
183 }
184}
185
186impl<O: HasSize + AnchorDeserialize + AnchorSerialize> SignedComputationOutputs<O> {
187 pub fn verify_output_raw(
188 self,
189 arcium_cluster_acc: &Cluster,
190 computation_account: &UncheckedAccount,
191 ) -> Result<Vec<u8>> {
192 let bls_pubkey = match arcium_cluster_acc.bls_public_key {
193 SetUnset::Set(bls_pubkey) => bls_pubkey,
194 SetUnset::Unset(..) => return Err(ArciumError::InvalidClusterBLSPublicKey.into()),
195 };
196
197 let (slot, slot_counter) = get_slot_and_slot_counter_bytes(computation_account)?;
198
199 match self {
200 SignedComputationOutputs::Success(o_bytes, bls_sig_bytes) => {
201 let message = &[
202 &[BLSDomainSeparator::Success as u8],
203 o_bytes.as_slice(),
204 slot.as_ref(),
207 slot_counter.as_ref(),
208 ]
209 .concat();
210
211 let bls_pubkey = solana_alt_bn128_bls::G2CompressedPoint(bls_pubkey.0);
213 let bls_sig = solana_alt_bn128_bls::G1Point(bls_sig_bytes);
214
215 bls_pubkey
217 .verify_signature::<Sha256Normalized, &[u8], solana_alt_bn128_bls::G1Point>(
218 bls_sig, message,
219 )
220 .map_err(|_| ArciumError::BLSSignatureVerificationFailed)?;
221
222 Ok(o_bytes)
223 }
224 SignedComputationOutputs::Failure(_) => Err(ArciumError::AbortedComputation.into()),
225 SignedComputationOutputs::MarkerForIdlBuildDoNotUseThis(_) => {
226 Err(ArciumError::MarkerForIdlBuildUsageNotAllowed.into())
227 }
228 }
229 }
230
231 pub fn verify_output(
232 self,
233 arcium_cluster_acc: &Cluster,
234 computation_account: &UncheckedAccount,
235 ) -> Result<O> {
236 let raw = self.verify_output_raw(arcium_cluster_acc, computation_account)?;
237 Ok(O::try_from_slice(&raw)?)
238 }
239}
240
241const SIGNER_ACCOUNT_BUMP_OFFSET: usize = 8;
243
244pub fn queue_computation<'info, T>(
245 accs: &T,
246 computation_offset: u64,
247 args: ArgumentList,
248 callback_instructions: Vec<CallbackInstruction>,
249 num_callback_txs: u8,
250 cu_price_micro: u64,
251) -> Result<()>
252where
253 T: QueueCompAccs<'info>,
254{
255 #[cfg(not(feature = "multi-tx-callbacks"))]
256 if num_callback_txs != 1 {
257 return Err(error!(ArciumError::MultiTxCallbacksDisabled));
258 }
259
260 let bump = accs.signer_pda_bump();
261 let signer_seeds: &[&[&[u8]]] = &[&[SIGN_PDA_SEED, &[bump]]];
262 let queue_comp_accounts = accs.queue_comp_accs();
263 queue_comp_accounts.sign_seed.try_borrow_mut_data()?[SIGNER_ACCOUNT_BUMP_OFFSET] = bump;
264
265 let cpi_context =
266 CpiContext::new_with_signer(accs.arcium_program(), queue_comp_accounts, signer_seeds);
267 arcium_client::idl::arcium::cpi::queue_computation(
268 cpi_context,
269 computation_offset,
270 accs.comp_def_offset(),
271 args,
272 accs.mxe_program(),
273 callback_instructions,
274 num_callback_txs,
275 0,
276 cu_price_micro,
277 )
278}
279
280pub fn init_comp_def<'info, T>(
281 accs: &T,
282 circuit_source_override: Option<CircuitSource>,
283 finalize_authority: Option<Pubkey>,
284) -> Result<()>
285where
286 T: InitCompDefAccs<'info>,
287{
288 let cpi_context = CpiContext::new(
289 accs.arcium_program(),
290 InitComputationDefinition {
291 signer: accs.signer(),
292 system_program: accs.system_program(),
293 mxe: accs.mxe_acc(),
294 comp_def_acc: accs.comp_def_acc(),
295 address_lookup_table: accs.address_lookup_table(),
296 lut_program: accs.lut_program(),
297 },
298 );
299
300 let signature = ComputationSignature {
301 parameters: accs.params(),
302 outputs: accs.outputs(),
303 };
304 let computation_definition = ComputationDefinitionMeta {
305 circuit_len: accs.compiled_circuit_len(),
306 signature,
307 };
308 init_computation_definition(
309 cpi_context,
310 accs.comp_def_offset(),
311 accs.mxe_program(),
312 computation_definition,
313 circuit_source_override,
314 accs.weight(),
315 finalize_authority,
316 )?;
317
318 Ok(())
319}
320
321pub struct ShortVec<T: AnchorSerialize + AnchorDeserialize> {
322 pub data: Vec<T>,
323}
324
325impl<T: AnchorSerialize + AnchorDeserialize> AnchorSerialize for ShortVec<T> {
326 fn serialize<W: std::io::Write>(
327 &self,
328 writer: &mut W,
329 ) -> std::result::Result<(), std::io::Error> {
330 let len: u16 = self.data.len().try_into().map_err(|_| {
331 std::io::Error::new(
332 std::io::ErrorKind::InvalidInput,
333 "Length too large, must fit in u16",
334 )
335 })?;
336 len.serialize(writer)?;
337 for item in &self.data {
338 item.serialize(writer)?;
339 }
340 Ok(())
341 }
342}
343
344impl<T: AnchorSerialize + AnchorDeserialize> AnchorDeserialize for ShortVec<T> {
345 fn deserialize_reader<R: std::io::Read>(
346 reader: &mut R,
347 ) -> std::result::Result<Self, std::io::Error> {
348 let len: u16 = u16::deserialize_reader(reader)?;
349 let mut data = Vec::with_capacity(len as usize);
350 for _ in 0..len {
351 data.push(T::deserialize_reader(reader)?);
352 }
353 Ok(Self { data })
354 }
355}
356
357fn get_slot_and_slot_counter_bytes(
359 computation_account: &UncheckedAccount,
360) -> Result<([u8; 8], [u8; 2])> {
361 const SLOT_OFFSET: usize = 100;
362 const SLOT_COUNTER_OFFSET: usize = 108;
363
364 let data = computation_account.try_borrow_data()?;
365
366 let slot_bytes: [u8; 8] = data
367 .get(SLOT_OFFSET..SLOT_OFFSET + 8)
368 .ok_or(ArciumError::InvalidComputationAccount)?
369 .try_into()
370 .map_err(|_| ArciumError::InvalidComputationAccount)?;
371
372 let slot_counter_bytes: [u8; 2] = data
373 .get(SLOT_COUNTER_OFFSET..SLOT_COUNTER_OFFSET + 2)
374 .ok_or(ArciumError::InvalidComputationAccount)?
375 .try_into()
376 .map_err(|_| ArciumError::InvalidComputationAccount)?;
377
378 Ok((slot_bytes, slot_counter_bytes))
379}
380
381#[cfg(feature = "idl-build")]
382impl<T: AnchorSerialize + AnchorDeserialize> anchor_lang::idl::build::IdlBuild for ShortVec<T> {
383 fn create_type() -> Option<anchor_lang::idl::types::IdlTypeDef> {
384 Some(anchor_lang::idl::types::IdlTypeDef {
385 name: Self::get_full_path(),
386 docs: vec![],
387 serialization: anchor_lang::idl::types::IdlSerialization::default(),
388 repr: None,
389 generics: <[_]>::into_vec(Box::new([
390 anchor_lang::idl::types::IdlTypeDefGeneric::Type { name: "T".into() }.into(),
391 ])),
392 ty: anchor_lang::idl::types::IdlTypeDefTy::Struct {
393 fields: Some(anchor_lang::idl::types::IdlDefinedFields::Named(
394 <[_]>::into_vec(Box::new([anchor_lang::idl::types::IdlField {
395 name: "data".into(),
396 docs: vec![],
397 ty: anchor_lang::idl::types::IdlType::Vec(Box::new(
398 anchor_lang::idl::types::IdlType::Generic("T".into()),
399 )),
400 }])),
401 )),
402 },
403 })
404 }
405
406 fn insert_types(
407 types: &mut std::collections::BTreeMap<String, anchor_lang::idl::types::IdlTypeDef>,
408 ) {
409 }
410
411 fn get_full_path() -> String {
412 std::fmt::format(format_args!("{0}", "ShortVec"))
413 }
414}
415
416#[cfg(feature = "idl-build")]
417impl<O: HasSize + AnchorSerialize + AnchorDeserialize> anchor_lang::idl::build::IdlBuild
418 for SignedComputationOutputs<O>
419{
420 fn create_type() -> Option<anchor_lang::idl::types::IdlTypeDef> {
421 Some(anchor_lang::idl::types::IdlTypeDef {
422 name: Self::get_full_path(),
423 docs: vec![],
424 serialization: anchor_lang::idl::types::IdlSerialization::default(),
425 repr: None,
426 generics: <[_]>::into_vec(Box::new([
427 anchor_lang::idl::types::IdlTypeDefGeneric::Type { name: "O".into() }.into(),
428 ])),
429 ty: anchor_lang::idl::types::IdlTypeDefTy::Enum {
430 variants: vec![
431 anchor_lang::idl::types::IdlEnumVariant {
432 name: "Success".into(),
433 fields: Some(anchor_lang::idl::types::IdlDefinedFields::Tuple(vec![
434 anchor_lang::idl::types::IdlType::Generic("O".into()),
435 anchor_lang::idl::types::IdlType::Array(
436 Box::new(anchor_lang::idl::types::IdlType::U8),
437 anchor_lang::idl::types::IdlArrayLen::Value(64),
438 ),
439 ])),
440 },
441 anchor_lang::idl::types::IdlEnumVariant {
442 name: "Failure".into(),
443 fields: None,
444 },
445 anchor_lang::idl::types::IdlEnumVariant {
446 name: "MarkerForIdlBuildDoNotUseThis".into(),
447 fields: Some(anchor_lang::idl::types::IdlDefinedFields::Tuple(vec![
448 anchor_lang::idl::types::IdlType::Generic("O".into()),
449 ])),
450 },
451 ],
452 },
453 })
454 }
455
456 fn insert_types(
457 types: &mut std::collections::BTreeMap<String, anchor_lang::idl::types::IdlTypeDef>,
458 ) {
459 }
460
461 fn get_full_path() -> String {
462 "SignedComputationOutputs".to_string()
463 }
464}
465
466#[macro_export]
467macro_rules! derive_seed {
468 ($name:ident) => {
469 stringify!($name).as_bytes()
470 };
471}
472
473pub const fn comp_def_offset(conf_ix_name: &str) -> u32 {
474 let hasher = ::sha2_const_stable::Sha256::new();
475 let result = hasher.update(conf_ix_name.as_bytes()).finalize();
476 u32::from_le_bytes([result[0], result[1], result[2], result[3]])
477}
478
479pub const MXE_PDA_SEED: &[u8] = derive_seed!(MXEAccount);
480pub const MEMPOOL_PDA_SEED: &[u8] = b"Mempool";
481pub const EXECPOOL_PDA_SEED: &[u8] = b"Execpool";
482pub const COMP_PDA_SEED: &[u8] = derive_seed!(ComputationAccount);
483pub const COMP_DEF_PDA_SEED: &[u8] = derive_seed!(ComputationDefinitionAccount);
484pub const CLUSTER_PDA_SEED: &[u8] = derive_seed!(Cluster);
485pub const POOL_PDA_SEED: &[u8] = derive_seed!(FeePool);
486pub const CLOCK_PDA_SEED: &[u8] = derive_seed!(ClockAccount);
487pub const SIGN_PDA_SEED: &[u8] = derive_seed!(ArciumSignerAccount);
488
489pub const ARCIUM_CLOCK_ACCOUNT_ADDRESS: Pubkey = CLOCK_PDA.0;
490pub const ARCIUM_FEE_POOL_ACCOUNT_ADDRESS: Pubkey = FEE_POOL_PDA.0;
491pub const LUT_PROGRAM_ID: Pubkey = solana_address_lookup_table_interface::program::ID;
492
493#[macro_export]
494macro_rules! derive_mxe_pda {
495 () => {
496 Pubkey::find_program_address(&[MXE_PDA_SEED, ID.to_bytes().as_ref()], &ARCIUM_PROG_ID).0
497 };
498}
499
500#[macro_export]
501macro_rules! derive_mempool_pda {
502 ($mxe_account:expr, $error_path:expr) => {
503 Pubkey::find_program_address(
504 &[
505 MEMPOOL_PDA_SEED,
506 &$mxe_account.cluster.ok_or($error_path)?.to_le_bytes(),
507 ],
508 &ARCIUM_PROG_ID,
509 )
510 .0
511 };
512}
513
514#[macro_export]
515macro_rules! derive_execpool_pda {
516 ($mxe_account:expr, $error_path:expr) => {
517 Pubkey::find_program_address(
518 &[
519 EXECPOOL_PDA_SEED,
520 &$mxe_account.cluster.ok_or($error_path)?.to_le_bytes(),
521 ],
522 &ARCIUM_PROG_ID,
523 )
524 .0
525 };
526}
527
528#[macro_export]
529macro_rules! derive_comp_pda {
530 ($computation_offset:expr, $mxe_account:expr, $error_path:expr) => {
531 Pubkey::find_program_address(
532 &[
533 COMP_PDA_SEED,
534 &$mxe_account.cluster.ok_or($error_path)?.to_le_bytes(),
535 &$computation_offset.to_le_bytes(),
536 ],
537 &ARCIUM_PROG_ID,
538 )
539 .0
540 };
541}
542
543#[macro_export]
544macro_rules! derive_comp_def_pda {
545 ($conf_ix_name:expr) => {
546 Pubkey::find_program_address(
547 &[
548 COMP_DEF_PDA_SEED,
549 &ID_CONST.to_bytes(),
550 &$conf_ix_name.to_le_bytes(),
551 ],
552 &ARCIUM_PROG_ID,
553 )
554 .0
555 };
556}
557
558#[macro_export]
559macro_rules! derive_cluster_pda {
560 ($mxe_account:expr, $error_path:expr) => {
561 Pubkey::find_program_address(
562 &[
563 CLUSTER_PDA_SEED,
564 &$mxe_account.cluster.ok_or($error_path)?.to_le_bytes(),
565 ],
566 &ARCIUM_PROG_ID,
567 )
568 .0
569 };
570}
571
572#[macro_export]
573macro_rules! derive_sign_pda {
574 () => {
575 Pubkey::find_program_address(&[SIGN_PDA_SEED], &ID_CONST).0
576 };
577}
578
579#[macro_export]
580macro_rules! derive_mxe_lut_pda {
581 ($lut_offset:expr) => {{
582 let mxe_pda = derive_mxe_pda!();
583 ::arcium_anchor::solana_address_lookup_table_interface::instruction::derive_lookup_table_address(&mxe_pda, $lut_offset).0
584 }};
585}
586include!("arg_builder.rs");
587include!("arg_match_param.rs");
588pub const fn const_match_computation(
589 arguments: &[ArgumentRef],
590 accounts: &[AccountArgument],
591 parameters: &[Parameter],
592) {
593 if let Err(err) = args_match_params(arguments, accounts, parameters) {
594 err.const_panic();
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601 use arcium_client::idl::arcium::{
602 accounts::ComputationAccount,
603 types::{ComputationStatus, ExecutionFee},
604 ID_CONST as ARCIUM_PROG_ID,
605 };
606 use std::{cell::RefCell, rc::Rc};
607
608 fn derive_arcium_pda(seeds: &[&[u8]]) -> Pubkey {
609 Pubkey::find_program_address(seeds, &ARCIUM_PROG_ID).0
610 }
611
612 #[test]
613 fn test_comp_def_offset() {
614 let conf_ix_name = "add_together";
615 let offset = comp_def_offset(conf_ix_name);
616 assert_eq!(offset, 4005749700);
617 }
618
619 #[test]
620 fn test_clock_account_address() {
621 let address = derive_arcium_pda(&[CLOCK_PDA_SEED]);
622 assert_eq!(address, ARCIUM_CLOCK_ACCOUNT_ADDRESS);
623 }
624
625 #[test]
626 fn test_fee_pool_account_address() {
627 let address = derive_arcium_pda(&[POOL_PDA_SEED]);
628 assert_eq!(address, ARCIUM_FEE_POOL_ACCOUNT_ADDRESS);
629 }
630
631 #[test]
632 fn test_get_slot_and_slot_counter_bytes() {
633 let computation_account = ComputationAccount {
634 payer: Pubkey::default(),
635 mxe_program_id: Pubkey::default(),
636 computation_definition_offset: 0,
637 execution_fee: ExecutionFee {
638 base_fee: 0,
639 priority_fee: 0,
640 output_delivery_fee: 0,
641 },
642 slot: 12345,
643 slot_counter: 5678,
644 status: ComputationStatus::Queued,
645 arguments: ArgumentList {
646 args: vec![],
647 byte_arrays: vec![],
648 plaintext_numbers: vec![],
649 values_128_bit: vec![],
650 accounts: vec![],
651 },
652 custom_callback_instructions: Vec::new(),
653 callback_transactions_required: 0,
654 callback_transactions_submitted_bm: 0,
655 bump: 0,
656 };
657
658 let mut key = Pubkey::default();
659 let mut lamports = 0;
660 let mut data = vec![];
661 let mut owner = Pubkey::default();
662
663 computation_account.try_serialize(&mut data).unwrap();
664
665 let account_info = AccountInfo {
666 key: &mut key,
667 lamports: Rc::new(RefCell::new(&mut lamports)),
668 data: Rc::new(RefCell::new(&mut data)),
669 owner: &mut owner,
670 rent_epoch: 0,
671 is_signer: false,
672 is_writable: false,
673 executable: false,
674 };
675
676 let computation_account = UncheckedAccount::try_from(&account_info);
677 let (slot_bytes, slot_counter_bytes) =
678 get_slot_and_slot_counter_bytes(&computation_account).unwrap();
679
680 let slot = u64::from_le_bytes(slot_bytes);
681 let slot_counter = u16::from_le_bytes(slot_counter_bytes);
682
683 assert_eq!(slot, 12345);
684 assert_eq!(slot_counter, 5678);
685 }
686
687 #[derive(Debug, PartialEq, AnchorSerialize, AnchorDeserialize)]
688 struct TestOutput {
689 value: u64,
690 }
691
692 impl HasSize for TestOutput {
693 const SIZE: usize = 8;
694 }
695
696 #[test]
697 fn test_signed_computation_outputs_roundtrip_success() {
698 let original = SignedComputationOutputs::<TestOutput>::Success(
699 vec![1, 2, 3, 4, 5, 6, 7, 8],
700 [42u8; 64],
701 );
702 let mut buf = Vec::new();
703 original.serialize(&mut buf).unwrap();
704
705 assert_eq!(buf.len(), 1 + 8 + 64);
706 assert_eq!(buf[0], 0);
707
708 let deserialized =
709 SignedComputationOutputs::<TestOutput>::deserialize(&mut &buf[..]).unwrap();
710 match deserialized {
711 SignedComputationOutputs::Success(bytes, sig) => {
712 assert_eq!(bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
713 assert_eq!(sig, [42u8; 64]);
714 }
715 _ => panic!("Expected Success variant"),
716 }
717 }
718
719 #[test]
720 fn test_signed_computation_outputs_no_length_prefix() {
721 let output = SignedComputationOutputs::<TestOutput>::Success(vec![0xAA; 8], [0xBB; 64]);
722 let mut buf = Vec::new();
723 output.serialize(&mut buf).unwrap();
724
725 assert_eq!(&buf[1..9], &[0xAA; 8]);
726 assert_eq!(&buf[9..73], &[0xBB; 64]);
727 }
728
729 #[test]
730 fn test_signed_computation_outputs_roundtrip_failure() {
731 let original = SignedComputationOutputs::<TestOutput>::Failure([0; 64]);
732 let mut buf = Vec::new();
733 original.serialize(&mut buf).unwrap();
734
735 assert_eq!(buf.len(), 65);
737 assert_eq!(buf[0], BLSDomainSeparator::Failure as u8);
738
739 let deserialized =
740 SignedComputationOutputs::<TestOutput>::deserialize(&mut &buf[..]).unwrap();
741 assert!(matches!(deserialized, SignedComputationOutputs::Failure(_)));
742 }
743
744 #[test]
745 fn test_signed_computation_outputs_serialize_validates_size() {
746 let invalid = SignedComputationOutputs::<TestOutput>::Success(vec![1, 2, 3], [0u8; 64]);
747 let mut buf = Vec::new();
748 let result = invalid.serialize(&mut buf);
749 assert!(result.is_err());
750 }
751}