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