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, MAX_MEMO_SIZE,
25 TransactionData,
26};
27use crate::{BlsScalar, Error, JubJubAffine, JubJubScalar};
28
29pub use phoenix_circuits::{InputNoteInfo, OutputNoteInfo, TxCircuit};
31pub use phoenix_core::{
32 Error as CoreError, NOTE_VAL_ENC_SIZE, Note, OUTPUT_NOTES, PublicKey,
33 SecretKey, Sender, StealthAddress, TxSkeleton, ViewKey, value_commitment,
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 max_fee = gas_limit * gas_price;
164
165 if input_value < transfer_value + max_fee + deposit {
166 return Err(Error::InsufficientBalance);
167 }
168
169 let transfer_value_blinder = if obfuscate_transfer_note {
171 JubJubScalar::random(&mut *rng)
172 } else {
173 JubJubScalar::zero()
174 };
175 let transfer_sender_blinder = [
176 JubJubScalar::random(&mut *rng),
177 JubJubScalar::random(&mut *rng),
178 ];
179 let change_sender_blinder = [
180 JubJubScalar::random(&mut *rng),
181 JubJubScalar::random(&mut *rng),
182 ];
183 let transfer_note = if obfuscate_transfer_note {
184 Note::obfuscated(
185 rng,
186 &sender_pk,
187 receiver_pk,
188 transfer_value,
189 transfer_value_blinder,
190 transfer_sender_blinder,
191 )
192 } else {
193 Note::transparent(
194 rng,
195 &sender_pk,
196 receiver_pk,
197 transfer_value,
198 transfer_sender_blinder,
199 )
200 };
201 let change_value = input_value - transfer_value - max_fee - deposit;
204 let change_value_blinder = JubJubScalar::random(&mut *rng);
205 let change_note = Note::obfuscated(
206 rng,
207 &sender_pk,
208 refund_pk,
209 change_value,
210 change_value_blinder,
211 change_sender_blinder,
212 );
213
214 let refund_sender_blinder = [
217 JubJubScalar::random(&mut *rng),
218 JubJubScalar::random(&mut *rng),
219 ];
220 let refund_sa = *change_note.stealth_address();
221 let fee = Fee {
222 gas_limit,
223 gas_price,
224 stealth_address: refund_sa,
225 sender: Sender::encrypt(
226 refund_sa.note_pk(),
227 refund_pk,
228 &refund_sender_blinder,
229 ),
230 };
231
232 let outputs = [transfer_note.clone(), change_note.clone()];
233
234 let tx_skeleton = TxSkeleton {
236 root,
237 nullifiers: input_nullifiers.clone(),
239 outputs,
240 max_fee,
241 deposit,
242 };
243 let payload = Payload {
244 chain_id,
245 tx_skeleton,
246 fee,
247 data,
248 };
249 let payload_hash = payload.hash();
250
251 let mut input_notes_info = Vec::with_capacity(input_len);
255 inputs
256 .into_iter()
257 .zip(input_nullifiers)
258 .zip(input_values)
259 .zip(input_value_blinders)
260 .for_each(
261 |(
262 (((note, merkle_opening), nullifier), value),
263 value_blinder,
264 )| {
265 let note_sk = sender_sk.gen_note_sk(note.stealth_address());
266 let note_pk_p = JubJubAffine::from(
267 crate::GENERATOR_NUMS_EXTENDED * note_sk.as_ref(),
268 );
269 let signature = note_sk.sign_double(rng, payload_hash);
270 input_notes_info.push(InputNoteInfo {
271 merkle_opening,
272 note,
273 note_pk_p,
274 value,
275 value_blinder,
276 nullifier,
277 signature,
278 });
279 },
280 );
281
282 let transfer_value_commitment =
284 value_commitment(transfer_value, transfer_value_blinder);
285 let transfer_note_sender_enc = match transfer_note.sender() {
286 Sender::Encryption(enc) => enc,
287 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
288 };
289 let change_value_commitment =
290 value_commitment(change_value, change_value_blinder);
291 let change_note_sender_enc = match change_note.sender() {
292 Sender::Encryption(enc) => enc,
293 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
294 };
295 let output_notes_info = [
296 OutputNoteInfo {
297 value: transfer_value,
298 value_commitment: transfer_value_commitment,
299 value_blinder: transfer_value_blinder,
300 note_pk: JubJubAffine::from(
301 transfer_note.stealth_address().note_pk().as_ref(),
302 ),
303 sender_enc: *transfer_note_sender_enc,
304 sender_blinder: transfer_sender_blinder,
305 },
306 OutputNoteInfo {
307 value: change_value,
308 value_commitment: change_value_commitment,
309 value_blinder: change_value_blinder,
310 note_pk: JubJubAffine::from(
311 change_note.stealth_address().note_pk().as_ref(),
312 ),
313 sender_enc: *change_note_sender_enc,
314 sender_blinder: change_sender_blinder,
315 },
316 ];
317
318 let schnorr_sk_a = SchnorrSecretKey::from(sender_sk.a());
320 let sig_a = schnorr_sk_a.sign(rng, payload_hash);
321 let schnorr_sk_b = SchnorrSecretKey::from(sender_sk.b());
322 let sig_b = schnorr_sk_b.sign(rng, payload_hash);
323
324 Ok(Self {
325 payload,
326 proof: prover.prove(
327 &TxCircuitVec {
328 input_notes_info,
329 output_notes_info,
330 payload_hash,
331 root,
332 deposit,
333 max_fee,
334 sender_pk,
335 signatures: (sig_a, sig_b),
336 }
337 .to_var_bytes(),
338 )?,
339 })
340 }
341
342 #[must_use]
346 pub fn from_payload_and_proof(payload: Payload, proof: Vec<u8>) -> Self {
347 Self { payload, proof }
348 }
349
350 pub fn set_proof(&mut self, proof: Vec<u8>) {
360 self.proof = proof;
361 }
362
363 #[must_use]
365 pub fn proof(&self) -> &[u8] {
366 &self.proof
367 }
368
369 #[must_use]
372 pub fn payload_hash(&self) -> BlsScalar {
373 self.payload.hash()
374 }
375
376 #[must_use]
378 pub fn nullifiers(&self) -> &[BlsScalar] {
379 &self.payload.tx_skeleton.nullifiers
380 }
381
382 #[must_use]
384 pub fn root(&self) -> &BlsScalar {
385 &self.payload.tx_skeleton.root
386 }
387
388 #[must_use]
390 pub fn outputs(&self) -> &[Note; OUTPUT_NOTES] {
391 &self.payload.tx_skeleton.outputs
392 }
393
394 #[must_use]
396 pub fn fee(&self) -> &Fee {
397 &self.payload.fee
398 }
399
400 #[must_use]
403 pub fn stealth_address(&self) -> &StealthAddress {
404 &self.payload.fee.stealth_address
405 }
406
407 #[must_use]
410 pub fn sender(&self) -> &Sender {
411 &self.payload.fee.sender
412 }
413
414 #[must_use]
416 pub fn gas_limit(&self) -> u64 {
417 self.payload.fee.gas_limit
418 }
419
420 #[must_use]
422 pub fn gas_price(&self) -> u64 {
423 self.payload.fee.gas_price
424 }
425
426 #[must_use]
428 pub fn chain_id(&self) -> u8 {
429 self.payload.chain_id
430 }
431
432 #[must_use]
434 pub fn max_fee(&self) -> u64 {
435 self.payload.tx_skeleton.max_fee
436 }
437
438 #[must_use]
440 pub fn deposit(&self) -> u64 {
441 self.payload.tx_skeleton.deposit
442 }
443
444 #[must_use]
446 pub fn call(&self) -> Option<&ContractCall> {
447 #[allow(clippy::match_wildcard_for_single_variants)]
448 match self.data()? {
449 TransactionData::Call(c) => Some(c),
450 _ => None,
451 }
452 }
453
454 #[must_use]
456 pub fn deploy(&self) -> Option<&ContractDeploy> {
457 #[allow(clippy::match_wildcard_for_single_variants)]
458 match self.data()? {
459 TransactionData::Deploy(d) => Some(d),
460 _ => None,
461 }
462 }
463
464 #[must_use]
466 pub fn blob(&self) -> Option<&Vec<BlobData>> {
467 #[allow(clippy::match_wildcard_for_single_variants)]
468 match self.data()? {
469 TransactionData::Blob(d) => Some(d),
470 _ => None,
471 }
472 }
473
474 #[must_use]
476 pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
477 #[allow(clippy::match_wildcard_for_single_variants)]
478 match self.data_mut()? {
479 TransactionData::Blob(d) => Some(d),
480 _ => None,
481 }
482 }
483
484 #[must_use]
486 pub fn memo(&self) -> Option<&[u8]> {
487 match self.data()? {
488 TransactionData::Memo(memo) => Some(memo),
489 _ => None,
490 }
491 }
492
493 #[must_use]
495 pub(crate) fn data(&self) -> Option<&TransactionData> {
496 self.payload.data.as_ref()
497 }
498
499 #[must_use]
501 pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
502 self.payload.data.as_mut()
503 }
504
505 #[must_use]
509 pub fn strip_off_bytecode(&self) -> Option<Self> {
510 let deploy = self.deploy()?;
511
512 let stripped_deploy = TransactionData::Deploy(ContractDeploy {
513 owner: deploy.owner.clone(),
514 init_args: deploy.init_args.clone(),
515 bytecode: ContractBytecode {
516 hash: deploy.bytecode.hash,
517 bytes: Vec::new(),
518 },
519 nonce: deploy.nonce,
520 });
521
522 let mut stripped_transaction = self.clone();
523 stripped_transaction.payload.data = Some(stripped_deploy);
524
525 Some(stripped_transaction)
526 }
527
528 #[must_use]
534 pub fn blob_to_memo(&self) -> Option<Self> {
535 let data = self.data()?;
536
537 if let TransactionData::Blob(_) = data {
538 let hash = data.signature_message();
539 let memo = TransactionData::Memo(hash);
540 let mut converted_tx = self.clone();
541 converted_tx.payload.data = Some(memo);
542 Some(converted_tx)
543 } else {
544 None
545 }
546 }
547
548 #[must_use]
550 pub fn to_var_bytes(&self) -> Vec<u8> {
551 let mut bytes = Vec::new();
552
553 let payload_bytes = self.payload.to_var_bytes();
554 bytes.extend((payload_bytes.len() as u64).to_bytes());
555 bytes.extend(payload_bytes);
556
557 bytes.extend((self.proof.len() as u64).to_bytes());
558 bytes.extend(&self.proof);
559
560 bytes
561 }
562
563 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
568 let mut buf = buf;
569
570 let payload_len = usize::try_from(u64::from_reader(&mut buf)?)
571 .map_err(|_| BytesError::InvalidData)?;
572
573 if buf.len() < payload_len {
574 return Err(BytesError::InvalidData);
575 }
576 let (payload_buf, new_buf) = buf.split_at(payload_len);
577
578 let payload = Payload::from_slice(payload_buf)?;
579 buf = new_buf;
580
581 let proof_len = usize::try_from(u64::from_reader(&mut buf)?)
582 .map_err(|_| BytesError::InvalidData)?;
583 if buf.len() < proof_len {
584 return Err(BytesError::InvalidData);
585 }
586 let proof = buf[..proof_len].into();
587
588 Ok(Self { payload, proof })
589 }
590
591 #[must_use]
596 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
597 let mut bytes = self.payload.to_hash_input_bytes();
598 bytes.extend(&self.proof);
599 bytes
600 }
601
602 #[must_use]
604 pub fn hash(&self) -> BlsScalar {
605 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
606 }
607
608 #[must_use]
626 pub fn public_inputs(&self) -> Vec<BlsScalar> {
627 let tx_skeleton = &self.payload.tx_skeleton;
628
629 let input_len = tx_skeleton.nullifiers.len();
631 let output_len = tx_skeleton.outputs.len();
632
633 let size =
634 1 + 1
636 + input_len
638 + 2 * output_len
640 + 1 + 1
642 + 2 * output_len
644 + 2 * 4 * output_len;
646 let mut pis = Vec::<BlsScalar>::with_capacity(size);
648 pis.push(self.payload.hash());
649 pis.push(tx_skeleton.root);
650 pis.extend(tx_skeleton.nullifiers().iter());
651 tx_skeleton.outputs().iter().for_each(|note| {
652 let value_commitment = note.value_commitment();
653 pis.push(value_commitment.get_u());
654 pis.push(value_commitment.get_v());
655 });
656 pis.push(tx_skeleton.max_fee().into());
657 pis.push(tx_skeleton.deposit().into());
658 tx_skeleton.outputs().iter().for_each(|note| {
659 let note_pk =
660 JubJubAffine::from(note.stealth_address().note_pk().as_ref());
661 pis.push(note_pk.get_u());
662 pis.push(note_pk.get_v());
663 });
664 tx_skeleton
665 .outputs()
666 .iter()
667 .for_each(|note| match note.sender() {
668 Sender::Encryption(sender_enc) => {
669 pis.push(sender_enc[0].0.get_u());
670 pis.push(sender_enc[0].0.get_v());
671 pis.push(sender_enc[0].1.get_u());
672 pis.push(sender_enc[0].1.get_v());
673 pis.push(sender_enc[1].0.get_u());
674 pis.push(sender_enc[1].0.get_v());
675 pis.push(sender_enc[1].1.get_u());
676 pis.push(sender_enc[1].1.get_v());
677 }
678 Sender::ContractInfo(_) => {
679 panic!("All output-notes must provide a sender-encryption")
680 }
681 });
682
683 pis
684 }
685}
686
687#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
689#[archive_attr(derive(CheckBytes))]
690pub struct Payload {
691 pub chain_id: u8,
693 pub tx_skeleton: TxSkeleton,
695 pub fee: Fee,
697 pub data: Option<TransactionData>,
699}
700
701impl PartialEq for Payload {
702 fn eq(&self, other: &Self) -> bool {
703 self.hash() == other.hash()
704 }
705}
706
707impl Eq for Payload {}
708
709impl Payload {
710 #[must_use]
712 pub fn to_var_bytes(&self) -> Vec<u8> {
713 let mut bytes = Vec::from([self.chain_id]);
714
715 let skeleton_bytes = self.tx_skeleton.to_var_bytes();
717 bytes.extend((skeleton_bytes.len() as u64).to_bytes());
718 bytes.extend(skeleton_bytes);
719
720 bytes.extend(self.fee.to_bytes());
722
723 bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
725
726 bytes
727 }
728
729 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
734 let mut buf = buf;
735
736 let chain_id = u8::from_reader(&mut buf)?;
737
738 #[allow(clippy::cast_possible_truncation)]
740 let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?)
741 .map_err(|_| BytesError::InvalidData)?;
742 let tx_skeleton = TxSkeleton::from_slice(buf)?;
743 buf = &buf[skeleton_len..];
744
745 let fee = Fee::from_reader(&mut buf)?;
747
748 let data = TransactionData::from_slice(buf)?;
750
751 Ok(Self {
752 chain_id,
753 tx_skeleton,
754 fee,
755 data,
756 })
757 }
758
759 #[must_use]
764 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
765 let mut bytes = Vec::from([self.chain_id]);
766
767 bytes.extend(self.tx_skeleton.to_hash_input_bytes());
768
769 if let Some(data) = &self.data {
770 bytes.extend(data.signature_message());
771 }
772
773 bytes
774 }
775
776 #[must_use]
779 pub fn hash(&self) -> BlsScalar {
780 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
781 }
782}
783
784#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)]
786#[archive_attr(derive(CheckBytes))]
787pub struct Fee {
788 pub gas_limit: u64,
790 pub gas_price: u64,
792 pub stealth_address: StealthAddress,
794 pub sender: Sender,
796}
797
798impl PartialEq for Fee {
799 fn eq(&self, other: &Self) -> bool {
800 self.sender == other.sender && self.hash() == other.hash()
801 }
802}
803
804impl Eq for Fee {}
805
806impl Fee {
807 #[must_use]
809 pub fn new<R: RngCore + CryptoRng>(
810 rng: &mut R,
811 refund_pk: &PublicKey,
812 gas_limit: u64,
813 gas_price: u64,
814 ) -> Self {
815 let r = JubJubScalar::random(&mut *rng);
816
817 let sender_blinder = [
818 JubJubScalar::random(&mut *rng),
819 JubJubScalar::random(&mut *rng),
820 ];
821
822 Self::deterministic(
823 &r,
824 refund_pk,
825 gas_limit,
826 gas_price,
827 &sender_blinder,
828 )
829 }
830
831 #[must_use]
833 pub fn deterministic(
834 r: &JubJubScalar,
835 refund_pk: &PublicKey,
836 gas_limit: u64,
837 gas_price: u64,
838 sender_blinder: &[JubJubScalar; 2],
839 ) -> Self {
840 let refund_address = refund_pk.gen_stealth_address(r);
841 let sender = Sender::encrypt(
842 refund_address.note_pk(),
843 refund_pk,
844 sender_blinder,
845 );
846
847 Fee {
848 gas_limit,
849 gas_price,
850 stealth_address: refund_address,
851 sender,
852 }
853 }
854
855 #[must_use]
857 pub fn max_fee(&self) -> u64 {
858 self.gas_limit * self.gas_price
859 }
860
861 #[must_use]
863 pub fn hash(&self) -> BlsScalar {
864 let npk = self.stealth_address.note_pk().as_ref().to_hash_inputs();
865
866 let hash_inputs = [
867 BlsScalar::from(self.gas_limit),
868 BlsScalar::from(self.gas_price),
869 npk[0],
870 npk[1],
871 ];
872 Hash::digest(Domain::Other, &hash_inputs)[0]
873 }
874
875 #[must_use]
880 pub fn gen_remainder_note(
881 &self,
882 gas_consumed: u64,
883 deposit: Option<u64>,
884 ) -> Note {
885 let gas_consumed = cmp::min(gas_consumed, self.gas_limit);
890 let gas_changes = (self.gas_limit - gas_consumed) * self.gas_price;
891
892 Note::transparent_stealth(
893 self.stealth_address,
894 gas_changes + deposit.unwrap_or_default(),
895 self.sender,
896 )
897 }
898}
899
900const SIZE: usize = 2 * u64::SIZE + StealthAddress::SIZE + Sender::SIZE;
901
902impl Serializable<SIZE> for Fee {
903 type Error = BytesError;
904
905 fn to_bytes(&self) -> [u8; Self::SIZE] {
907 let mut buf = [0u8; Self::SIZE];
908
909 buf[..u64::SIZE].copy_from_slice(&self.gas_limit.to_bytes());
910 let mut start = u64::SIZE;
911 buf[start..start + u64::SIZE]
912 .copy_from_slice(&self.gas_price.to_bytes());
913 start += u64::SIZE;
914 buf[start..start + StealthAddress::SIZE]
915 .copy_from_slice(&self.stealth_address.to_bytes());
916 start += StealthAddress::SIZE;
917 buf[start..start + Sender::SIZE]
918 .copy_from_slice(&self.sender.to_bytes());
919
920 buf
921 }
922
923 fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
926 let mut reader = &bytes[..];
927
928 let gas_limit = u64::from_reader(&mut reader)?;
929 let gas_price = u64::from_reader(&mut reader)?;
930 let refund_address = StealthAddress::from_reader(&mut reader)?;
931 let sender = Sender::from_reader(&mut reader)?;
932
933 Ok(Fee {
934 gas_limit,
935 gas_price,
936 stealth_address: refund_address,
937 sender,
938 })
939 }
940}
941
942#[derive(Debug, Clone, PartialEq)]
945pub struct TxCircuitVec {
946 pub input_notes_info: Vec<InputNoteInfo<NOTES_TREE_DEPTH>>,
948 pub output_notes_info: [OutputNoteInfo; OUTPUT_NOTES],
950 pub payload_hash: BlsScalar,
952 pub root: BlsScalar,
954 pub deposit: u64,
956 pub max_fee: u64,
958 pub sender_pk: PublicKey,
960 pub signatures: (SchnorrSignature, SchnorrSignature),
962}
963
964impl TxCircuitVec {
965 #[must_use]
967 pub fn to_var_bytes(&self) -> Vec<u8> {
968 let input_len = self.input_notes_info.len();
969
970 let mut bytes = Vec::with_capacity(Self::size(input_len));
971
972 bytes.extend((input_len as u64).to_bytes());
974
975 for info in &self.input_notes_info {
977 bytes.extend(info.to_var_bytes());
978 }
979 for info in &self.output_notes_info {
980 bytes.extend(info.to_bytes());
981 }
982 bytes.extend(self.payload_hash.to_bytes());
983 bytes.extend(self.root.to_bytes());
984 bytes.extend(self.deposit.to_bytes());
985 bytes.extend(self.max_fee.to_bytes());
986 bytes.extend(self.sender_pk.to_bytes());
987 bytes.extend(self.signatures.0.to_bytes());
988 bytes.extend(self.signatures.1.to_bytes());
989
990 bytes
991 }
992
993 pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
999 let input_len = u64::from_slice(bytes)?;
1000
1001 #[allow(clippy::cast_possible_truncation)]
1003 if bytes.len() < Self::size(input_len as usize) {
1004 return Err(BytesError::BadLength {
1005 found: bytes.len(),
1006 expected: Self::size(input_len as usize),
1007 });
1008 }
1009
1010 let bytes = &bytes[u64::SIZE..];
1011 let circuit: TxCircuitVec = match input_len {
1012 1 => TxCircuit::<NOTES_TREE_DEPTH, 1>::from_slice(bytes)?.into(),
1013 2 => TxCircuit::<NOTES_TREE_DEPTH, 2>::from_slice(bytes)?.into(),
1014 3 => TxCircuit::<NOTES_TREE_DEPTH, 3>::from_slice(bytes)?.into(),
1015 4 => TxCircuit::<NOTES_TREE_DEPTH, 4>::from_slice(bytes)?.into(),
1016 _ => return Err(BytesError::InvalidData),
1017 };
1018
1019 Ok(circuit)
1020 }
1021
1022 const fn size(input_len: usize) -> usize {
1023 u64::SIZE
1024 + input_len * InputNoteInfo::<NOTES_TREE_DEPTH>::SIZE
1025 + OUTPUT_NOTES * OutputNoteInfo::SIZE
1026 + 2 * BlsScalar::SIZE
1027 + 2 * u64::SIZE
1028 + PublicKey::SIZE
1029 + 2 * SchnorrSignature::SIZE
1030 }
1031}
1032
1033impl<const I: usize> From<TxCircuit<NOTES_TREE_DEPTH, I>> for TxCircuitVec {
1034 fn from(circuit: TxCircuit<NOTES_TREE_DEPTH, I>) -> Self {
1035 TxCircuitVec {
1036 input_notes_info: circuit.input_notes_info.to_vec(),
1037 output_notes_info: circuit.output_notes_info,
1038 payload_hash: circuit.payload_hash,
1039 root: circuit.root,
1040 deposit: circuit.deposit,
1041 max_fee: circuit.max_fee,
1042 sender_pk: circuit.sender_pk,
1043 signatures: circuit.signatures,
1044 }
1045 }
1046}
1047
1048pub trait Prove {
1051 fn prove(&self, tx_circuit_vec_bytes: &[u8]) -> Result<Vec<u8>, Error>;
1061}