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, TransactionData,
25    MAX_MEMO_SIZE,
26};
27use crate::{BlsScalar, Error, JubJubAffine, JubJubScalar};
28
29// phoenix types
30pub 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
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        // Set the fee.
160        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        // Generate output notes:
168        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        // The change note should have the value of the input note, minus what
200        // is maximally spent.
201        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        // Now we can set the tx-skeleton, payload and get the payload-hash
214        let tx_skeleton = TxSkeleton {
215            root,
216            // we also need the nullifiers for the tx-circuit, hence the clone
217            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        // --- Create the transaction proof
231
232        // Create a vector with all the information for the input-notes
233        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        // Create the information for the output-notes
262        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        // Sign the payload hash using both 'a' and 'b' of the sender_sk
298        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    /// Creates a new phoenix transaction given the [`Payload`] and proof. Note
322    /// that this function doesn't guarantee that the proof matches the
323    /// payload, if possible use [`Self::new`] instead.
324    #[must_use]
325    pub fn from_payload_and_proof(payload: Payload, proof: Vec<u8>) -> Self {
326        Self { payload, proof }
327    }
328
329    /// Replaces the inner `proof` bytes for a given `proof`.
330    ///
331    /// This can be used to delegate the proof generation after a
332    /// [`Transaction`] is created.
333    /// In order to do that, the transaction would be created using the
334    /// serialized circuit-bytes for the proof-field. Those bytes can be
335    /// sent to a 3rd-party prover-service that generates the proof-bytes
336    /// and sends them back. The proof-bytes will then replace the
337    /// circuit-bytes in the transaction using this function.
338    pub fn set_proof(&mut self, proof: Vec<u8>) {
339        self.proof = proof;
340    }
341
342    /// The proof of the transaction.
343    #[must_use]
344    pub fn proof(&self) -> &[u8] {
345        &self.proof
346    }
347
348    /// The payload-hash of the transaction used as input in the
349    /// phoenix-circuit.
350    #[must_use]
351    pub fn payload_hash(&self) -> BlsScalar {
352        self.payload.hash()
353    }
354
355    /// Returns the nullifiers of the transaction.
356    #[must_use]
357    pub fn nullifiers(&self) -> &[BlsScalar] {
358        &self.payload.tx_skeleton.nullifiers
359    }
360
361    /// Return the root of the notes tree.
362    #[must_use]
363    pub fn root(&self) -> &BlsScalar {
364        &self.payload.tx_skeleton.root
365    }
366
367    /// Return the output notes of the transaction.
368    #[must_use]
369    pub fn outputs(&self) -> &[Note; OUTPUT_NOTES] {
370        &self.payload.tx_skeleton.outputs
371    }
372
373    /// Return the fee for the transaction.
374    #[must_use]
375    pub fn fee(&self) -> &Fee {
376        &self.payload.fee
377    }
378
379    /// Return the stealth address for returning funds for Phoenix transactions
380    /// as specified in the fee.
381    #[must_use]
382    pub fn stealth_address(&self) -> &StealthAddress {
383        &self.payload.fee.stealth_address
384    }
385
386    /// Returns the sender data for Phoenix transactions as specified in the
387    /// fee.
388    #[must_use]
389    pub fn sender(&self) -> &Sender {
390        &self.payload.fee.sender
391    }
392
393    /// Returns the gas limit of the transaction.
394    #[must_use]
395    pub fn gas_limit(&self) -> u64 {
396        self.payload.fee.gas_limit
397    }
398
399    /// Returns the gas price of the transaction.
400    #[must_use]
401    pub fn gas_price(&self) -> u64 {
402        self.payload.fee.gas_price
403    }
404
405    /// Returns the chain ID of the transaction.
406    #[must_use]
407    pub fn chain_id(&self) -> u8 {
408        self.payload.chain_id
409    }
410
411    /// Returns the max fee to be spend by the transaction.
412    #[must_use]
413    pub fn max_fee(&self) -> u64 {
414        self.payload.tx_skeleton.max_fee
415    }
416
417    /// Returns the deposit of the transaction.
418    #[must_use]
419    pub fn deposit(&self) -> u64 {
420        self.payload.tx_skeleton.deposit
421    }
422
423    /// Return the contract call data, if there is any.
424    #[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    /// Return the contract deploy data, if there is any.
434    #[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    /// Return the blob data, if there is any.
444    #[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    /// Return the mutable blob data, if there is any.
454    #[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    /// Returns the memo used with the transaction, if any.
464    #[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    /// Returns the transaction data, if it exists.
473    #[must_use]
474    pub(crate) fn data(&self) -> Option<&TransactionData> {
475        self.payload.data.as_ref()
476    }
477
478    /// Returns the transaction data, if it exists.
479    #[must_use]
480    pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
481        self.payload.data.as_mut()
482    }
483
484    /// Creates a modified clone of this transaction if it contains data for
485    /// deployment, clones all fields except for the bytecode' 'bytes' part.
486    /// Returns none if the transaction is not a deployment transaction.
487    #[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    /// Creates a modified clone of this transaction if it contains a Blob,
508    /// clones all fields except for the Blob, whose versioned hashes are set as
509    /// Memo.
510    ///
511    /// Returns none if the transaction is not a Blob transaction.
512    #[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    /// Serialize the `Transaction` into a variable length byte buffer.
528    #[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    /// Deserialize the Transaction from a bytes buffer.
543    ///
544    /// # Errors
545    /// Errors when the bytes are not canonical.
546    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    /// Return input bytes to hash the Transaction.
568    ///
569    /// Note: The result of this function is *only* meant to be used as an input
570    /// for hashing and *cannot* be used to deserialize the `Transaction` again.
571    #[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    /// Create the `Transaction`-hash.
579    #[must_use]
580    pub fn hash(&self) -> BlsScalar {
581        BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
582    }
583
584    /// Return the public input to be used in the phoenix-transaction circuit
585    /// verification
586    ///
587    /// These are:
588    /// - `payload_hash`
589    /// - `root`
590    /// - `[nullifier; I]`
591    /// - `[output_value_commitment; 2]`
592    /// - `max_fee`
593    /// - `deposit`
594    /// - `(npk_out_0, npk_out_1)`
595    /// - `(enc_A_npk_out_0, enc_B_npk_out_0)`
596    /// - `(enc_A_npk_out_1, enc_B_npk_out_1)`
597    ///
598    /// # Panics
599    /// Panics if one of the output-notes doesn't have the sender set to
600    /// [`Sender::Encryption`].
601    #[must_use]
602    pub fn public_inputs(&self) -> Vec<BlsScalar> {
603        let tx_skeleton = &self.payload.tx_skeleton;
604
605        // retrieve the number of input and output notes
606        let input_len = tx_skeleton.nullifiers.len();
607        let output_len = tx_skeleton.outputs.len();
608
609        let size =
610            // payload-hash and root
611            1 + 1
612            // nullifiers
613            + input_len
614            // output-notes value-commitment
615            + 2 * output_len
616            // max-fee and deposit
617            + 1 + 1
618            // output-notes public-keys
619            + 2 * output_len
620            // sender-encryption for both output-notes
621            + 2 * 4 * output_len;
622        // build the public input vector
623        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/// The transaction payload
663#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
664#[archive_attr(derive(CheckBytes))]
665pub struct Payload {
666    /// ID of the chain for this transaction to execute on.
667    pub chain_id: u8,
668    /// Transaction skeleton used for the phoenix transaction.
669    pub tx_skeleton: TxSkeleton,
670    /// Data used to calculate the transaction fee and refund unspent gas.
671    pub fee: Fee,
672    /// Data to do a contract call, deployment, or insert a memo.
673    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    /// Serialize the `Payload` into a variable length byte buffer.
686    #[must_use]
687    pub fn to_var_bytes(&self) -> Vec<u8> {
688        let mut bytes = Vec::from([self.chain_id]);
689
690        // serialize the tx-skeleton
691        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        // serialize the fee
696        bytes.extend(self.fee.to_bytes());
697
698        // serialize the transaction data, if present.
699        bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
700
701        bytes
702    }
703
704    /// Deserialize the Payload from a bytes buffer.
705    ///
706    /// # Errors
707    /// Errors when the bytes are not canonical.
708    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        // deserialize the tx-skeleton
714        #[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        // deserialize fee
721        let fee = Fee::from_reader(&mut buf)?;
722
723        // deserialize optional transaction data
724        let data = TransactionData::from_slice(buf)?;
725
726        Ok(Self {
727            chain_id,
728            tx_skeleton,
729            fee,
730            data,
731        })
732    }
733
734    /// Return input bytes to hash the payload.
735    ///
736    /// Note: The result of this function is *only* meant to be used as an input
737    /// for hashing and *cannot* be used to deserialize the `Payload` again.
738    #[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    /// Create the `Payload`-hash to be used as an input to the
752    /// phoenix-transaction circuit.
753    #[must_use]
754    pub fn hash(&self) -> BlsScalar {
755        BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
756    }
757}
758
759/// The Fee structure
760#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)]
761#[archive_attr(derive(CheckBytes))]
762pub struct Fee {
763    /// Gas limit set for a phoenix transaction
764    pub gas_limit: u64,
765    /// Gas price set for a phoenix transaction
766    pub gas_price: u64,
767    /// Address to send the remainder note
768    pub stealth_address: StealthAddress,
769    /// Sender to use for the remainder
770    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    /// Create a new Fee with inner randomness
783    #[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    /// Create a new Fee without inner randomness
807    #[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    /// Calculate the max-fee.
831    #[must_use]
832    pub fn max_fee(&self) -> u64 {
833        self.gas_limit * self.gas_price
834    }
835
836    /// Return a hash represented by `H(gas_limit, gas_price, H([note_pk]))`
837    #[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    /// Generates a remainder from the fee and the given gas consumed.
851    ///
852    /// If there is a deposit, it means that the deposit hasn't been picked up
853    /// by the contract. In this case, it is added to the remainder note.
854    #[must_use]
855    pub fn gen_remainder_note(
856        &self,
857        gas_consumed: u64,
858        deposit: Option<u64>,
859    ) -> Note {
860        // Consuming more gas than the limit provided should never occur, and
861        // it's not the responsibility of the `Fee` to check that.
862        // Here defensively ensure it's not panicking, capping the gas consumed
863        // to the gas limit.
864        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    /// Converts a Fee into it's byte representation
881    #[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    /// Attempts to convert a byte representation of a fee into a `Fee`,
900    /// failing if the input is invalid
901    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/// This struct mimics the [`TxCircuit`] but is not generic over the amount of
919/// input-notes.
920#[derive(Debug, Clone, PartialEq)]
921pub struct TxCircuitVec {
922    /// All information needed in relation to the transaction input-notes
923    pub input_notes_info: Vec<InputNoteInfo<NOTES_TREE_DEPTH>>,
924    /// All information needed in relation to the transaction output-notes
925    pub output_notes_info: [OutputNoteInfo; OUTPUT_NOTES],
926    /// The hash of the transaction-payload
927    pub payload_hash: BlsScalar,
928    /// The root of the tree of notes corresponding to the input-note openings
929    pub root: BlsScalar,
930    /// The deposit of the transaction, is zero if there is no deposit
931    pub deposit: u64,
932    /// The maximum fee that the transaction may spend
933    pub max_fee: u64,
934    /// The public key of the sender used for the sender-encryption
935    pub sender_pk: PublicKey,
936    /// The signature of the payload-hash signed with sk.a and sk.b
937    pub signatures: (SchnorrSignature, SchnorrSignature),
938}
939
940impl TxCircuitVec {
941    /// Serialize a [`TxCircuitVec`] into a vector of bytes.
942    #[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        // first serialize the amount of input-notes
949        bytes.extend((input_len as u64).to_bytes());
950
951        // then serialize the other fields
952        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    /// Deserialize a [`TxCircuitVec`] from a slice of bytes.
970    ///
971    /// # Errors
972    ///
973    /// Will return [`dusk_bytes::Error`] in case of a deserialization error.
974    pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
975        let input_len = u64::from_slice(bytes)?;
976
977        // the input-len is smaller than a u32::MAX
978        #[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
1024/// This trait can be used to implement different methods to generate a proof
1025/// from the circuit-bytes.
1026pub trait Prove {
1027    /// Generate a transaction proof from all the information needed to create a
1028    /// tx-circuit.
1029    ///
1030    /// # Errors
1031    /// This function errors in case of an incorrect circuit or of an
1032    /// unobtainable prover-key.
1033    //
1034    // Note that the reference to `self` is needed to plug in a running client
1035    // when delegating the proof generation.
1036    fn prove(&self, tx_circuit_vec_bytes: &[u8]) -> Result<Vec<u8>, Error>;
1037}