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))]
286#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
287#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
288pub struct ContractCall {
289    /// The unique ID of the contract to be called.
290    pub contract: ContractId,
291    /// The function of the contract that should be called.
292    pub fn_name: String,
293    /// The function arguments for the contract call, in bytes.
294    #[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
295    pub fn_args: Vec<u8>,
296}
297
298impl ContractDeploy {
299    /// Serialize a `ContractDeploy` into a variable length byte buffer.
300    #[must_use]
301    pub fn to_var_bytes(&self) -> Vec<u8> {
302        let mut bytes = Vec::new();
303
304        bytes.extend(&self.bytecode.to_var_bytes());
305
306        bytes.extend((self.owner.len() as u64).to_bytes());
307        bytes.extend(&self.owner);
308
309        match &self.init_args {
310            Some(init_args) => {
311                bytes.push(1);
312                bytes.extend((init_args.len() as u64).to_bytes());
313                bytes.extend(init_args);
314            }
315            None => bytes.push(0),
316        }
317
318        bytes.extend(self.nonce.to_bytes());
319
320        bytes
321    }
322
323    /// Deserialize a `ContractDeploy` from a byte buffer.
324    ///
325    /// # Errors
326    /// Errors when the bytes are not canonical.
327    pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
328        let mut buf = buf;
329
330        let bytecode = ContractBytecode::from_buf(&mut buf)?;
331
332        let owner = crate::read_vec(&mut buf)?;
333
334        let init_args = match u8::from_reader(&mut buf)? {
335            0 => None,
336            1 => Some(crate::read_vec(&mut buf)?),
337            _ => return Err(BytesError::InvalidData),
338        };
339
340        let nonce = u64::from_reader(&mut buf)?;
341
342        Ok(Self {
343            bytecode,
344            owner,
345            init_args,
346            nonce,
347        })
348    }
349}
350
351impl ContractCall {
352    /// Creates a new contract call with empty `fn_args`.
353    ///
354    /// Initializes a contract call by setting the function arguments to an
355    /// empty vector.
356    ///
357    /// # Parameters
358    /// - `contract`: A value convertible into a `ContractId`, representing the
359    ///   target contract.
360    /// - `fn_name`: A value convertible into a `String`, specifying the name of
361    ///   the function to be called.
362    pub fn new(
363        contract: impl Into<ContractId>,
364        fn_name: impl Into<String>,
365    ) -> Self {
366        Self {
367            contract: contract.into(),
368            fn_name: fn_name.into(),
369            fn_args: vec![],
370        }
371    }
372
373    /// Consumes `self` and returns a new contract call with raw function
374    /// arguments.
375    ///
376    /// Updates the contract call with raw serialized arguments provided as a
377    /// `Vec<u8>`.
378    ///
379    /// # Parameters
380    /// - `fn_args`: A `Vec<u8>` representing pre-serialized function arguments.
381    #[must_use]
382    pub fn with_raw_args(mut self, fn_args: Vec<u8>) -> Self {
383        self.fn_args = fn_args;
384        self
385    }
386
387    /// Consumes `self` and returns a new contract call with serialized function
388    /// arguments.
389    ///
390    /// Serializes the provided function arguments using `rkyv` serialization
391    /// and returns an updated contract call.
392    ///
393    /// # Parameters
394    /// - `fn_args`: A reference to an object implementing `Serialize` for the
395    ///   given `AllocSerializer`.
396    ///
397    /// # Returns
398    /// - `Ok(Self)`: If the serialization is successful.
399    /// - `Err(Error::Rkyv)`: If the `rkyv` serialization fails.
400    ///
401    /// # Errors
402    /// Returns an error if `rkyv` serialization fails.
403    pub fn with_args<A>(self, fn_arg: &A) -> Result<Self, Error>
404    where
405        A: for<'b> Serialize<StandardBufSerializer<'b>>,
406        A::Archived: for<'b> CheckBytes<DefaultValidator<'b>>,
407    {
408        // scratch-space and page-size values taken from piecrust-uplink
409        const SCRATCH_SPACE: usize = 1024;
410        const PAGE_SIZE: usize = 0x1000;
411
412        let mut sbuf = [0u8; SCRATCH_SPACE];
413        let scratch = BufferScratch::new(&mut sbuf);
414        let mut buffer = [0u8; PAGE_SIZE];
415        let ser = BufferSerializer::new(&mut buffer[..]);
416        let mut ser = CompositeSerializer::new(ser, scratch, Infallible);
417
418        ser.serialize_value(fn_arg)
419            .map_err(|e| Error::Rkyv(format!("{e:?}")))?;
420        let pos = ser.pos();
421
422        let fn_args = buffer[..pos].to_vec();
423
424        Ok(self.with_raw_args(fn_args))
425    }
426
427    /// Serialize a `ContractCall` into a variable length byte buffer.
428    #[must_use]
429    pub fn to_var_bytes(&self) -> Vec<u8> {
430        let mut bytes = Vec::new();
431
432        bytes.extend(self.contract.as_bytes());
433
434        let fn_name_bytes = self.fn_name.as_bytes();
435        bytes.extend((fn_name_bytes.len() as u64).to_bytes());
436        bytes.extend(fn_name_bytes);
437
438        bytes.extend((self.fn_args.len() as u64).to_bytes());
439        bytes.extend(&self.fn_args);
440
441        bytes
442    }
443
444    /// Deserialize a `ContractCall` from a byte buffer.
445    ///
446    /// # Errors
447    /// Errors when the bytes are not canonical.
448    pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
449        let mut buf = buf;
450
451        let contract = crate::read_arr::<32>(&mut buf)?;
452
453        let fn_name = crate::read_str(&mut buf)?;
454
455        let fn_args = crate::read_vec(&mut buf)?;
456
457        Ok(Self {
458            contract: contract.into(),
459            fn_name,
460            fn_args,
461        })
462    }
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
466#[archive_attr(derive(CheckBytes))]
467/// Holds bytes of bytecode and its hash.
468pub struct ContractBytecode {
469    /// Blake3 hash of the bytecode bytes.
470    pub hash: [u8; 32],
471    /// Bytecode bytes.
472    pub bytes: Vec<u8>,
473}
474
475impl ContractBytecode {
476    /// Provides contribution bytes for an external hash.
477    #[must_use]
478    pub fn to_hash_input_bytes(&self) -> Vec<u8> {
479        self.hash.to_vec()
480    }
481
482    /// Serializes this object into a variable length buffer
483    #[must_use]
484    pub fn to_var_bytes(&self) -> Vec<u8> {
485        let mut bytes = Vec::new();
486        bytes.extend(self.hash);
487        bytes.extend((self.bytes.len() as u64).to_bytes());
488        bytes.extend(&self.bytes);
489        bytes
490    }
491
492    /// Deserializes from a bytes buffer.
493    /// Resets buffer to a position after the bytes read.
494    ///
495    /// # Errors
496    /// Errors when the bytes are not available.
497    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
498        let hash = crate::read_arr::<32>(buf)?;
499        let bytes = crate::read_vec(buf)?;
500        Ok(Self { hash, bytes })
501    }
502}
503
504impl BlobData {
505    /// Version of the KZG commitment hash used in versioned blob hashes.
506    pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;
507
508    /// Provides contribution bytes for an external hash.
509    #[must_use]
510    pub fn to_hash_input_bytes(&self) -> Vec<u8> {
511        self.hash.to_vec()
512    }
513
514    /// Serializes this object into a variable length buffer
515    #[must_use]
516    pub fn to_var_bytes(&self) -> Vec<u8> {
517        let mut bytes = Vec::new();
518        bytes.extend(self.hash);
519        if let Some(data) = &self.data {
520            bytes.push(1u8);
521            bytes.extend(data.to_var_bytes());
522        } else {
523            bytes.push(0u8);
524        }
525        bytes
526    }
527
528    /// Deserializes from a bytes buffer.
529    /// Resets buffer to a position after the bytes read.
530    ///
531    /// # Errors
532    /// Errors when the bytes are not available.
533    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
534        let hash = crate::read_arr(buf)?;
535
536        let data = match u8::from_reader(buf)? {
537            0 => None,
538            1 => Some(BlobSidecar::from_buf(buf)?),
539            _ => return Err(BytesError::InvalidData),
540        };
541
542        Ok(Self { hash, data })
543    }
544
545    /// Take the data field, if it exists.
546    #[must_use]
547    pub fn take_sidecar(&mut self) -> Option<BlobSidecar> {
548        self.data.take()
549    }
550
551    /// Computes the versioned blob hash from a 48-byte KZG commitment.
552    ///
553    /// This follows the EIP-4844 definition: 0x01 ‖ SHA256(commitment)[1..]
554    #[must_use]
555    pub fn hash_from_commitment(commitment: &[u8]) -> [u8; 32] {
556        let digest = Sha256::digest(commitment);
557        let mut out = [0u8; 32];
558        out[0] = Self::VERSIONED_HASH_VERSION_KZG;
559        out[1..].copy_from_slice(&digest[1..]);
560        out
561    }
562}
563
564impl BlobSidecar {
565    /// Serializes this object into a variable length buffer
566    #[must_use]
567    pub fn to_var_bytes(&self) -> Vec<u8> {
568        let mut bytes = Vec::new();
569        bytes.extend(self.commitment);
570        bytes.extend(self.proof);
571        bytes.extend(self.data);
572        bytes
573    }
574
575    /// Deserializes from a bytes buffer.
576    /// Resets buffer to a position after the bytes read.
577    ///
578    /// # Errors
579    /// Errors when the bytes are not available.
580    pub fn from_buf(buf: &mut &[u8]) -> Result<Self, BytesError> {
581        let commitment = crate::read_arr(buf)?;
582        let proof = crate::read_arr(buf)?;
583        let data = crate::read_arr(buf)?;
584
585        Ok(Self {
586            commitment,
587            proof,
588            data,
589        })
590    }
591}