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;
17pub use phoenix_circuits::{InputNoteInfo, OutputNoteInfo, TxCircuit};
19pub use phoenix_core::{
20 Error as CoreError, NOTE_VAL_ENC_SIZE, Note, OUTPUT_NOTES, PublicKey,
21 SecretKey, Sender, StealthAddress, TxSkeleton, ViewKey, value_commitment,
22};
23use rand::{CryptoRng, RngCore};
24use rkyv::{Archive, Deserialize, Serialize};
25
26use crate::signatures::schnorr::{
27 SecretKey as SchnorrSecretKey, Signature as SchnorrSignature,
28};
29use crate::transfer::data::{
30 BlobData, ContractBytecode, ContractCall, ContractDeploy, MAX_MEMO_SIZE,
31 TransactionData,
32};
33use crate::{BlsScalar, Error, JubJubAffine, JubJubScalar};
34
35pub const NOTES_TREE_DEPTH: usize = 17;
37pub use poseidon_merkle::ARITY as NOTES_TREE_ARITY;
39pub type NotesTree = poseidon_merkle::Tree<(), NOTES_TREE_DEPTH>;
41pub type NoteOpening = poseidon_merkle::Opening<(), NOTES_TREE_DEPTH>;
43pub type NoteTreeItem = poseidon_merkle::Item<()>;
45
46#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
48#[archive_attr(derive(CheckBytes))]
49pub struct NoteLeaf {
50 pub block_height: u64,
52 pub note: Note,
54}
55
56impl AsRef<Note> for NoteLeaf {
57 fn as_ref(&self) -> &Note {
58 &self.note
59 }
60}
61
62impl cmp::Ord for NoteLeaf {
64 fn cmp(&self, other: &Self) -> cmp::Ordering {
65 self.note.pos().cmp(other.note.pos())
66 }
67}
68
69impl cmp::PartialOrd for NoteLeaf {
70 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
71 Some(self.cmp(other))
72 }
73}
74
75pub const TRANSCRIPT_LABEL: &[u8] = b"dusk-network";
78
79#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
81#[archive_attr(derive(CheckBytes))]
82pub struct Transaction {
83 payload: Payload,
84 proof: Vec<u8>,
85}
86
87impl PartialEq for Transaction {
88 fn eq(&self, other: &Self) -> bool {
89 self.hash() == other.hash()
90 }
91}
92
93impl Eq for Transaction {}
94
95impl Transaction {
96 fn from_slice_with(
97 buf: &[u8],
98 parse_payload: fn(&[u8]) -> Result<Payload, BytesError>,
99 ) -> Result<Self, BytesError> {
100 let mut buf = buf;
101 let payload = parse_payload(read_len_prefixed(&mut buf)?)?;
102 let proof = read_len_prefixed(&mut buf)?.into();
103
104 if !buf.is_empty() {
105 return Err(BytesError::InvalidData);
106 }
107
108 Ok(Self { payload, proof })
109 }
110
111 #[allow(clippy::too_many_lines)]
124 #[allow(clippy::too_many_arguments)]
125 #[allow(clippy::similar_names)]
126 pub fn new<R: RngCore + CryptoRng, P: Prove>(
127 rng: &mut R,
128 sender_sk: &SecretKey,
129 refund_pk: &PublicKey,
130 receiver_pk: &PublicKey,
131 inputs: Vec<(Note, NoteOpening)>,
132 root: BlsScalar,
133 transfer_value: u64,
134 obfuscate_transfer_note: bool,
135 deposit: u64,
136 gas_limit: u64,
137 gas_price: u64,
138 chain_id: u8,
139 data: Option<impl Into<TransactionData>>,
140 prover: &P,
141 ) -> Result<Self, Error> {
142 let data = data.map(Into::into);
143
144 if let Some(TransactionData::Memo(memo)) = data.as_ref()
145 && memo.len() > MAX_MEMO_SIZE
146 {
147 return Err(Error::MemoTooLarge(memo.len()));
148 }
149
150 let sender_pk = PublicKey::from(sender_sk);
151 let sender_vk = ViewKey::from(sender_sk);
152
153 let input_len = inputs.len();
155 let mut input_values = Vec::with_capacity(input_len);
156 let mut input_value_blinders = Vec::with_capacity(input_len);
157 let mut input_nullifiers = Vec::with_capacity(input_len);
158 for (note, _opening) in &inputs {
159 let note_nullifier = note.gen_nullifier(sender_sk);
160 for nullifier in &input_nullifiers {
161 if note_nullifier == *nullifier {
162 return Err(Error::Replay);
163 }
164 }
165 input_nullifiers.push(note_nullifier);
166 input_values.push(note.value(Some(&sender_vk))?);
167 input_value_blinders.push(note.value_blinder(Some(&sender_vk))?);
168 }
169 let input_value: u64 = input_values.iter().sum();
170
171 let max_fee = gas_limit * gas_price;
178
179 if input_value < transfer_value + max_fee + deposit {
180 return Err(Error::InsufficientBalance);
181 }
182
183 let transfer_value_blinder = if obfuscate_transfer_note {
185 JubJubScalar::random(&mut *rng)
186 } else {
187 JubJubScalar::zero()
188 };
189 let transfer_sender_blinder = [
190 JubJubScalar::random(&mut *rng),
191 JubJubScalar::random(&mut *rng),
192 ];
193 let change_sender_blinder = [
194 JubJubScalar::random(&mut *rng),
195 JubJubScalar::random(&mut *rng),
196 ];
197 let transfer_note = if obfuscate_transfer_note {
198 Note::obfuscated(
199 rng,
200 &sender_pk,
201 receiver_pk,
202 transfer_value,
203 transfer_value_blinder,
204 transfer_sender_blinder,
205 )
206 } else {
207 Note::transparent(
208 rng,
209 &sender_pk,
210 receiver_pk,
211 transfer_value,
212 transfer_sender_blinder,
213 )
214 };
215 let change_value = input_value - transfer_value - max_fee - deposit;
218 let change_value_blinder = JubJubScalar::random(&mut *rng);
219 let change_note = Note::obfuscated(
220 rng,
221 &sender_pk,
222 refund_pk,
223 change_value,
224 change_value_blinder,
225 change_sender_blinder,
226 );
227
228 let refund_sender_blinder = [
231 JubJubScalar::random(&mut *rng),
232 JubJubScalar::random(&mut *rng),
233 ];
234 let refund_sa = *change_note.stealth_address();
235 let fee = Fee {
236 gas_limit,
237 gas_price,
238 stealth_address: refund_sa,
239 sender: Sender::encrypt(
240 refund_sa.note_pk(),
241 refund_pk,
242 &refund_sender_blinder,
243 ),
244 };
245
246 let outputs = [transfer_note.clone(), change_note.clone()];
247
248 let tx_skeleton = TxSkeleton {
250 root,
251 nullifiers: input_nullifiers.clone(),
253 outputs,
254 max_fee,
255 deposit,
256 };
257 let payload = Payload {
258 chain_id,
259 tx_skeleton,
260 fee,
261 data,
262 };
263 let payload_hash = payload.hash();
264
265 let mut input_notes_info = Vec::with_capacity(input_len);
269 inputs
270 .into_iter()
271 .zip(input_nullifiers)
272 .zip(input_values)
273 .zip(input_value_blinders)
274 .for_each(
275 |(
276 (((note, merkle_opening), nullifier), value),
277 value_blinder,
278 )| {
279 let note_sk = sender_sk.gen_note_sk(note.stealth_address());
280 let note_pk_p = JubJubAffine::from(
281 crate::GENERATOR_NUMS_EXTENDED * note_sk.as_ref(),
282 );
283 let signature = note_sk.sign_double(rng, payload_hash);
284 input_notes_info.push(InputNoteInfo {
285 merkle_opening,
286 note,
287 note_pk_p,
288 value,
289 value_blinder,
290 nullifier,
291 signature,
292 });
293 },
294 );
295
296 let transfer_value_commitment =
298 value_commitment(transfer_value, transfer_value_blinder);
299 let transfer_note_sender_enc = match transfer_note.sender() {
300 Sender::Encryption(enc) => enc,
301 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
302 };
303 let change_value_commitment =
304 value_commitment(change_value, change_value_blinder);
305 let change_note_sender_enc = match change_note.sender() {
306 Sender::Encryption(enc) => enc,
307 Sender::ContractInfo(_) => unreachable!("The sender is encrypted"),
308 };
309 let output_notes_info = [
310 OutputNoteInfo {
311 value: transfer_value,
312 value_commitment: transfer_value_commitment,
313 value_blinder: transfer_value_blinder,
314 note_pk: JubJubAffine::from(
315 transfer_note.stealth_address().note_pk().as_ref(),
316 ),
317 sender_enc: *transfer_note_sender_enc,
318 sender_blinder: transfer_sender_blinder,
319 },
320 OutputNoteInfo {
321 value: change_value,
322 value_commitment: change_value_commitment,
323 value_blinder: change_value_blinder,
324 note_pk: JubJubAffine::from(
325 change_note.stealth_address().note_pk().as_ref(),
326 ),
327 sender_enc: *change_note_sender_enc,
328 sender_blinder: change_sender_blinder,
329 },
330 ];
331
332 let schnorr_sk_a = SchnorrSecretKey::from(sender_sk.a());
334 let sig_a = schnorr_sk_a.sign(rng, payload_hash);
335 let schnorr_sk_b = SchnorrSecretKey::from(sender_sk.b());
336 let sig_b = schnorr_sk_b.sign(rng, payload_hash);
337
338 Ok(Self {
339 payload,
340 proof: prover.prove(
341 &TxCircuitVec {
342 input_notes_info,
343 output_notes_info,
344 payload_hash,
345 root,
346 deposit,
347 max_fee,
348 sender_pk,
349 signatures: (sig_a, sig_b),
350 }
351 .to_var_bytes(),
352 )?,
353 })
354 }
355
356 #[must_use]
360 pub fn from_payload_and_proof(payload: Payload, proof: Vec<u8>) -> Self {
361 Self { payload, proof }
362 }
363
364 pub fn set_proof(&mut self, proof: Vec<u8>) {
374 self.proof = proof;
375 }
376
377 #[must_use]
379 pub fn proof(&self) -> &[u8] {
380 &self.proof
381 }
382
383 #[must_use]
386 pub fn payload_hash(&self) -> BlsScalar {
387 self.payload.hash()
388 }
389
390 #[must_use]
392 pub fn nullifiers(&self) -> &[BlsScalar] {
393 &self.payload.tx_skeleton.nullifiers
394 }
395
396 #[must_use]
398 pub fn root(&self) -> &BlsScalar {
399 &self.payload.tx_skeleton.root
400 }
401
402 #[must_use]
404 pub fn outputs(&self) -> &[Note; OUTPUT_NOTES] {
405 &self.payload.tx_skeleton.outputs
406 }
407
408 #[must_use]
410 pub fn fee(&self) -> &Fee {
411 &self.payload.fee
412 }
413
414 #[must_use]
417 pub fn stealth_address(&self) -> &StealthAddress {
418 &self.payload.fee.stealth_address
419 }
420
421 #[must_use]
424 pub fn sender(&self) -> &Sender {
425 &self.payload.fee.sender
426 }
427
428 #[must_use]
430 pub fn gas_limit(&self) -> u64 {
431 self.payload.fee.gas_limit
432 }
433
434 #[must_use]
436 pub fn gas_price(&self) -> u64 {
437 self.payload.fee.gas_price
438 }
439
440 #[must_use]
442 pub fn chain_id(&self) -> u8 {
443 self.payload.chain_id
444 }
445
446 #[must_use]
448 pub fn max_fee(&self) -> u64 {
449 self.payload.tx_skeleton.max_fee
450 }
451
452 #[must_use]
454 pub fn deposit(&self) -> u64 {
455 self.payload.tx_skeleton.deposit
456 }
457
458 #[must_use]
460 pub fn call(&self) -> Option<&ContractCall> {
461 #[allow(clippy::match_wildcard_for_single_variants)]
462 match self.data()? {
463 TransactionData::Call(c) => Some(c),
464 _ => None,
465 }
466 }
467
468 #[must_use]
470 pub fn deploy(&self) -> Option<&ContractDeploy> {
471 #[allow(clippy::match_wildcard_for_single_variants)]
472 match self.data()? {
473 TransactionData::Deploy(d) => Some(d),
474 _ => None,
475 }
476 }
477
478 #[must_use]
480 pub fn blob(&self) -> Option<&Vec<BlobData>> {
481 #[allow(clippy::match_wildcard_for_single_variants)]
482 match self.data()? {
483 TransactionData::Blob(d) => Some(d),
484 _ => None,
485 }
486 }
487
488 #[must_use]
490 pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
491 #[allow(clippy::match_wildcard_for_single_variants)]
492 match self.data_mut()? {
493 TransactionData::Blob(d) => Some(d),
494 _ => None,
495 }
496 }
497
498 #[must_use]
500 pub fn memo(&self) -> Option<&[u8]> {
501 match self.data()? {
502 TransactionData::Memo(memo) => Some(memo),
503 _ => None,
504 }
505 }
506
507 #[must_use]
509 pub(crate) fn data(&self) -> Option<&TransactionData> {
510 self.payload.data.as_ref()
511 }
512
513 #[must_use]
515 pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
516 self.payload.data.as_mut()
517 }
518
519 #[must_use]
523 pub fn strip_off_bytecode(&self) -> Option<Self> {
524 let deploy = self.deploy()?;
525
526 let stripped_deploy = TransactionData::Deploy(ContractDeploy {
527 owner: deploy.owner.clone(),
528 init_args: deploy.init_args.clone(),
529 bytecode: ContractBytecode {
530 hash: deploy.bytecode.hash,
531 bytes: Vec::new(),
532 },
533 nonce: deploy.nonce,
534 });
535
536 let mut stripped_transaction = self.clone();
537 stripped_transaction.payload.data = Some(stripped_deploy);
538
539 Some(stripped_transaction)
540 }
541
542 #[must_use]
548 pub fn blob_to_memo(&self) -> Option<Self> {
549 let data = self.data()?;
550
551 if let TransactionData::Blob(_) = data {
552 let hash = data.signature_message();
553 let memo = TransactionData::Memo(hash);
554 let mut converted_tx = self.clone();
555 converted_tx.payload.data = Some(memo);
556 Some(converted_tx)
557 } else {
558 None
559 }
560 }
561
562 #[must_use]
564 pub fn to_var_bytes(&self) -> Vec<u8> {
565 let mut bytes = Vec::new();
566
567 let payload_bytes = self.payload.to_var_bytes();
568 bytes.extend((payload_bytes.len() as u64).to_bytes());
569 bytes.extend(payload_bytes);
570
571 bytes.extend((self.proof.len() as u64).to_bytes());
572 bytes.extend(&self.proof);
573
574 bytes
575 }
576
577 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
582 Self::from_slice_with(buf, Payload::from_slice)
583 }
584
585 pub fn from_slice_ledger_compat(buf: &[u8]) -> Result<Self, BytesError> {
592 Self::from_slice_with(buf, Payload::from_slice_ledger_compat)
593 }
594
595 #[must_use]
600 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
601 let mut bytes = self.payload.to_hash_input_bytes();
602 bytes.extend(&self.proof);
603 bytes
604 }
605
606 #[must_use]
608 pub fn hash(&self) -> BlsScalar {
609 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
610 }
611
612 #[must_use]
630 pub fn public_inputs(&self) -> Vec<BlsScalar> {
631 let tx_skeleton = &self.payload.tx_skeleton;
632
633 let input_len = tx_skeleton.nullifiers.len();
635 let output_len = tx_skeleton.outputs.len();
636
637 let size =
638 1 + 1
640 + input_len
642 + 2 * output_len
644 + 1 + 1
646 + 2 * output_len
648 + 2 * 4 * output_len;
650 let mut pis = Vec::<BlsScalar>::with_capacity(size);
652 pis.push(self.payload.hash());
653 pis.push(tx_skeleton.root);
654 pis.extend(tx_skeleton.nullifiers().iter());
655 tx_skeleton.outputs().iter().for_each(|note| {
656 let value_commitment = note.value_commitment();
657 pis.push(value_commitment.get_u());
658 pis.push(value_commitment.get_v());
659 });
660 pis.push(tx_skeleton.max_fee().into());
661 pis.push(tx_skeleton.deposit().into());
662 tx_skeleton.outputs().iter().for_each(|note| {
663 let note_pk =
664 JubJubAffine::from(note.stealth_address().note_pk().as_ref());
665 pis.push(note_pk.get_u());
666 pis.push(note_pk.get_v());
667 });
668 tx_skeleton
669 .outputs()
670 .iter()
671 .for_each(|note| match note.sender() {
672 Sender::Encryption(sender_enc) => {
673 pis.push(sender_enc[0].0.get_u());
674 pis.push(sender_enc[0].0.get_v());
675 pis.push(sender_enc[0].1.get_u());
676 pis.push(sender_enc[0].1.get_v());
677 pis.push(sender_enc[1].0.get_u());
678 pis.push(sender_enc[1].0.get_v());
679 pis.push(sender_enc[1].1.get_u());
680 pis.push(sender_enc[1].1.get_v());
681 }
682 Sender::ContractInfo(_) => {
683 panic!("All output-notes must provide a sender-encryption")
684 }
685 });
686
687 pis
688 }
689}
690
691#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
693#[archive_attr(derive(CheckBytes))]
694pub struct Payload {
695 pub chain_id: u8,
697 pub tx_skeleton: TxSkeleton,
699 pub fee: Fee,
701 pub data: Option<TransactionData>,
703}
704
705impl PartialEq for Payload {
706 fn eq(&self, other: &Self) -> bool {
707 self.hash() == other.hash()
708 }
709}
710
711impl Eq for Payload {}
712
713impl Payload {
714 fn from_slice_with(
715 buf: &[u8],
716 parse_skeleton: fn(&[u8]) -> Result<TxSkeleton, BytesError>,
717 parse_fee: fn(&mut &[u8]) -> Result<Fee, BytesError>,
718 ) -> Result<Self, BytesError> {
719 let mut buf = buf;
720 let chain_id = u8::from_reader(&mut buf)?;
721 let tx_skeleton = parse_skeleton(read_len_prefixed(&mut buf)?)?;
722 let fee = parse_fee(&mut buf)?;
723 let data = TransactionData::from_slice(buf)?;
724
725 Ok(Self {
726 chain_id,
727 tx_skeleton,
728 fee,
729 data,
730 })
731 }
732
733 #[must_use]
735 pub fn to_var_bytes(&self) -> Vec<u8> {
736 let mut bytes = Vec::from([self.chain_id]);
737
738 let skeleton_bytes = self.tx_skeleton.to_var_bytes();
740 bytes.extend((skeleton_bytes.len() as u64).to_bytes());
741 bytes.extend(skeleton_bytes);
742
743 bytes.extend(self.fee.to_bytes());
745
746 bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
748
749 bytes
750 }
751
752 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
757 Self::from_slice_with(buf, TxSkeleton::from_slice, Fee::read_strict)
758 }
759
760 pub fn from_slice_ledger_compat(buf: &[u8]) -> Result<Self, BytesError> {
767 Self::from_slice_with(
768 buf,
769 TxSkeleton::from_slice_legacy_compat,
770 Fee::read_legacy_compat,
771 )
772 }
773
774 #[must_use]
779 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
780 let mut bytes = Vec::from([self.chain_id]);
781
782 bytes.extend(self.tx_skeleton.to_hash_input_bytes());
783
784 if let Some(data) = &self.data {
785 bytes.extend(data.signature_message());
786 }
787
788 bytes
789 }
790
791 #[must_use]
794 pub fn hash(&self) -> BlsScalar {
795 BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
796 }
797}
798
799#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)]
801#[archive_attr(derive(CheckBytes))]
802pub struct Fee {
803 pub gas_limit: u64,
805 pub gas_price: u64,
807 pub stealth_address: StealthAddress,
809 pub sender: Sender,
811}
812
813impl PartialEq for Fee {
814 fn eq(&self, other: &Self) -> bool {
815 self.sender == other.sender && self.hash() == other.hash()
816 }
817}
818
819impl Eq for Fee {}
820
821impl Fee {
822 const SIZE: usize = 2 * u64::SIZE + StealthAddress::SIZE + Sender::SIZE;
823
824 #[must_use]
826 pub fn new<R: RngCore + CryptoRng>(
827 rng: &mut R,
828 refund_pk: &PublicKey,
829 gas_limit: u64,
830 gas_price: u64,
831 ) -> Self {
832 let r = JubJubScalar::random(&mut *rng);
833
834 let sender_blinder = [
835 JubJubScalar::random(&mut *rng),
836 JubJubScalar::random(&mut *rng),
837 ];
838
839 Self::deterministic(
840 &r,
841 refund_pk,
842 gas_limit,
843 gas_price,
844 &sender_blinder,
845 )
846 }
847
848 #[must_use]
850 pub fn deterministic(
851 r: &JubJubScalar,
852 refund_pk: &PublicKey,
853 gas_limit: u64,
854 gas_price: u64,
855 sender_blinder: &[JubJubScalar; 2],
856 ) -> Self {
857 let refund_address = refund_pk.gen_stealth_address(r);
858 let sender = Sender::encrypt(
859 refund_address.note_pk(),
860 refund_pk,
861 sender_blinder,
862 );
863
864 Fee {
865 gas_limit,
866 gas_price,
867 stealth_address: refund_address,
868 sender,
869 }
870 }
871
872 #[must_use]
874 pub fn max_fee(&self) -> u64 {
875 self.gas_limit * self.gas_price
876 }
877
878 #[must_use]
880 pub fn hash(&self) -> BlsScalar {
881 let npk = self.stealth_address.note_pk().as_ref().to_hash_inputs();
882
883 let hash_inputs = [
884 BlsScalar::from(self.gas_limit),
885 BlsScalar::from(self.gas_price),
886 npk[0],
887 npk[1],
888 ];
889 Hash::digest(Domain::Other, &hash_inputs)[0]
890 }
891
892 #[must_use]
897 pub fn gen_remainder_note(
898 &self,
899 gas_consumed: u64,
900 deposit: Option<u64>,
901 ) -> Note {
902 let gas_consumed = cmp::min(gas_consumed, self.gas_limit);
907 let gas_changes = (self.gas_limit - gas_consumed) * self.gas_price;
908
909 Note::transparent_stealth(
910 self.stealth_address,
911 gas_changes + deposit.unwrap_or_default(),
912 self.sender,
913 )
914 }
915
916 fn read_strict(reader: &mut &[u8]) -> Result<Self, BytesError> {
917 Self::from_reader(reader)
918 }
919
920 fn read_legacy_compat(reader: &mut &[u8]) -> Result<Self, BytesError> {
921 let bytes = take_fixed_bytes::<{ Fee::SIZE }>(reader)?;
922 let mut reader = &bytes[..];
923 let gas_limit = u64::from_reader(&mut reader)?;
924 let gas_price = u64::from_reader(&mut reader)?;
925
926 let stealth_address =
927 StealthAddress::from_bytes_legacy_compat(take_fixed_bytes::<
928 { StealthAddress::SIZE },
929 >(
930 &mut reader
931 )?)?;
932 let sender = Sender::from_bytes_legacy_compat(take_fixed_bytes::<
933 { Sender::SIZE },
934 >(&mut reader)?)?;
935
936 Ok(Fee {
937 gas_limit,
938 gas_price,
939 stealth_address,
940 sender,
941 })
942 }
943}
944
945const SIZE: usize = Fee::SIZE;
946
947fn read_len_prefixed<'a>(buf: &mut &'a [u8]) -> Result<&'a [u8], BytesError> {
948 let len = usize::try_from(u64::from_reader(buf)?)
949 .map_err(|_| BytesError::InvalidData)?;
950
951 if buf.len() < len {
952 return Err(BytesError::InvalidData);
953 }
954
955 let (bytes, rest) = buf.split_at(len);
956 *buf = rest;
957 Ok(bytes)
958}
959
960fn take_fixed_bytes<'a, const N: usize>(
961 buf: &mut &'a [u8],
962) -> Result<&'a [u8; N], BytesError> {
963 if buf.len() < N {
964 return Err(BytesError::InvalidData);
965 }
966
967 let (bytes, rest) = buf.split_at(N);
968 *buf = rest;
969 bytes.try_into().map_err(|_| BytesError::InvalidData)
970}
971
972impl Serializable<SIZE> for Fee {
973 type Error = BytesError;
974
975 fn to_bytes(&self) -> [u8; Self::SIZE] {
977 let mut buf = [0u8; Self::SIZE];
978
979 buf[..u64::SIZE].copy_from_slice(&self.gas_limit.to_bytes());
980 let mut start = u64::SIZE;
981 buf[start..start + u64::SIZE]
982 .copy_from_slice(&self.gas_price.to_bytes());
983 start += u64::SIZE;
984 buf[start..start + StealthAddress::SIZE]
985 .copy_from_slice(&self.stealth_address.to_bytes());
986 start += StealthAddress::SIZE;
987 buf[start..start + Sender::SIZE]
988 .copy_from_slice(&self.sender.to_bytes());
989
990 buf
991 }
992
993 fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
996 let mut reader = &bytes[..];
997
998 let gas_limit = u64::from_reader(&mut reader)?;
999 let gas_price = u64::from_reader(&mut reader)?;
1000 let refund_address = StealthAddress::from_reader(&mut reader)?;
1001 let sender = Sender::from_reader(&mut reader)?;
1002
1003 Ok(Fee {
1004 gas_limit,
1005 gas_price,
1006 stealth_address: refund_address,
1007 sender,
1008 })
1009 }
1010}
1011
1012#[derive(Debug, Clone, PartialEq)]
1015pub struct TxCircuitVec {
1016 pub input_notes_info: Vec<InputNoteInfo<NOTES_TREE_DEPTH>>,
1018 pub output_notes_info: [OutputNoteInfo; OUTPUT_NOTES],
1020 pub payload_hash: BlsScalar,
1022 pub root: BlsScalar,
1024 pub deposit: u64,
1026 pub max_fee: u64,
1028 pub sender_pk: PublicKey,
1030 pub signatures: (SchnorrSignature, SchnorrSignature),
1032}
1033
1034impl TxCircuitVec {
1035 #[must_use]
1037 pub fn to_var_bytes(&self) -> Vec<u8> {
1038 let input_len = self.input_notes_info.len();
1039
1040 let mut bytes = Vec::with_capacity(Self::size(input_len));
1041
1042 bytes.extend((input_len as u64).to_bytes());
1044
1045 for info in &self.input_notes_info {
1047 bytes.extend(info.to_var_bytes());
1048 }
1049 for info in &self.output_notes_info {
1050 bytes.extend(info.to_bytes());
1051 }
1052 bytes.extend(self.payload_hash.to_bytes());
1053 bytes.extend(self.root.to_bytes());
1054 bytes.extend(self.deposit.to_bytes());
1055 bytes.extend(self.max_fee.to_bytes());
1056 bytes.extend(self.sender_pk.to_bytes());
1057 bytes.extend(self.signatures.0.to_bytes());
1058 bytes.extend(self.signatures.1.to_bytes());
1059
1060 bytes
1061 }
1062
1063 pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
1069 let input_len = u64::from_slice(bytes)?;
1070
1071 #[allow(clippy::cast_possible_truncation)]
1073 if bytes.len() < Self::size(input_len as usize) {
1074 return Err(BytesError::BadLength {
1075 found: bytes.len(),
1076 expected: Self::size(input_len as usize),
1077 });
1078 }
1079
1080 let bytes = &bytes[u64::SIZE..];
1081 let circuit: TxCircuitVec = match input_len {
1082 1 => TxCircuit::<NOTES_TREE_DEPTH, 1>::from_slice(bytes)?.into(),
1083 2 => TxCircuit::<NOTES_TREE_DEPTH, 2>::from_slice(bytes)?.into(),
1084 3 => TxCircuit::<NOTES_TREE_DEPTH, 3>::from_slice(bytes)?.into(),
1085 4 => TxCircuit::<NOTES_TREE_DEPTH, 4>::from_slice(bytes)?.into(),
1086 _ => return Err(BytesError::InvalidData),
1087 };
1088
1089 Ok(circuit)
1090 }
1091
1092 const fn size(input_len: usize) -> usize {
1093 u64::SIZE
1094 + input_len * InputNoteInfo::<NOTES_TREE_DEPTH>::SIZE
1095 + OUTPUT_NOTES * OutputNoteInfo::SIZE
1096 + 2 * BlsScalar::SIZE
1097 + 2 * u64::SIZE
1098 + PublicKey::SIZE
1099 + 2 * SchnorrSignature::SIZE
1100 }
1101}
1102
1103impl<const I: usize> From<TxCircuit<NOTES_TREE_DEPTH, I>> for TxCircuitVec {
1104 fn from(circuit: TxCircuit<NOTES_TREE_DEPTH, I>) -> Self {
1105 TxCircuitVec {
1106 input_notes_info: circuit.input_notes_info.to_vec(),
1107 output_notes_info: circuit.output_notes_info,
1108 payload_hash: circuit.payload_hash,
1109 root: circuit.root,
1110 deposit: circuit.deposit,
1111 max_fee: circuit.max_fee,
1112 sender_pk: circuit.sender_pk,
1113 signatures: circuit.signatures,
1114 }
1115 }
1116}
1117
1118pub trait Prove {
1121 fn prove(&self, tx_circuit_vec_bytes: &[u8]) -> Result<Vec<u8>, Error>;
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135 use super::*;
1136
1137 const HISTORICAL_R: [u8; 32] = [
1138 0x93, 0x98, 0xf3, 0x42, 0x62, 0x20, 0x2d, 0x7f, 0x24, 0x57, 0x74, 0xef,
1139 0xd6, 0x51, 0xa5, 0x03, 0xcb, 0x7a, 0x4f, 0x6d, 0x7c, 0xcf, 0x23, 0x40,
1140 0x4d, 0x17, 0xc2, 0xbf, 0xf3, 0x0e, 0x68, 0xa9,
1141 ];
1142
1143 const HISTORICAL_NOTE_PK: [u8; 32] = [
1144 0x0a, 0x8e, 0x2e, 0x57, 0xa2, 0x2d, 0x40, 0xb5, 0x51, 0x22, 0x38, 0x9f,
1145 0x32, 0x41, 0x7f, 0x20, 0xf8, 0x4d, 0xd7, 0x0b, 0x33, 0x88, 0x60, 0x42,
1146 0xe4, 0x44, 0xc4, 0x5f, 0x5d, 0x50, 0x4b, 0x8d,
1147 ];
1148
1149 fn historical_note_bytes() -> [u8; Note::SIZE] {
1150 let mut bytes = [0u8; Note::SIZE];
1151 bytes[0] = 0; let mut offset = 1 + JubJubAffine::SIZE;
1154 bytes[offset..offset + 32].copy_from_slice(&HISTORICAL_R);
1155 bytes[offset + 32..offset + 64].copy_from_slice(&HISTORICAL_NOTE_PK);
1156 offset += StealthAddress::SIZE + u64::SIZE + NOTE_VAL_ENC_SIZE;
1157
1158 bytes[offset] = 1; bytes
1160 }
1161
1162 fn historical_protocol_tx_bytes() -> Vec<u8> {
1163 let mut skeleton = Vec::new();
1164 skeleton.extend(BlsScalar::from(0u64).to_bytes());
1165 skeleton.extend(0u64.to_bytes());
1166 skeleton.extend(historical_note_bytes());
1167 skeleton.extend(Note::empty().to_bytes());
1168 skeleton.extend(0u64.to_bytes());
1169 skeleton.extend(0u64.to_bytes());
1170
1171 let mut fee = [0u8; Fee::SIZE];
1172 fee[2 * u64::SIZE..2 * u64::SIZE + StealthAddress::SIZE]
1173 .copy_from_slice(&StealthAddress::default().to_bytes());
1174 fee[2 * u64::SIZE + StealthAddress::SIZE] = 1;
1175
1176 let mut payload = Vec::new();
1177 payload.push(0);
1178 payload.extend((skeleton.len() as u64).to_bytes());
1179 payload.extend(skeleton);
1180 payload.extend(fee);
1181 payload.extend(TransactionData::option_to_var_bytes(None));
1182
1183 let mut tx = Vec::new();
1184 tx.push(0);
1185 tx.extend((payload.len() as u64).to_bytes());
1186 tx.extend(payload);
1187 tx.extend(0u64.to_bytes());
1188 tx
1189 }
1190
1191 #[test]
1192 fn tx_skeleton_from_slice_rejects_unbounded_nullifier_count_without_panicking()
1193 {
1194 let mut bytes = Vec::new();
1195 bytes.extend(BlsScalar::from(0u64).to_bytes());
1196 bytes.extend(u64::MAX.to_bytes());
1197
1198 let min_tail_len = OUTPUT_NOTES * Note::SIZE + u64::SIZE + u64::SIZE;
1201 bytes.resize(BlsScalar::SIZE + u64::SIZE + min_tail_len, 0u8);
1202
1203 let err = TxSkeleton::from_slice(&bytes).unwrap_err();
1204 assert_eq!(err, BytesError::InvalidData);
1205 }
1206
1207 #[test]
1208 fn ledger_compat_accepts_historical_non_subgroup_note_pk() {
1209 let tx = historical_protocol_tx_bytes();
1210
1211 assert!(
1212 crate::transfer::Transaction::decode_with_format(
1213 crate::transfer::TransactionFormat::Aegis,
1214 &tx,
1215 )
1216 .is_err()
1217 );
1218 assert!(
1219 crate::transfer::Transaction::decode_with_format(
1220 crate::transfer::TransactionFormat::PreAegis,
1221 &tx,
1222 )
1223 .is_ok()
1224 );
1225 }
1226}