Skip to main content

dusk_core/transfer/
phoenix.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! Types related to the phoenix transaction model of Dusk's transfer contract.
8
9use 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
29// phoenix types
30pub 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
36/// The depth of the merkle tree of notes stored in the transfer-contract.
37pub const NOTES_TREE_DEPTH: usize = 17;
38/// The arity of the merkle tree of notes stored in the transfer-contract.
39pub use poseidon_merkle::ARITY as NOTES_TREE_ARITY;
40/// The merkle tree of notes stored in the transfer-contract.
41pub type NotesTree = poseidon_merkle::Tree<(), NOTES_TREE_DEPTH>;
42/// The merkle opening for a note-hash in the merkle tree of notes.
43pub type NoteOpening = poseidon_merkle::Opening<(), NOTES_TREE_DEPTH>;
44/// the tree item for the merkle-tree of notes stored in the transfer-contract.
45pub type NoteTreeItem = poseidon_merkle::Item<()>;
46
47/// A leaf of the merkle tree of notes stored in the transfer-contract.
48#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
49#[archive_attr(derive(CheckBytes))]
50pub struct NoteLeaf {
51    /// The height of the block when the note was inserted in the tree.
52    pub block_height: u64,
53    /// The note inserted in the tree.
54    pub note: Note,
55}
56
57impl AsRef<Note> for NoteLeaf {
58    fn as_ref(&self) -> &Note {
59        &self.note
60    }
61}
62
63/// Ord compares positions, not values, note values need to be decrypted first
64impl 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
76/// Label used for the ZK transcript initialization. Must be the same for prover
77/// and verifier.
78pub const TRANSCRIPT_LABEL: &[u8] = b"dusk-network";
79
80/// Phoenix transaction.
81#[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    /// Create a new phoenix transaction given the sender secret-key, receiver
98    /// public-key, the input note positions in the transaction tree and the
99    /// new output-notes.
100    ///
101    /// # Errors
102    /// The creation of a transaction is not possible and will error if:
103    /// - one of the input-notes doesn't belong to the `sender_sk`
104    /// - the transaction input doesn't cover the transaction costs
105    /// - the `inputs` vector is either empty or larger than 4 elements
106    /// - the `inputs` vector contains duplicate `Note`s
107    /// - the `prover` is implemented incorrectly
108    /// - the memo, if given, is too large
109    #[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        // get input note values, value-blinders and nullifiers
140        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        // --- Create the transaction payload
158
159        // Compute max_fee directly so the change note can be created
160        // before the Fee. This lets us derive Fee.stealth_address from
161        // the ZK-proven change note, binding the refund destination to
162        // the proof and preventing stealth-address redirection.
163        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        // Generate output notes:
170        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        // The change note should have the value of the input note, minus what
202        // is maximally spent.
203        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        // Derive Fee.stealth_address from the change note so the gas
215        // refund destination is bound to the ZK-proven output.
216        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        // Now we can set the tx-skeleton, payload and get the payload-hash
235        let tx_skeleton = TxSkeleton {
236            root,
237            // we also need the nullifiers for the tx-circuit, hence the clone
238            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        // --- Create the transaction proof
252
253        // Create a vector with all the information for the input-notes
254        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        // Create the information for the output-notes
283        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        // Sign the payload hash using both 'a' and 'b' of the sender_sk
319        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    /// Creates a new phoenix transaction given the [`Payload`] and proof. Note
343    /// that this function doesn't guarantee that the proof matches the
344    /// payload, if possible use [`Self::new`] instead.
345    #[must_use]
346    pub fn from_payload_and_proof(payload: Payload, proof: Vec<u8>) -> Self {
347        Self { payload, proof }
348    }
349
350    /// Replaces the inner `proof` bytes for a given `proof`.
351    ///
352    /// This can be used to delegate the proof generation after a
353    /// [`Transaction`] is created.
354    /// In order to do that, the transaction would be created using the
355    /// serialized circuit-bytes for the proof-field. Those bytes can be
356    /// sent to a 3rd-party prover-service that generates the proof-bytes
357    /// and sends them back. The proof-bytes will then replace the
358    /// circuit-bytes in the transaction using this function.
359    pub fn set_proof(&mut self, proof: Vec<u8>) {
360        self.proof = proof;
361    }
362
363    /// The proof of the transaction.
364    #[must_use]
365    pub fn proof(&self) -> &[u8] {
366        &self.proof
367    }
368
369    /// The payload-hash of the transaction used as input in the
370    /// phoenix-circuit.
371    #[must_use]
372    pub fn payload_hash(&self) -> BlsScalar {
373        self.payload.hash()
374    }
375
376    /// Returns the nullifiers of the transaction.
377    #[must_use]
378    pub fn nullifiers(&self) -> &[BlsScalar] {
379        &self.payload.tx_skeleton.nullifiers
380    }
381
382    /// Return the root of the notes tree.
383    #[must_use]
384    pub fn root(&self) -> &BlsScalar {
385        &self.payload.tx_skeleton.root
386    }
387
388    /// Return the output notes of the transaction.
389    #[must_use]
390    pub fn outputs(&self) -> &[Note; OUTPUT_NOTES] {
391        &self.payload.tx_skeleton.outputs
392    }
393
394    /// Return the fee for the transaction.
395    #[must_use]
396    pub fn fee(&self) -> &Fee {
397        &self.payload.fee
398    }
399
400    /// Return the stealth address for returning funds for Phoenix transactions
401    /// as specified in the fee.
402    #[must_use]
403    pub fn stealth_address(&self) -> &StealthAddress {
404        &self.payload.fee.stealth_address
405    }
406
407    /// Returns the sender data for Phoenix transactions as specified in the
408    /// fee.
409    #[must_use]
410    pub fn sender(&self) -> &Sender {
411        &self.payload.fee.sender
412    }
413
414    /// Returns the gas limit of the transaction.
415    #[must_use]
416    pub fn gas_limit(&self) -> u64 {
417        self.payload.fee.gas_limit
418    }
419
420    /// Returns the gas price of the transaction.
421    #[must_use]
422    pub fn gas_price(&self) -> u64 {
423        self.payload.fee.gas_price
424    }
425
426    /// Returns the chain ID of the transaction.
427    #[must_use]
428    pub fn chain_id(&self) -> u8 {
429        self.payload.chain_id
430    }
431
432    /// Returns the max fee to be spend by the transaction.
433    #[must_use]
434    pub fn max_fee(&self) -> u64 {
435        self.payload.tx_skeleton.max_fee
436    }
437
438    /// Returns the deposit of the transaction.
439    #[must_use]
440    pub fn deposit(&self) -> u64 {
441        self.payload.tx_skeleton.deposit
442    }
443
444    /// Return the contract call data, if there is any.
445    #[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    /// Return the contract deploy data, if there is any.
455    #[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    /// Return the blob data, if there is any.
465    #[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    /// Return the mutable blob data, if there is any.
475    #[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    /// Returns the memo used with the transaction, if any.
485    #[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    /// Returns the transaction data, if it exists.
494    #[must_use]
495    pub(crate) fn data(&self) -> Option<&TransactionData> {
496        self.payload.data.as_ref()
497    }
498
499    /// Returns the transaction data, if it exists.
500    #[must_use]
501    pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
502        self.payload.data.as_mut()
503    }
504
505    /// Creates a modified clone of this transaction if it contains data for
506    /// deployment, clones all fields except for the bytecode' 'bytes' part.
507    /// Returns none if the transaction is not a deployment transaction.
508    #[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    /// Creates a modified clone of this transaction if it contains a Blob,
529    /// clones all fields except for the Blob, whose versioned hashes are set as
530    /// Memo.
531    ///
532    /// Returns none if the transaction is not a Blob transaction.
533    #[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    /// Serialize the `Transaction` into a variable length byte buffer.
549    #[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    /// Deserialize the Transaction from a bytes buffer.
564    ///
565    /// # Errors
566    /// Errors when the bytes are not canonical.
567    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    /// Return input bytes to hash the Transaction.
592    ///
593    /// Note: The result of this function is *only* meant to be used as an input
594    /// for hashing and *cannot* be used to deserialize the `Transaction` again.
595    #[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    /// Create the `Transaction`-hash.
603    #[must_use]
604    pub fn hash(&self) -> BlsScalar {
605        BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
606    }
607
608    /// Return the public input to be used in the phoenix-transaction circuit
609    /// verification
610    ///
611    /// These are:
612    /// - `payload_hash`
613    /// - `root`
614    /// - `[nullifier; I]`
615    /// - `[output_value_commitment; 2]`
616    /// - `max_fee`
617    /// - `deposit`
618    /// - `(npk_out_0, npk_out_1)`
619    /// - `(enc_A_npk_out_0, enc_B_npk_out_0)`
620    /// - `(enc_A_npk_out_1, enc_B_npk_out_1)`
621    ///
622    /// # Panics
623    /// Panics if one of the output-notes doesn't have the sender set to
624    /// [`Sender::Encryption`].
625    #[must_use]
626    pub fn public_inputs(&self) -> Vec<BlsScalar> {
627        let tx_skeleton = &self.payload.tx_skeleton;
628
629        // retrieve the number of input and output notes
630        let input_len = tx_skeleton.nullifiers.len();
631        let output_len = tx_skeleton.outputs.len();
632
633        let size =
634            // payload-hash and root
635            1 + 1
636            // nullifiers
637            + input_len
638            // output-notes value-commitment
639            + 2 * output_len
640            // max-fee and deposit
641            + 1 + 1
642            // output-notes public-keys
643            + 2 * output_len
644            // sender-encryption for both output-notes
645            + 2 * 4 * output_len;
646        // build the public input vector
647        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/// The transaction payload
688#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
689#[archive_attr(derive(CheckBytes))]
690pub struct Payload {
691    /// ID of the chain for this transaction to execute on.
692    pub chain_id: u8,
693    /// Transaction skeleton used for the phoenix transaction.
694    pub tx_skeleton: TxSkeleton,
695    /// Data used to calculate the transaction fee and refund unspent gas.
696    pub fee: Fee,
697    /// Data to do a contract call, deployment, or insert a memo.
698    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    /// Serialize the `Payload` into a variable length byte buffer.
711    #[must_use]
712    pub fn to_var_bytes(&self) -> Vec<u8> {
713        let mut bytes = Vec::from([self.chain_id]);
714
715        // serialize the tx-skeleton
716        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        // serialize the fee
721        bytes.extend(self.fee.to_bytes());
722
723        // serialize the transaction data, if present.
724        bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
725
726        bytes
727    }
728
729    /// Deserialize the Payload from a bytes buffer.
730    ///
731    /// # Errors
732    /// Errors when the bytes are not canonical.
733    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        // deserialize the tx-skeleton
739        #[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        // deserialize fee
746        let fee = Fee::from_reader(&mut buf)?;
747
748        // deserialize optional transaction data
749        let data = TransactionData::from_slice(buf)?;
750
751        Ok(Self {
752            chain_id,
753            tx_skeleton,
754            fee,
755            data,
756        })
757    }
758
759    /// Return input bytes to hash the payload.
760    ///
761    /// Note: The result of this function is *only* meant to be used as an input
762    /// for hashing and *cannot* be used to deserialize the `Payload` again.
763    #[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    /// Create the `Payload`-hash to be used as an input to the
777    /// phoenix-transaction circuit.
778    #[must_use]
779    pub fn hash(&self) -> BlsScalar {
780        BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
781    }
782}
783
784/// The Fee structure
785#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)]
786#[archive_attr(derive(CheckBytes))]
787pub struct Fee {
788    /// Gas limit set for a phoenix transaction
789    pub gas_limit: u64,
790    /// Gas price set for a phoenix transaction
791    pub gas_price: u64,
792    /// Address to send the remainder note
793    pub stealth_address: StealthAddress,
794    /// Sender to use for the remainder
795    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    /// Create a new Fee with inner randomness
808    #[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    /// Create a new Fee without inner randomness
832    #[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    /// Calculate the max-fee.
856    #[must_use]
857    pub fn max_fee(&self) -> u64 {
858        self.gas_limit * self.gas_price
859    }
860
861    /// Return a hash represented by `H(gas_limit, gas_price, H([note_pk]))`
862    #[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    /// Generates a remainder from the fee and the given gas consumed.
876    ///
877    /// If there is a deposit, it means that the deposit hasn't been picked up
878    /// by the contract. In this case, it is added to the remainder note.
879    #[must_use]
880    pub fn gen_remainder_note(
881        &self,
882        gas_consumed: u64,
883        deposit: Option<u64>,
884    ) -> Note {
885        // Consuming more gas than the limit provided should never occur, and
886        // it's not the responsibility of the `Fee` to check that.
887        // Here defensively ensure it's not panicking, capping the gas consumed
888        // to the gas limit.
889        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    /// Converts a Fee into it's byte representation
906    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    /// Attempts to convert a byte representation of a fee into a `Fee`,
924    /// failing if the input is invalid
925    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/// This struct mimics the [`TxCircuit`] but is not generic over the amount of
943/// input-notes.
944#[derive(Debug, Clone, PartialEq)]
945pub struct TxCircuitVec {
946    /// All information needed in relation to the transaction input-notes
947    pub input_notes_info: Vec<InputNoteInfo<NOTES_TREE_DEPTH>>,
948    /// All information needed in relation to the transaction output-notes
949    pub output_notes_info: [OutputNoteInfo; OUTPUT_NOTES],
950    /// The hash of the transaction-payload
951    pub payload_hash: BlsScalar,
952    /// The root of the tree of notes corresponding to the input-note openings
953    pub root: BlsScalar,
954    /// The deposit of the transaction, is zero if there is no deposit
955    pub deposit: u64,
956    /// The maximum fee that the transaction may spend
957    pub max_fee: u64,
958    /// The public key of the sender used for the sender-encryption
959    pub sender_pk: PublicKey,
960    /// The signature of the payload-hash signed with sk.a and sk.b
961    pub signatures: (SchnorrSignature, SchnorrSignature),
962}
963
964impl TxCircuitVec {
965    /// Serialize a [`TxCircuitVec`] into a vector of bytes.
966    #[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        // first serialize the amount of input-notes
973        bytes.extend((input_len as u64).to_bytes());
974
975        // then serialize the other fields
976        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    /// Deserialize a [`TxCircuitVec`] from a slice of bytes.
994    ///
995    /// # Errors
996    ///
997    /// Will return [`dusk_bytes::Error`] in case of a deserialization error.
998    pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
999        let input_len = u64::from_slice(bytes)?;
1000
1001        // the input-len is smaller than a u32::MAX
1002        #[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
1048/// This trait can be used to implement different methods to generate a proof
1049/// from the circuit-bytes.
1050pub trait Prove {
1051    /// Generate a transaction proof from all the information needed to create a
1052    /// tx-circuit.
1053    ///
1054    /// # Errors
1055    /// This function errors in case of an incorrect circuit or of an
1056    /// unobtainable prover-key.
1057    //
1058    // Note that the reference to `self` is needed to plug in a running client
1059    // when delegating the proof generation.
1060    fn prove(&self, tx_circuit_vec_bytes: &[u8]) -> Result<Vec<u8>, Error>;
1061}