dusk_core/transfer/
data.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//! Extra data that may be sent with the `data` field of either transaction
8//! type.
9
10#[cfg(feature = "kzg")]
11mod kzg_blob;
12
13#[cfg(feature = "serde")]
14use serde_with::{hex::Hex, serde_as};
15
16use alloc::string::String;
17use alloc::vec::Vec;
18use alloc::{format, vec};
19
20use bytecheck::CheckBytes;
21use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
22use piecrust_uplink::StandardBufSerializer;
23use rkyv::ser::serializers::{
24    BufferScratch, BufferSerializer, CompositeSerializer,
25};
26use rkyv::ser::Serializer;
27use rkyv::validation::validators::DefaultValidator;
28use rkyv::{Archive, Deserialize, Infallible, Serialize};
29use sha2::{Digest, Sha256};
30
31use crate::abi::ContractId;
32use crate::Error;
33
34/// The maximum size of a memo.
35pub const MAX_MEMO_SIZE: usize = 512;
36
37/// Data for either contract call or contract deployment.
38#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
39#[archive_attr(derive(CheckBytes))]
40#[allow(clippy::large_enum_variant)]
41pub enum TransactionData {
42    /// Data for a contract call.
43    Call(ContractCall),
44    /// Data for a contract deployment.
45    Deploy(ContractDeploy),
46    /// Additional data added to a transaction, that is not a deployment or a
47    /// call.
48    Memo(Vec<u8>),
49    /// Data for blob storage together with contract call.    
50    Blob(Vec<BlobData>),
51}
52
53impl TransactionData {
54    const NONE_ID: u8 = 0x00;
55    const CALL_ID: u8 = 0x01;
56    const DEPLOY_ID: u8 = 0x02;
57    const MEMO_ID: u8 = 0x03;
58    const BLOB_ID: u8 = 0x04;
59
60    /// Return input bytes to hash the `TransactionData`.
61    ///
62    /// Note: The result of this function is *only* meant to be used as an input
63    /// for hashing and *cannot* be used to deserialize the payload again.
64    #[must_use]
65    pub fn signature_message(&self) -> Vec<u8> {
66        let mut bytes = vec![];
67
68        #[allow(clippy::match_same_arms)]
69        match &self {
70            TransactionData::Deploy(d) => {
71                bytes.extend(&d.bytecode.to_hash_input_bytes());
72                bytes.extend(&d.owner);
73                if let Some(init_args) = &d.init_args {
74                    bytes.extend(init_args);
75                }
76            }
77            TransactionData::Call(c) => {
78                bytes.extend(c.contract.as_bytes());
79                bytes.extend(c.fn_name.as_bytes());
80                bytes.extend(&c.fn_args);
81            }
82            TransactionData::Memo(m) => {
83                bytes.extend(m);
84            }
85            TransactionData::Blob(blobs) => {
86                // We only return the bytes of the blobs' versioned hashes to
87                // be signed.
88                // We do not sign the rest of the blob data because this can be
89                // deleted in the future, making it impossible to verify its
90                // signature.
91                // Instead, it is essential to verify commitments and proofs
92                // against the blob data before including them
93                // in a block
94                for blob in blobs {
95                    bytes.extend(blob.to_hash_input_bytes());
96                }
97            }
98        }
99
100        bytes
101    }
102
103    /// Serialize a `TransactionData` into a variable length byte buffer.
104    #[must_use]
105    pub fn to_var_bytes(&self) -> Vec<u8> {
106        let mut bytes = Vec::new();
107        // serialize the contract call, deployment or memo, if present.
108        match &self {
109            TransactionData::Call(call) => {
110                bytes.push(Self::CALL_ID);
111                bytes.extend(call.to_var_bytes());
112            }
113            TransactionData::Deploy(deploy) => {
114                bytes.push(Self::DEPLOY_ID);
115                bytes.extend(deploy.to_var_bytes());
116            }
117            TransactionData::Memo(memo) => {
118                bytes.push(Self::MEMO_ID);
119                bytes.extend((memo.len() as u64).to_bytes());
120                bytes.extend(memo);
121            }
122            TransactionData::Blob(blobs) => {
123                bytes.push(Self::BLOB_ID);
124                // It's safe to use `u8` here because the maximum number of
125                // blobs per transaction is 16 (MAX_MEMO_SIZE /
126                // VERSIONED_HASH_SIZE), which fits in a `u8`.
127                #[allow(clippy::cast_possible_truncation)]
128                bytes.extend((blobs.len() as u8).to_bytes());
129                for blob in blobs {
130                    bytes.extend(blob.to_var_bytes());
131                }
132            }
133        }
134
135        bytes
136    }
137
138    /// Serialize an `Option<TransactionData>` into a variable length byte
139    /// buffer.
140    #[must_use]
141    pub fn option_to_var_bytes(data: Option<&TransactionData>) -> Vec<u8> {
142        let mut bytes = Vec::new();
143        if let Some(data) = data {
144            bytes.extend(data.to_var_bytes());
145        } else {
146            bytes.push(Self::NONE_ID);
147        }
148        bytes
149    }
150
151    /// Deserialize the optional `TransactionData` from bytes slice.
152    ///
153    /// # Errors
154    /// Errors when the bytes are not canonical.
155    pub fn from_slice(buf: &[u8]) -> Result<Option<Self>, BytesError> {
156        let mut buf = buf;
157
158        // deserialize optional transaction data
159        let data = match u8::from_reader(&mut buf)? {
160            Self::NONE_ID => None,
161            Self::CALL_ID => {
162                Some(TransactionData::Call(ContractCall::from_slice(buf)?))
163            }
164            Self::DEPLOY_ID => {
165                Some(TransactionData::Deploy(ContractDeploy::from_slice(buf)?))
166            }
167            Self::MEMO_ID => {
168                // we only build for 64-bit so this truncation is impossible
169                #[allow(clippy::cast_possible_truncation)]
170                let size = u64::from_reader(&mut buf)? as usize;
171
172                if buf.len() != size || size > MAX_MEMO_SIZE {
173                    return Err(BytesError::InvalidData);
174                }
175
176                let memo = buf[..size].to_vec();
177                Some(TransactionData::Memo(memo))
178            }
179            Self::BLOB_ID => {
180                let blobs_len = u8::from_reader(&mut buf)?;
181                let mut blobs = Vec::with_capacity(blobs_len as usize);
182                for _ in 0..blobs_len {
183                    let blob = BlobData::from_buf(&mut buf)?;
184                    blobs.push(blob);
185                }
186                Some(TransactionData::Blob(blobs))
187            }
188            _ => {
189                return Err(BytesError::InvalidData);
190            }
191        };
192
193        Ok(data)
194    }
195}
196
197impl From<ContractCall> for TransactionData {
198    fn from(c: ContractCall) -> Self {
199        TransactionData::Call(c)
200    }
201}
202
203impl From<ContractDeploy> for TransactionData {
204    fn from(d: ContractDeploy) -> Self {
205        TransactionData::Deploy(d)
206    }
207}
208
209impl From<Vec<u8>> for TransactionData {
210    fn from(d: Vec<u8>) -> Self {
211        TransactionData::Memo(d)
212    }
213}
214
215impl From<String> for TransactionData {
216    fn from(d: String) -> Self {
217        TransactionData::Memo(d.as_bytes().to_vec())
218    }
219}
220
221impl From<Vec<BlobData>> for TransactionData {
222    fn from(blobs: Vec<BlobData>) -> Self {
223        TransactionData::Blob(blobs)
224    }
225}
226
227/// Data for performing a contract deployment
228#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
229#[archive_attr(derive(CheckBytes))]
230pub struct ContractDeploy {
231    /// Bytecode of the contract to be deployed.
232    pub bytecode: ContractBytecode,
233    /// Owner of the contract to be deployed.
234    pub owner: Vec<u8>,
235    /// Init method arguments of the deployed contract.
236    pub init_args: Option<Vec<u8>>,
237    /// Nonce for contract id uniqueness and vanity
238    pub nonce: u64,
239}
240
241/// Represents a reference to blob data, including its hash and optional
242/// sidecar.
243#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
244#[archive_attr(derive(CheckBytes))]
245#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub struct BlobData {
248    /// Versioned hash of the KZG commitment: 0x01 ‖ SHA256(commitment)[1..]
249    #[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
250    pub hash: [u8; 32],
251
252    /// Optional sidecar containing the full blob, commitment, and proof.
253    /// This field is optional to allow the sidecar to be deleted after the
254    /// challenge period.
255    pub data: Option<BlobSidecar>,
256}
257
258/// Contains the full contents of a blob, including its KZG commitment and
259/// proof.
260#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
261#[archive_attr(derive(CheckBytes))]
262#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
263#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
264pub struct BlobSidecar {
265    /// KZG commitment to the blob (compressed G₁ point, 48 bytes)
266    #[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
267    pub commitment: [u8; 48],
268
269    /// KZG proof for evaluation correctness (compressed G₁ point, 48 bytes)
270    #[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
271    pub proof: [u8; 48],
272
273    /// Blob data: 4096 field elements, each 32 bytes (128 KiB total)
274    #[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
275    pub data: BlobDataPart,
276}
277
278const BYTES_PER_BLOB: usize = 4096 * 32;
279/// A type alias for the BLOB data part, which consists of 4096 field elements
280/// (each 32 bytes), total 128 KiB
281pub type BlobDataPart = [u8; BYTES_PER_BLOB];
282
283/// All the data the transfer-contract needs to perform a contract-call.
284#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
285#[archive_attr(derive(CheckBytes))]
286pub struct ContractCall {
287    /// The unique ID of the contract to be called.
288    pub contract: ContractId,
289    /// The function of the contract that should be called.
290    pub fn_name: String,
291    /// The function arguments for the contract call, in bytes.
292    pub fn_args: Vec<u8>,
293}
294
295impl ContractDeploy {
296    /// Serialize a `ContractDeploy` into a variable length byte buffer.
297    #[must_use]
298    pub fn to_var_bytes(&self) -> Vec<u8> {
299        let mut bytes = Vec::new();
300
301        bytes.extend(&self.bytecode.to_var_bytes());
302
303        bytes.extend((self.owner.len() as u64).to_bytes());
304        bytes.extend(&self.owner);
305
306        match &self.init_args {
307            Some(init_args) => {
308                bytes.push(1);
309                bytes.extend((init_args.len() as u64).to_bytes());
310                bytes.extend(init_args);
311            }
312            None => bytes.push(0),
313        }
314
315        bytes.extend(self.nonce.to_bytes());
316
317        bytes
318    }
319
320    /// Deserialize a `ContractDeploy` from a byte buffer.
321    ///
322    /// # Errors
323    /// Errors when the bytes are not canonical.
324    pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
325        let mut buf = buf;
326
327        let bytecode = ContractBytecode::from_buf(&mut buf)?;
328
329        let owner = crate::read_vec(&mut buf)?;
330
331        let init_args = match u8::from_reader(&mut buf)? {
332            0 => None,
333            1 => Some(crate::read_vec(&mut buf)?),
334            _ => return Err(BytesError::InvalidData),
335        };
336
337        let nonce = u64::from_reader(&mut buf)?;
338
339        Ok(Self {
340            bytecode,
341            owner,
342            init_args,
343            nonce,
344        })
345    }
346}
347
348impl ContractCall {
349    /// Creates a new contract call with empty `fn_args`.
350    ///
351    /// Initializes a contract call by setting the function arguments to an
352    /// empty vector.
353    ///
354    /// # Parameters
355    /// - `contract`: A value convertible into a `ContractId`, representing the
356    ///   target contract.
357    /// - `fn_name`: A value convertible into a `String`, specifying the name of
358    ///   the function to be called.
359    pub fn new(
360        contract: impl Into<ContractId>,
361        fn_name: impl Into<String>,
362    ) -> Self {
363        Self {
364            contract: contract.into(),
365            fn_name: fn_name.into(),
366            fn_args: vec![],
367        }
368    }
369
370    /// Consumes `self` and returns a new contract call with raw function
371    /// arguments.
372    ///
373    /// Updates the contract call with raw serialized arguments provided as a
374    /// `Vec<u8>`.
375    ///
376    /// # Parameters
377    /// - `fn_args`: A `Vec<u8>` representing pre-serialized function arguments.
378    #[must_use]
379    pub fn with_raw_args(mut self, fn_args: Vec<u8>) -> Self {
380        self.fn_args = fn_args;
381        self
382    }
383
384    /// Consumes `self` and returns a new contract call with serialized function
385    /// arguments.
386    ///
387    /// Serializes the provided function arguments using `rkyv` serialization
388    /// and returns an updated contract call.
389    ///
390    /// # Parameters
391    /// - `fn_args`: A reference to an object implementing `Serialize` for the
392    ///   given `AllocSerializer`.
393    ///
394    /// # Returns
395    /// - `Ok(Self)`: If the serialization is successful.
396    /// - `Err(Error::Rkyv)`: If the `rkyv` serialization fails.
397    ///
398    /// # Errors
399    /// Returns an error if `rkyv` serialization fails.
400    pub fn with_args<A>(self, fn_arg: &A) -> Result<Self, Error>
401    where
402        A: for<'b> Serialize<StandardBufSerializer<'b>>,
403        A::Archived: for<'b> CheckBytes<DefaultValidator<'b>>,
404    {
405        // scratch-space and page-size values taken from piecrust-uplink
406        const SCRATCH_SPACE: usize = 1024;
407        const PAGE_SIZE: usize = 0x1000;
408
409        let mut sbuf = [0u8; SCRATCH_SPACE];
410        let scratch = BufferScratch::new(&mut sbuf);
411        let mut buffer = [0u8; PAGE_SIZE];
412        let ser = BufferSerializer::new(&mut buffer[..]);
413        let mut ser = CompositeSerializer::new(ser, scratch, Infallible);
414
415        ser.serialize_value(fn_arg)
416            .map_err(|e| Error::Rkyv(format!("{e:?}")))?;
417        let pos = ser.pos();
418
419        let fn_args = buffer[..pos].to_vec();
420
421        Ok(self.with_raw_args(fn_args))
422    }
423
424    /// Serialize a `ContractCall` into a variable length byte buffer.
425    #[must_use]
426    pub fn to_var_bytes(&self) -> Vec<u8> {
427        let mut bytes = Vec::new();
428
429        bytes.extend(self.contract.as_bytes());
430
431        let fn_name_bytes = self.fn_name.as_bytes();
432        bytes.extend((fn_name_bytes.len() as u64).to_bytes());
433        bytes.extend(fn_name_bytes);
434
435        bytes.extend((self.fn_args.len() as u64).to_bytes());
436        bytes.extend(&self.fn_args);
437
438        bytes
439    }
440
441    /// Deserialize a `ContractCall` from a byte buffer.
442    ///
443    /// # Errors
444    /// Errors when the bytes are not canonical.
445    pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
446        let mut buf = buf;
447
448        let contract = crate::read_arr::<32>(&mut buf)?;
449
450        let fn_name = crate::read_str(&mut buf)?;
451
452        let fn_args = crate::read_vec(&mut buf)?;
453
454        Ok(Self {
455            contract: contract.into(),
456            fn_name,
457            fn_args,
458        })
459    }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
463#[archive_attr(derive(CheckBytes))]
464/// Holds bytes of bytecode and its hash.
465pub struct ContractBytecode {
466    /// Blake3 hash of the bytecode bytes.
467    pub hash: [u8; 32],
468    /// Bytecode bytes.
469    pub bytes: Vec<u8>,
470}
471
472impl ContractBytecode {
473    /// Provides contribution bytes for an external hash.
474    #[must_use]
475    pub fn to_hash_input_bytes(&self) -> Vec<u8> {
476        self.hash.to_vec()
477    }
478
479    /// Serializes this object into a variable length buffer
480    #[must_use]
481    pub fn to_var_bytes(&self) -> Vec<u8> {
482        let mut bytes = Vec::new();
483        bytes.extend(self.hash);
484        bytes.extend((self.bytes.len() as u64).to_bytes());
485        bytes.extend(&self.bytes);
486        bytes
487    }
488
489    /// Deserializes from a bytes buffer.
490    /// Resets buffer to a position after the bytes read.
491    ///
492    /// # Errors
493    /// Errors when the bytes are not available.
494    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
495        let hash = crate::read_arr::<32>(buf)?;
496        let bytes = crate::read_vec(buf)?;
497        Ok(Self { hash, bytes })
498    }
499}
500
501impl BlobData {
502    /// Version of the KZG commitment hash used in versioned blob hashes.
503    pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
504
505    /// Provides contribution bytes for an external hash.
506    #[must_use]
507    pub fn to_hash_input_bytes(&self) -> Vec<u8> {
508        self.hash.to_vec()
509    }
510
511    /// Serializes this object into a variable length buffer
512    #[must_use]
513    pub fn to_var_bytes(&self) -> Vec<u8> {
514        let mut bytes = Vec::new();
515        bytes.extend(self.hash);
516        if let Some(data) = &self.data {
517            bytes.push(1u8);
518            bytes.extend(data.to_var_bytes());
519        } else {
520            bytes.push(0u8);
521        }
522        bytes
523    }
524
525    /// Deserializes from a bytes buffer.
526    /// Resets buffer to a position after the bytes read.
527    ///
528    /// # Errors
529    /// Errors when the bytes are not available.
530    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
531        let hash = crate::read_arr(buf)?;
532
533        let data = match u8::from_reader(buf)? {
534            0 => None,
535            1 => Some(BlobSidecar::from_buf(buf)?),
536            _ => return Err(BytesError::InvalidData),
537        };
538
539        Ok(Self { hash, data })
540    }
541
542    /// Take the data field, if it exists.
543    #[must_use]
544    pub fn take_sidecar(&mut self) -> Option<BlobSidecar> {
545        self.data.take()
546    }
547
548    /// Computes the versioned blob hash from a 48-byte KZG commitment.
549    ///
550    /// This follows the EIP-4844 definition: 0x01 ‖ SHA256(commitment)[1..]
551    #[must_use]
552    pub fn hash_from_commitment(commitment: &[u8]) -> [u8; 32] {
553        let digest = Sha256::digest(commitment);
554        let mut out = [0u8; 32];
555        out[0] = Self::VERSIONED_HASH_VERSION_KZG;
556        out[1..].copy_from_slice(&digest[1..]);
557        out
558    }
559}
560
561impl BlobSidecar {
562    /// Serializes this object into a variable length buffer
563    #[must_use]
564    pub fn to_var_bytes(&self) -> Vec<u8> {
565        let mut bytes = Vec::new();
566        bytes.extend(self.commitment);
567        bytes.extend(self.proof);
568        bytes.extend(self.data);
569        bytes
570    }
571
572    /// Deserializes from a bytes buffer.
573    /// Resets buffer to a position after the bytes read.
574    ///
575    /// # Errors
576    /// Errors when the bytes are not available.
577    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
578        let commitment = crate::read_arr(buf)?;
579        let proof = crate::read_arr(buf)?;
580        let data = crate::read_arr(buf)?;
581
582        Ok(Self {
583            commitment,
584            proof,
585            data,
586        })
587    }
588}