Skip to main content

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