thor_devkit/
transactions.rs

1//! VeChain transactions support.
2
3use crate::address::{Address, AddressConvertible, PrivateKey};
4#[cfg(feature = "builder")]
5use crate::network::ThorNode;
6use crate::rlp::{
7    lstrip, static_left_pad, AsBytes, AsVec, BufMut, Bytes, BytesMut, Decodable, Encodable, Maybe,
8    RLPError,
9};
10#[cfg(feature = "builder")]
11pub use crate::transaction_builder::{TransactionBuilder, TransactionBuilderError};
12use crate::utils::blake2_256;
13use crate::{rlp_encodable, U256};
14use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
15use secp256k1::{Message, PublicKey, Secp256k1};
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19rlp_encodable! {
20    /// Represents a single VeChain transaction.
21    #[cfg_attr(feature="serde", serde_with::serde_as)]
22    #[cfg_attr(feature="serde", derive(Deserialize, Serialize))]
23    #[derive(Clone, Debug, Eq, PartialEq)]
24    pub struct Transaction {
25        /// Chain tag
26        #[cfg_attr(feature="serde", serde(rename="chainTag"))]
27        pub chain_tag: u8,
28        /// Previous block reference
29        ///
30        /// First 4 bytes (BE) are block height, the rest is part of referred block ID.
31        #[cfg_attr(feature="serde", serde(rename="blockRef"))]
32        pub block_ref: u64,
33        /// Expiration (in blocks)
34        pub expiration: u32,
35        /// Vector of clauses
36        pub clauses: Vec<Clause>,
37        /// Coefficient to calculate the gas price.
38        #[cfg_attr(feature="serde", serde(rename="gasPriceCoef"))]
39        pub gas_price_coef: u8,
40        /// Maximal amount of gas to spend for transaction.
41        pub gas: u64,
42        /// Hash of transaction on which current transaction depends.
43        ///
44        /// May be left unspecified if this functionality is not necessary.
45        #[cfg_attr(feature="serde", serde(rename="dependsOn"))]
46        pub depends_on: Option<U256> => AsBytes<U256>,
47        /// Transaction nonce
48        pub nonce: u64,
49        /// Reserved fields.
50        pub reserved: Option<Reserved> => AsVec<Reserved>,
51        /// Signature. 65 bytes for regular transactions, 130 - for VIP-191.
52        ///
53        /// Ignored when making a signing hash.
54        ///
55        /// For VIP-191 transactions, this would be a simple concatenation
56        /// of two signatures.
57        #[cfg_attr(feature="serde", serde(with = "serde_with::As::<Option<crate::utils::unhex::Hex>>"))]
58        pub signature: Option<Bytes> => Maybe<Bytes>,
59    }
60}
61
62impl Transaction {
63    /// Gas cost for whole transaction execution.
64    pub const TRANSACTION_GAS: u64 = 5_000;
65
66    pub fn get_signing_hash(&self) -> [u8; 32] {
67        //! Get a signing hash for this transaction.
68        let mut encoded = Vec::with_capacity(1024);
69        let mut without_signature = self.clone();
70        without_signature.signature = None;
71        without_signature.encode(&mut encoded);
72        blake2_256(&[encoded])
73    }
74
75    pub fn get_delegate_signing_hash(&self, delegate_for: &Address) -> [u8; 32] {
76        //! Get a signing hash for this transaction with fee delegation.
77        //!
78        //! `VIP-191 <https://github.com/vechain/VIPs/blob/master/vips/VIP-191.md>`
79        let main_hash = self.get_signing_hash();
80        blake2_256(&[&main_hash[..], &delegate_for.to_fixed_bytes()[..]])
81    }
82
83    pub fn sign(self, private_key: &PrivateKey) -> Self {
84        //! Create a copy of transaction with a signature emplaced.
85        //!
86        //! You can call `.encode()` on the result to get bytes ready to be sent
87        //! over wire.
88        let hash = self.get_signing_hash();
89        let signature = Self::sign_hash(hash, private_key);
90        self.with_signature(Bytes::copy_from_slice(&signature))
91            .expect("generated signature must be correct")
92    }
93
94    const fn signature_length_valid(&self) -> bool {
95        match &self.signature {
96            None => true,
97            Some(signature) => {
98                self.is_delegated() && signature.len() == 130
99                    || !self.is_delegated() && signature.len() == 65
100            }
101        }
102    }
103
104    pub fn with_signature(self, signature: Bytes) -> Result<Self, secp256k1::Error> {
105        //! Set a signature for this transaction.
106        let copy = Self {
107            signature: Some(signature),
108            ..self
109        };
110        if copy.signature_length_valid() {
111            Ok(copy)
112        } else {
113            Err(secp256k1::Error::IncorrectSignature)
114        }
115    }
116
117    pub fn sign_hash(hash: [u8; 32], private_key: &PrivateKey) -> [u8; 65] {
118        //! Sign a hash obtained from `Transaction::get_signing_hash`.
119        let secp = Secp256k1::signing_only();
120        let signature =
121            secp.sign_ecdsa_recoverable(&Message::from_slice(&hash).unwrap(), private_key);
122        let (recovery_id, bytes) = signature.serialize_compact();
123        bytes
124            .into_iter()
125            .chain([recovery_id.to_i32() as u8])
126            .collect::<Vec<_>>()
127            .try_into()
128            .unwrap()
129    }
130
131    pub fn intrinsic_gas(&self) -> u64 {
132        //! Calculate the intrinsic gas amount required for this transaction.
133        //!
134        //! This amount is always less than or equal to actual amount of gas necessary.
135        //! `More info <https://docs.vechain.org/core-concepts/transactions/transaction-calculation>`
136        let clauses_cost = if self.clauses.is_empty() {
137            Clause::REGULAR_CLAUSE_GAS
138        } else {
139            self.clauses.iter().map(Clause::intrinsic_gas).sum()
140        };
141        clauses_cost + Self::TRANSACTION_GAS
142    }
143
144    pub fn origin(&self) -> Result<Option<PublicKey>, secp256k1::Error> {
145        //! Recover origin public key using the signature.
146        //!
147        //! Returns `Ok(None)` if signature is unset.
148        match &self.signature {
149            None => Ok(None),
150            Some(signature) if self.signature_length_valid() => {
151                let hash = self.get_signing_hash();
152                let secp = Secp256k1::verification_only();
153
154                Ok(Some(secp.recover_ecdsa(
155                    &Message::from_slice(&hash)?,
156                    &RecoverableSignature::from_compact(
157                        &signature[..64],
158                        RecoveryId::from_i32(signature[64] as i32)?,
159                    )?,
160                )?))
161            }
162            _ => Err(secp256k1::Error::IncorrectSignature),
163        }
164    }
165
166    pub fn delegator(&self) -> Result<Option<PublicKey>, secp256k1::Error> {
167        //! Recover delegator public key using the signature.
168        //!
169        //! Returns `Ok(None)` if signature is unset or transaction is not delegated.
170        if !self.is_delegated() {
171            return Ok(None);
172        }
173        match &self.signature {
174            None => Ok(None),
175            Some(signature) if self.signature_length_valid() => {
176                let hash = self.get_delegate_signing_hash(
177                    &self
178                        .origin()?
179                        .expect("Must be set, already checked signature")
180                        .address(),
181                );
182                let secp = Secp256k1::verification_only();
183
184                Ok(Some(secp.recover_ecdsa(
185                    &Message::from_slice(&hash)?,
186                    &RecoverableSignature::from_compact(
187                        &signature[65..129],
188                        RecoveryId::from_i32(signature[129] as i32)?,
189                    )?,
190                )?))
191            }
192            _ => Err(secp256k1::Error::IncorrectSignature),
193        }
194    }
195
196    pub const fn is_delegated(&self) -> bool {
197        //! Check if transaction is VIP-191 delegated.
198        if let Some(reserved) = &self.reserved {
199            reserved.is_delegated()
200        } else {
201            false
202        }
203    }
204
205    pub fn id(&self) -> Result<Option<[u8; 32]>, secp256k1::Error> {
206        //! Calculate transaction ID using the signature.
207        //!
208        //! Returns `Ok(None)` if signature is unset.
209        match self.origin()? {
210            None => Ok(None),
211            Some(origin) => Ok(Some(blake2_256(&[
212                &self.get_signing_hash()[..],
213                &origin.address().to_fixed_bytes()[..],
214            ]))),
215        }
216    }
217
218    pub fn has_valid_signature(&self) -> bool {
219        //! Check wheter the signature is valid.
220        self._has_valid_signature().unwrap_or(false)
221    }
222
223    fn _has_valid_signature(&self) -> Result<bool, secp256k1::Error> {
224        //! Check wheter the signature is valid.
225        if !self.signature_length_valid() {
226            return Ok(false);
227        }
228        match &self.signature {
229            None => Ok(false),
230            Some(signature) => {
231                let hash = self.get_signing_hash();
232                let secp = Secp256k1::verification_only();
233                Ok(secp
234                    .recover_ecdsa(
235                        &Message::from_slice(&hash)?,
236                        &RecoverableSignature::from_compact(
237                            &signature[..64],
238                            RecoveryId::from_i32(signature[64] as i32)?,
239                        )?,
240                    )
241                    .is_ok())
242            }
243        }
244    }
245
246    pub fn to_broadcastable_bytes(&self) -> Result<Bytes, secp256k1::Error> {
247        //! Create a binary representation.
248        //!
249        //! Returns `Err(secp256k1::Error::IncorrectSignature)` if signature is not set.
250        if self.signature.is_some() {
251            let mut buf = BytesMut::new();
252            self.encode(&mut buf);
253            Ok(buf.into())
254        } else {
255            Err(secp256k1::Error::IncorrectSignature)
256        }
257    }
258
259    #[cfg(feature = "builder")]
260    pub fn build(node: ThorNode) -> TransactionBuilder {
261        //! Create a transaction builder.
262        TransactionBuilder::new(node)
263    }
264}
265
266rlp_encodable! {
267    /// Represents a single transaction clause (recipient, value and data).
268    #[cfg_attr(feature="serde", serde_with::serde_as)]
269    #[cfg_attr(feature="serde", derive(Deserialize, Serialize))]
270    #[derive(Clone, Debug, Eq, PartialEq)]
271    pub struct Clause {
272        /// Recipient
273        pub to: Option<Address> => AsBytes<Address>,
274        /// Amount of funds to spend.
275        pub value: U256,
276        /// Contract code or other data.
277        #[cfg_attr(feature="serde", serde(with = "serde_with::As::<crate::utils::unhex::Hex>"))]
278        pub data: Bytes,
279    }
280}
281
282impl Clause {
283    /// Gas spent for one regular clause execution.
284    pub const REGULAR_CLAUSE_GAS: u64 = 16_000;
285    /// Gas spent for one contract creation (without `to`) clause execution.
286    pub const CONTRACT_CREATION_CLAUSE_GAS: u64 = 48_000;
287    /// Intrinsic gas usage for a single zero byte of data.
288    pub const ZERO_DATA_BYTE_GAS_COST: u64 = 4;
289    /// Intrinsic gas usage for a single non-zero byte of data.
290    pub const NONZERO_DATA_BYTE_GAS_COST: u64 = 68;
291
292    pub fn intrinsic_gas(&self) -> u64 {
293        //! Calculate the intrinsic gas amount required for executing this clause.
294        //!
295        //! This amount is always less than actual amount of gas necessary.
296        //! `More info <https://docs.vechain.org/core-concepts/transactions/transaction-calculation>`
297        let clause_gas = if self.to.is_some() {
298            Self::REGULAR_CLAUSE_GAS
299        } else {
300            Self::CONTRACT_CREATION_CLAUSE_GAS
301        };
302        let data_gas: u64 = self
303            .data
304            .iter()
305            .map(|&b| {
306                if b == 0 {
307                    Self::ZERO_DATA_BYTE_GAS_COST
308                } else {
309                    Self::NONZERO_DATA_BYTE_GAS_COST
310                }
311            })
312            .sum();
313        clause_gas + data_gas
314    }
315}
316
317/// Represents a transaction's ``reserved`` field.
318#[cfg_attr(feature = "serde", serde_with::serde_as)]
319#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
320#[derive(Clone, Debug, Eq, PartialEq)]
321pub struct Reserved {
322    /// Features to enable (bitmask).
323    pub features: u32,
324    /// Currently unused field.
325    #[cfg_attr(
326        feature = "serde",
327        serde(with = "serde_with::As::<Vec<crate::utils::unhex::Hex>>")
328    )]
329    pub unused: Vec<Bytes>,
330}
331
332impl Encodable for Reserved {
333    fn encode(&self, out: &mut dyn BufMut) {
334        let mut buf = vec![];
335        self.features.to_be_bytes().encode(&mut buf);
336        let mut stripped_buf: Vec<_> = [lstrip(&buf[1..])]
337            .into_iter()
338            .map(Bytes::from)
339            .chain(self.unused.clone())
340            .rev()
341            .skip_while(Bytes::is_empty)
342            .collect();
343        stripped_buf.reverse();
344        stripped_buf.encode(out)
345    }
346}
347
348impl Decodable for Reserved {
349    fn decode(buf: &mut &[u8]) -> Result<Self, RLPError> {
350        if let Some((feature_bytes, unused)) = Vec::<Bytes>::decode(buf)?.split_first() {
351            Ok(Self {
352                features: u32::from_be_bytes(static_left_pad(feature_bytes)?),
353                unused: unused.to_vec(),
354            })
355        } else {
356            Ok(Self::new_empty())
357        }
358    }
359}
360
361impl Reserved {
362    /// Features bitmask for delegated transaction.
363    pub const DELEGATED_BIT: u32 = 1;
364
365    pub const fn new_delegated() -> Self {
366        //! Create reserved structure kind for VIP-191 delegation.
367        Self {
368            features: Self::DELEGATED_BIT,
369            unused: vec![],
370        }
371    }
372    pub const fn new_empty() -> Self {
373        //! Create reserved structure kind for regular transaction.
374        Self {
375            features: 0,
376            unused: vec![],
377        }
378    }
379    pub const fn is_delegated(&self) -> bool {
380        //! Belongs to delegated transaction?
381        self.features & Self::DELEGATED_BIT != 0
382    }
383    pub fn is_empty(&self) -> bool {
384        //! Belongs to non-delegated transaction?
385        self.features == 0 && self.unused.is_empty()
386    }
387}