1use alloc::vec::Vec;
10use core::cmp;
11use core::fmt::Debug;
12
13use bytecheck::CheckBytes;
14use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
15use dusk_poseidon::{Domain, Hash};
16use ff::Field;
17use rand::{CryptoRng, RngCore};
18use rkyv::{Archive, Deserialize, Serialize};
19
20use crate::signatures::schnorr::{
21 SecretKey as SchnorrSecretKey, Signature as SchnorrSignature,
22};
23use crate::transfer::data::{
24 BlobData, ContractBytecode, ContractCall, ContractDeploy, TransactionData,
25 MAX_MEMO_SIZE,
26};
27use crate::{BlsScalar, Error, JubJubAffine, JubJubScalar};
28
29pub use phoenix_circuits::{InputNoteInfo, OutputNoteInfo, TxCircuit};
31pub use phoenix_core::{
32 value_commitment, Error as CoreError, Note, PublicKey, SecretKey, Sender,
33 StealthAddress, TxSkeleton, ViewKey, NOTE_VAL_ENC_SIZE, OUTPUT_NOTES,
34};
35
36pub const NOTES_TREE_DEPTH: usize = 17;
38pub use poseidon_merkle::ARITY as NOTES_TREE_ARITY;
40pub type NotesTree = poseidon_merkle::Tree<(), NOTES_TREE_DEPTH>;
42pub type NoteOpening = poseidon_merkle::Opening<(), NOTES_TREE_DEPTH>;
44pub type NoteTreeItem = poseidon_merkle::Item<()>;
46
47#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
49#[archive_attr(derive(CheckBytes))]
50pub struct NoteLeaf {
51 pub block_height: u64,
53 pub note: Note,
55}
56
57impl AsRef<Note> for NoteLeaf {
58 fn as_ref(&self) -> &Note {
59 &self.note
60 }
61}
62
63impl cmp::Ord for NoteLeaf {
65 fn cmp(&self, other: &Self) -> cmp::Ordering {
66 self.note.pos().cmp(other.note.pos())
67 }
68}
69
70impl cmp::PartialOrd for NoteLeaf {
71 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
72 Some(self.cmp(other))
73 }
74}
75
76pub const TRANSCRIPT_LABEL: &[u8] = b"dusk-network";
79
80#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
82#[archive_attr(derive(CheckBytes))]
83pub struct Transaction {
84 payload: Payload,
85 proof: Vec<u8>,
86}
87
88impl PartialEq for Transaction {
89 fn eq(&self, other: &Self) -> bool {
90 self.hash() == other.hash()
91 }
92}
93
94impl Eq for Transaction {}
95
96impl Transaction {
97 #[allow(clippy::too_many_lines)]
110 #[allow(clippy::too_many_arguments)]
111 #[allow(clippy::similar_names)]
112 pub fn new<R: RngCore + CryptoRng, P: Prove>(
113 rng: &mut R,
114 sender_sk: &SecretKey,
115 refund_pk: &PublicKey,
116 receiver_pk: &PublicKey,
117 inputs: Vec<(Note, NoteOpening)>,
118 root: BlsScalar,
119 transfer_value: u64,
120 obfuscate_transfer_note: bool,
121 deposit: u64,
122 gas_limit: u64,
123 gas_price: u64,
124 chain_id: u8,
125 data: Option<impl Into<TransactionData>>,
126 prover: &P,
127 ) -> Result<Self, Error> {
128 let data = data.map(Into::into);
129
130 if let Some(TransactionData::Memo(memo)) = data.as_ref() {
131 if memo.len() > MAX_MEMO_SIZE {
132 return Err(Error::MemoTooLarge(memo.len()));
133 }
134 }
135
136 let sender_pk = PublicKey::from(sender_sk);
137 let sender_vk = ViewKey::from(sender_sk);
138
139 let input_len = inputs.len();
141 let mut input_values = Vec::with_capacity(input_len);
142 let mut input_value_blinders = Vec::with_capacity(input_len);
143 let mut input_nullifiers = Vec::with_capacity(input_len);
144 for (note, _opening) in &inputs {
145 let note_nullifier = note.gen_nullifier(sender_sk);
146 for nullifier in &input_nullifiers {
147 if note_nullifier == *nullifier {
148 return Err(Error::Replay);
149 }
150 }
151 input_nullifiers.push(note_nullifier);
152 input_values.push(note.value(Some(&sender_vk))?);
153 input_value_blinders.push(note.value_blinder(Some(&sender_vk))?);
154 }
155 let input_value: u64 = input_values.iter().sum();
156
157 let fee = Fee::new(rng, refund_pk, gas_limit, gas_price);
161 let max_fee = fee.max_fee();
162
163 if input_value < transfer_value + max_fee + deposit {
164 return Err(Error::InsufficientBalance);
165 }
166
167 let transfer_value_blinder = if obfuscate_transfer_note {
169 JubJubScalar::random(&mut *rng)
170 } else {
171 JubJubScalar::zero()
172 };
173 let transfer_sender_blinder = [
174 JubJubScalar::random(&mut *rng),
175 JubJubScalar::random(&mut *rng),
176 ];
177 let change_sender_blinder = [
178 JubJubScalar::random(&mut *rng),
179 JubJubScalar::random(&mut *rng),
180 ];
181 let transfer_note = if obfuscate_transfer_note {
182 Note::obfuscated(
183 rng,
184 &sender_pk,
185 receiver_pk,
186 transfer_value,
187 transfer_value_blinder,
188 transfer_sender_blinder,
189 )
190 } else {
191 Note::transparent(
192 rng,
193 &sender_pk,
194 receiver_pk,
195 transfer_value,
196 transfer_sender_blinder,
197 )
198 };
199 let change_value = input_value - transfer_value - max_fee - deposit;
202 let change_value_blinder = JubJubScalar::random(&mut *rng);
203 let change_note = Note::obfuscated(
204 rng,
205 &sender_pk,
206 refund_pk,
207 change_value,
208 change_value_blinder,
209 change_sender_blinder,
210 );
211 let outputs = [transfer_note.clone(), change_note.clone()];
212
213 let tx_skeleton = TxSkeleton {
215 root,
216 nullifiers: input_nullifiers.clone(),
218 outputs,
219 max_fee,
220 deposit,
221 };
222 let payload = Payload {
223 chain_id,
224 tx_skeleton,
225 fee,
226 data,
227 };
228 let payload_hash = payload.hash();
229
230 let mut input_notes_info = Vec::with_capacity(input_len);
234 inputs
235 .into_iter()
236 .zip(input_nullifiers)
237 .zip(input_values)
238 .zip(input_value_blinders)
239 .for_each(
240 |(
241 (((note, merkle_opening), nullifier), value),
242 value_blinder,
243 )| {
244 let note_sk = sender_sk.gen_note_sk(note.stealth_address());
245 let note_pk_p = JubJubAffine::from(
246 crate::GENERATOR_NUMS_EXTENDED * note_sk.as_ref(),
247 );
248 let signature = note_sk.sign_double(rng, payload_hash);
249 input_notes_info.push(InputNoteInfo {
250 merkle_opening,
251 note,
252 note_pk_p,
253 value,
254 value_blinder,
255 nullifier,
256 signature,
257 });
258 },
259 );
260
261 let transfer_value_commitment =
263 value_commitment(transfer_value, transfer_value_blinder);
264 let transfer_note_sender_enc = match transfer_note.sender() {
265 Sender::Encryption(enc) => enc,
266 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
267 };
268 let change_value_commitment =
269 value_commitment(change_value, change_value_blinder);
270 let change_note_sender_enc = match change_note.sender() {
271 Sender::Encryption(enc) => enc,
272 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
273 };
274 let output_notes_info = [
275 OutputNoteInfo {
276 value: transfer_value,
277 value_commitment: transfer_value_commitment,
278 value_blinder: transfer_value_blinder,
279 note_pk: JubJubAffine::from(
280 transfer_note.stealth_address().note_pk().as_ref(),
281 ),
282 sender_enc: *transfer_note_sender_enc,
283 sender_blinder: transfer_sender_blinder,
284 },
285 OutputNoteInfo {
286 value: change_value,
287 value_commitment: change_value_commitment,
288 value_blinder: change_value_blinder,
289 note_pk: JubJubAffine::from(
290 change_note.stealth_address().note_pk().as_ref(),
291 ),
292 sender_enc: *change_note_sender_enc,
293 sender_blinder: change_sender_blinder,
294 },
295 ];
296
297 let schnorr_sk_a = SchnorrSecretKey::from(sender_sk.a());
299 let sig_a = schnorr_sk_a.sign(rng, payload_hash);
300 let schnorr_sk_b = SchnorrSecretKey::from(sender_sk.b());
301 let sig_b = schnorr_sk_b.sign(rng, payload_hash);
302
303 Ok(Self {
304 payload,
305 proof: prover.prove(
306 &TxCircuitVec {
307 input_notes_info,
308 output_notes_info,
309 payload_hash,
310 root,
311 deposit,
312 max_fee,
313 sender_pk,
314 signatures: (sig_a, sig_b),
315 }
316 .to_var_bytes(),
317 )?,
318 })
319 }
320
321 #[must_use]
325 pub fn from_payload_and_proof(payload: Payload, proof: Vec<u8>) -> Self {
326 Self { payload, proof }
327 }
328
329 pub fn set_proof(&mut self, proof: Vec<u8>) {
339 self.proof = proof;
340 }
341
342 #[must_use]
344 pub fn proof(&self) -> &[u8] {
345 &self.proof
346 }
347
348 #[must_use]
351 pub fn payload_hash(&self) -> BlsScalar {
352 self.payload.hash()
353 }
354
355 #[must_use]
357 pub fn nullifiers(&self) -> &[BlsScalar] {
358 &self.payload.tx_skeleton.nullifiers
359 }
360
361 #[must_use]
363 pub fn root(&self) -> &BlsScalar {
364 &self.payload.tx_skeleton.root
365 }
366
367 #[must_use]
369 pub fn outputs(&self) -> &[Note; OUTPUT_NOTES] {
370 &self.payload.tx_skeleton.outputs
371 }
372
373 #[must_use]
375 pub fn fee(&self) -> &Fee {
376 &self.payload.fee
377 }
378
379 #[must_use]
382 pub fn stealth_address(&self) -> &StealthAddress {
383 &self.payload.fee.stealth_address
384 }
385
386 #[must_use]
389 pub fn sender(&self) -> &Sender {
390 &self.payload.fee.sender
391 }
392
393 #[must_use]
395 pub fn gas_limit(&self) -> u64 {
396 self.payload.fee.gas_limit
397 }
398
399 #[must_use]
401 pub fn gas_price(&self) -> u64 {
402 self.payload.fee.gas_price
403 }
404
405 #[must_use]
407 pub fn chain_id(&self) -> u8 {
408 self.payload.chain_id
409 }
410
411 #[must_use]
413 pub fn max_fee(&self) -> u64 {
414 self.payload.tx_skeleton.max_fee
415 }
416
417 #[must_use]
419 pub fn deposit(&self) -> u64 {
420 self.payload.tx_skeleton.deposit
421 }
422
423 #[must_use]
425 pub fn call(&self) -> Option<&ContractCall> {
426 #[allow(clippy::match_wildcard_for_single_variants)]
427 match self.data()? {
428 TransactionData::Call(ref c) => Some(c),
429 _ => None,
430 }
431 }
432
433 #[must_use]
435 pub fn deploy(&self) -> Option<&ContractDeploy> {
436 #[allow(clippy::match_wildcard_for_single_variants)]
437 match self.data()? {
438 TransactionData::Deploy(ref d) => Some(d),
439 _ => None,
440 }
441 }
442
443 #[must_use]
445 pub fn blob(&self) -> Option<&Vec<BlobData>> {
446 #[allow(clippy::match_wildcard_for_single_variants)]
447 match self.data()? {
448 TransactionData::Blob(ref d) => Some(d),
449 _ => None,
450 }
451 }
452
453 #[must_use]
455 pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
456 #[allow(clippy::match_wildcard_for_single_variants)]
457 match self.data_mut()? {
458 TransactionData::Blob(d) => Some(d),
459 _ => None,
460 }
461 }
462
463 #[must_use]
465 pub fn memo(&self) -> Option<&[u8]> {
466 match self.data()? {
467 TransactionData::Memo(memo) => Some(memo),
468 _ => None,
469 }
470 }
471
472 #[must_use]
474 pub(crate) fn data(&self) -> Option<&TransactionData> {
475 self.payload.data.as_ref()
476 }
477
478 #[must_use]
480 pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
481 self.payload.data.as_mut()
482 }
483
484 #[must_use]
488 pub fn strip_off_bytecode(&self) -> Option<Self> {
489 let deploy = self.deploy()?;
490
491 let stripped_deploy = TransactionData::Deploy(ContractDeploy {
492 owner: deploy.owner.clone(),
493 init_args: deploy.init_args.clone(),
494 bytecode: ContractBytecode {
495 hash: deploy.bytecode.hash,
496 bytes: Vec::new(),
497 },
498 nonce: deploy.nonce,
499 });
500
501 let mut stripped_transaction = self.clone();
502 stripped_transaction.payload.data = Some(stripped_deploy);
503
504 Some(stripped_transaction)
505 }
506
507 #[must_use]
513 pub fn blob_to_memo(&self) -> Option<Self> {
514 let data = self.data()?;
515
516 if let TransactionData::Blob(_) = data {
517 let hash = data.signature_message();
518 let memo = TransactionData::Memo(hash);
519 let mut converted_tx = self.clone();
520 converted_tx.payload.data = Some(memo);
521 Some(converted_tx)
522 } else {
523 None
524 }
525 }
526
527 #[must_use]
529 pub fn to_var_bytes(&self) -> Vec<u8> {
530 let mut bytes = Vec::new();
531
532 let payload_bytes = self.payload.to_var_bytes();
533 bytes.extend((payload_bytes.len() as u64).to_bytes());
534 bytes.extend(payload_bytes);
535
536 bytes.extend((self.proof.len() as u64).to_bytes());
537 bytes.extend(&self.proof);
538
539 bytes
540 }
541
542 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
547 let mut buf = buf;
548
549 let payload_len = usize::try_from(u64::from_reader(&mut buf)?)
550 .map_err(|_| BytesError::InvalidData)?;
551
552 if buf.len() < payload_len {
553 return Err(BytesError::InvalidData);
554 }
555 let (payload_buf, new_buf) = buf.split_at(payload_len);
556
557 let payload = Payload::from_slice(payload_buf)?;
558 buf = new_buf;
559
560 let proof_len = usize::try_from(u64::from_reader(&mut buf)?)
561 .map_err(|_| BytesError::InvalidData)?;
562 let proof = buf[..proof_len].into();
563
564 Ok(Self { payload, proof })
565 }
566
567 #[must_use]
572 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
573 let mut bytes = self.payload.to_hash_input_bytes();
574 bytes.extend(&self.proof);
575 bytes
576 }
577
578 #[must_use]
580 pub fn hash(&self) -> BlsScalar {
581 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
582 }
583
584 #[must_use]
602 pub fn public_inputs(&self) -> Vec<BlsScalar> {
603 let tx_skeleton = &self.payload.tx_skeleton;
604
605 let input_len = tx_skeleton.nullifiers.len();
607 let output_len = tx_skeleton.outputs.len();
608
609 let size =
610 1 + 1
612 + input_len
614 + 2 * output_len
616 + 1 + 1
618 + 2 * output_len
620 + 2 * 4 * output_len;
622 let mut pis = Vec::<BlsScalar>::with_capacity(size);
624 pis.push(self.payload.hash());
625 pis.push(tx_skeleton.root);
626 pis.extend(tx_skeleton.nullifiers().iter());
627 tx_skeleton.outputs().iter().for_each(|note| {
628 let value_commitment = note.value_commitment();
629 pis.push(value_commitment.get_u());
630 pis.push(value_commitment.get_v());
631 });
632 pis.push(tx_skeleton.max_fee().into());
633 pis.push(tx_skeleton.deposit().into());
634 tx_skeleton.outputs().iter().for_each(|note| {
635 let note_pk =
636 JubJubAffine::from(note.stealth_address().note_pk().as_ref());
637 pis.push(note_pk.get_u());
638 pis.push(note_pk.get_v());
639 });
640 tx_skeleton.outputs().iter().for_each(|note| {
641 match note.sender() {
642 Sender::Encryption(sender_enc) => {
643 pis.push(sender_enc[0].0.get_u());
644 pis.push(sender_enc[0].0.get_v());
645 pis.push(sender_enc[0].1.get_u());
646 pis.push(sender_enc[0].1.get_v());
647 pis.push(sender_enc[1].0.get_u());
648 pis.push(sender_enc[1].0.get_v());
649 pis.push(sender_enc[1].1.get_u());
650 pis.push(sender_enc[1].1.get_v());
651 }
652 Sender::ContractInfo(_) => {
653 panic!("All output-notes must provide a sender-encryption")
654 }
655 };
656 });
657
658 pis
659 }
660}
661
662#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
664#[archive_attr(derive(CheckBytes))]
665pub struct Payload {
666 pub chain_id: u8,
668 pub tx_skeleton: TxSkeleton,
670 pub fee: Fee,
672 pub data: Option<TransactionData>,
674}
675
676impl PartialEq for Payload {
677 fn eq(&self, other: &Self) -> bool {
678 self.hash() == other.hash()
679 }
680}
681
682impl Eq for Payload {}
683
684impl Payload {
685 #[must_use]
687 pub fn to_var_bytes(&self) -> Vec<u8> {
688 let mut bytes = Vec::from([self.chain_id]);
689
690 let skeleton_bytes = self.tx_skeleton.to_var_bytes();
692 bytes.extend((skeleton_bytes.len() as u64).to_bytes());
693 bytes.extend(skeleton_bytes);
694
695 bytes.extend(self.fee.to_bytes());
697
698 bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
700
701 bytes
702 }
703
704 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
709 let mut buf = buf;
710
711 let chain_id = u8::from_reader(&mut buf)?;
712
713 #[allow(clippy::cast_possible_truncation)]
715 let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?)
716 .map_err(|_| BytesError::InvalidData)?;
717 let tx_skeleton = TxSkeleton::from_slice(buf)?;
718 buf = &buf[skeleton_len..];
719
720 let fee = Fee::from_reader(&mut buf)?;
722
723 let data = TransactionData::from_slice(buf)?;
725
726 Ok(Self {
727 chain_id,
728 tx_skeleton,
729 fee,
730 data,
731 })
732 }
733
734 #[must_use]
739 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
740 let mut bytes = Vec::from([self.chain_id]);
741
742 bytes.extend(self.tx_skeleton.to_hash_input_bytes());
743
744 if let Some(data) = &self.data {
745 bytes.extend(data.signature_message());
746 }
747
748 bytes
749 }
750
751 #[must_use]
754 pub fn hash(&self) -> BlsScalar {
755 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
756 }
757}
758
759#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)]
761#[archive_attr(derive(CheckBytes))]
762pub struct Fee {
763 pub gas_limit: u64,
765 pub gas_price: u64,
767 pub stealth_address: StealthAddress,
769 pub sender: Sender,
771}
772
773impl PartialEq for Fee {
774 fn eq(&self, other: &Self) -> bool {
775 self.sender == other.sender && self.hash() == other.hash()
776 }
777}
778
779impl Eq for Fee {}
780
781impl Fee {
782 #[must_use]
784 pub fn new<R: RngCore + CryptoRng>(
785 rng: &mut R,
786 refund_pk: &PublicKey,
787 gas_limit: u64,
788 gas_price: u64,
789 ) -> Self {
790 let r = JubJubScalar::random(&mut *rng);
791
792 let sender_blinder = [
793 JubJubScalar::random(&mut *rng),
794 JubJubScalar::random(&mut *rng),
795 ];
796
797 Self::deterministic(
798 &r,
799 refund_pk,
800 gas_limit,
801 gas_price,
802 &sender_blinder,
803 )
804 }
805
806 #[must_use]
808 pub fn deterministic(
809 r: &JubJubScalar,
810 refund_pk: &PublicKey,
811 gas_limit: u64,
812 gas_price: u64,
813 sender_blinder: &[JubJubScalar; 2],
814 ) -> Self {
815 let refund_address = refund_pk.gen_stealth_address(r);
816 let sender = Sender::encrypt(
817 refund_address.note_pk(),
818 refund_pk,
819 sender_blinder,
820 );
821
822 Fee {
823 gas_limit,
824 gas_price,
825 stealth_address: refund_address,
826 sender,
827 }
828 }
829
830 #[must_use]
832 pub fn max_fee(&self) -> u64 {
833 self.gas_limit * self.gas_price
834 }
835
836 #[must_use]
838 pub fn hash(&self) -> BlsScalar {
839 let npk = self.stealth_address.note_pk().as_ref().to_hash_inputs();
840
841 let hash_inputs = [
842 BlsScalar::from(self.gas_limit),
843 BlsScalar::from(self.gas_price),
844 npk[0],
845 npk[1],
846 ];
847 Hash::digest(Domain::Other, &hash_inputs)[0]
848 }
849
850 #[must_use]
855 pub fn gen_remainder_note(
856 &self,
857 gas_consumed: u64,
858 deposit: Option<u64>,
859 ) -> Note {
860 let gas_consumed = cmp::min(gas_consumed, self.gas_limit);
865 let gas_changes = (self.gas_limit - gas_consumed) * self.gas_price;
866
867 Note::transparent_stealth(
868 self.stealth_address,
869 gas_changes + deposit.unwrap_or_default(),
870 self.sender,
871 )
872 }
873}
874
875const SIZE: usize = 2 * u64::SIZE + StealthAddress::SIZE + Sender::SIZE;
876
877impl Serializable<SIZE> for Fee {
878 type Error = BytesError;
879
880 #[must_use]
882 fn to_bytes(&self) -> [u8; Self::SIZE] {
883 let mut buf = [0u8; Self::SIZE];
884
885 buf[..u64::SIZE].copy_from_slice(&self.gas_limit.to_bytes());
886 let mut start = u64::SIZE;
887 buf[start..start + u64::SIZE]
888 .copy_from_slice(&self.gas_price.to_bytes());
889 start += u64::SIZE;
890 buf[start..start + StealthAddress::SIZE]
891 .copy_from_slice(&self.stealth_address.to_bytes());
892 start += StealthAddress::SIZE;
893 buf[start..start + Sender::SIZE]
894 .copy_from_slice(&self.sender.to_bytes());
895
896 buf
897 }
898
899 fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
902 let mut reader = &bytes[..];
903
904 let gas_limit = u64::from_reader(&mut reader)?;
905 let gas_price = u64::from_reader(&mut reader)?;
906 let refund_address = StealthAddress::from_reader(&mut reader)?;
907 let sender = Sender::from_reader(&mut reader)?;
908
909 Ok(Fee {
910 gas_limit,
911 gas_price,
912 stealth_address: refund_address,
913 sender,
914 })
915 }
916}
917
918#[derive(Debug, Clone, PartialEq)]
921pub struct TxCircuitVec {
922 pub input_notes_info: Vec<InputNoteInfo<NOTES_TREE_DEPTH>>,
924 pub output_notes_info: [OutputNoteInfo; OUTPUT_NOTES],
926 pub payload_hash: BlsScalar,
928 pub root: BlsScalar,
930 pub deposit: u64,
932 pub max_fee: u64,
934 pub sender_pk: PublicKey,
936 pub signatures: (SchnorrSignature, SchnorrSignature),
938}
939
940impl TxCircuitVec {
941 #[must_use]
943 pub fn to_var_bytes(&self) -> Vec<u8> {
944 let input_len = self.input_notes_info.len();
945
946 let mut bytes = Vec::with_capacity(Self::size(input_len));
947
948 bytes.extend((input_len as u64).to_bytes());
950
951 for info in &self.input_notes_info {
953 bytes.extend(info.to_var_bytes());
954 }
955 for info in &self.output_notes_info {
956 bytes.extend(info.to_bytes());
957 }
958 bytes.extend(self.payload_hash.to_bytes());
959 bytes.extend(self.root.to_bytes());
960 bytes.extend(self.deposit.to_bytes());
961 bytes.extend(self.max_fee.to_bytes());
962 bytes.extend(self.sender_pk.to_bytes());
963 bytes.extend(self.signatures.0.to_bytes());
964 bytes.extend(self.signatures.1.to_bytes());
965
966 bytes
967 }
968
969 pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
975 let input_len = u64::from_slice(bytes)?;
976
977 #[allow(clippy::cast_possible_truncation)]
979 if bytes.len() < Self::size(input_len as usize) {
980 return Err(BytesError::BadLength {
981 found: bytes.len(),
982 expected: Self::size(input_len as usize),
983 });
984 }
985
986 let bytes = &bytes[u64::SIZE..];
987 let circuit: TxCircuitVec = match input_len {
988 1 => TxCircuit::<NOTES_TREE_DEPTH, 1>::from_slice(bytes)?.into(),
989 2 => TxCircuit::<NOTES_TREE_DEPTH, 2>::from_slice(bytes)?.into(),
990 3 => TxCircuit::<NOTES_TREE_DEPTH, 3>::from_slice(bytes)?.into(),
991 4 => TxCircuit::<NOTES_TREE_DEPTH, 4>::from_slice(bytes)?.into(),
992 _ => return Err(BytesError::InvalidData),
993 };
994
995 Ok(circuit)
996 }
997
998 const fn size(input_len: usize) -> usize {
999 u64::SIZE
1000 + input_len * InputNoteInfo::<NOTES_TREE_DEPTH>::SIZE
1001 + OUTPUT_NOTES * OutputNoteInfo::SIZE
1002 + 2 * BlsScalar::SIZE
1003 + 2 * u64::SIZE
1004 + PublicKey::SIZE
1005 + 2 * SchnorrSignature::SIZE
1006 }
1007}
1008
1009impl<const I: usize> From<TxCircuit<NOTES_TREE_DEPTH, I>> for TxCircuitVec {
1010 fn from(circuit: TxCircuit<NOTES_TREE_DEPTH, I>) -> Self {
1011 TxCircuitVec {
1012 input_notes_info: circuit.input_notes_info.to_vec(),
1013 output_notes_info: circuit.output_notes_info,
1014 payload_hash: circuit.payload_hash,
1015 root: circuit.root,
1016 deposit: circuit.deposit,
1017 max_fee: circuit.max_fee,
1018 sender_pk: circuit.sender_pk,
1019 signatures: circuit.signatures,
1020 }
1021 }
1022}
1023
1024pub trait Prove {
1027 fn prove(&self, tx_circuit_vec_bytes: &[u8]) -> Result<Vec<u8>, Error>;
1037}