postchain_client/utils/
transaction.rs

1//! Transaction handling and digital signature functionality.
2//! 
3//! This module provides functionality for creating, managing, and signing blockchain
4//! transactions. It supports single and multi-signature transactions using ECDSA
5//! with the secp256k1 curve.
6//! 
7//! # Features
8//! - Transaction creation and management
9//! - Transaction ID generation
10//! - Single and multi-signature support
11//! - GTV (Generic Tree Value) encoding
12//! 
13//! # Example
14//! ```
15//! use crate::utils::transaction::{Transaction, TransactionStatus};
16//!
17//! let brid = "FA189BEBA886669CF7DF7DB3D8CFD878D1F80ED360BDCF26B43ABE3D9B3D53CC"; // Replace with actual blockchain RID
18//!
19//! let brid_to_vec = hex::decode(brid).unwrap();
20//! 
21//! // Create a new transaction
22//! let mut tx = Transaction::new(
23//!     brid_to_vec,    // blockchain RID
24//!     Some(vec![]),   // operations
25//!     None,           // signers
26//!     None            // signatures
27//! );
28//!
29//! // Sign the transaction
30//! let private_key1 = "C70D5A77CC10552019179B7390545C46647C9FCA1B6485850F2B913F87270300";  // Replace with actual private key
31//! tx.sign(&hex::decode(private_key1).unwrap().try_into().expect("Invalid private key 1")).expect("Failed to sign transaction");
32//!
33//! // Multi sign the transaction
34//! let private_key2 = "17106092B72489B785615BD2ACB2DDE8D0EA05A2029DCA4054987494781F988C";  // Replace with actual private key
35//! tx.sign(&[
36//! &hex::decode(private_key1).unwrap().try_into().expect("Invalid private key 1"),
37//! &hex::decode(private_key2).unwrap().try_into().expect("Invalid private key 2")
38//! ]).expect("Failed to multi sign transaction");
39//!
40//! // Sign the transaction from raw private key
41//! tx.sign_from_raw_priv_key(private_key1);
42//!
43//! // Multi sign the transaction from raw private keys
44//! tx.multi_sign_from_raw_priv_keys(&[private_key1, private_key2]);
45//!
46//! ```
47
48
49use crate::encoding::gtv;
50use crate::utils::hasher::gtv_hash;
51use crate::encoding::gtv::decode as gtv_decode;
52use super::{hasher, operation::Operation, operation::Params as Op_Params};
53use secp256k1::{PublicKey, Secp256k1, SecretKey, Message, ecdsa::Signature};
54use hex::FromHex;
55
56/// Represents the current status of a transaction in the blockchain.
57#[derive(Debug, PartialEq)]
58pub enum TransactionStatus {
59    /// Transaction was rejected by the blockchain
60    REJECTED,
61    /// Transaction has been confirmed and included in a block
62    CONFIRMED,
63    /// Transaction is waiting to be included in a block
64    WAITING,
65    /// Transaction status is unknown
66    UNKNOWN
67}
68
69/// Represents a blockchain transaction with operations and signatures.
70/// 
71/// A transaction contains a list of operations to be executed, along with
72/// the necessary signatures to authorize these operations. It supports
73/// both single and multi-signature scenarios.
74#[derive(Debug)]
75pub struct Transaction {
76    /// Unique identifier of the blockchain this transaction belongs to
77    pub blockchain_rid: Vec<u8>,
78    /// List of operations to be executed in this transaction
79    pub operations: Option<Vec<Operation>>,
80    /// List of public keys of the signers
81    pub signers: Option<Vec<Vec<u8>>>,
82    /// List of signatures corresponding to the signers
83    pub signatures: Option<Vec<Vec<u8>>>,
84    // Hash version (default is 1)
85    pub merkle_hash_version: u8
86}
87
88/// Helper macro to extract a field from a dictionary with type checking.
89///
90/// This macro simplifies extracting a value from a dictionary (typically a HashMap)
91/// where the value is expected to be of a specific enum variant. It performs a type check
92/// and returns a reference to the inner value if the type matches, or returns an error if not.
93///
94/// # Parameters
95/// - `$dict`: The dictionary (e.g., &HashMap<String, Params>) to extract from.
96/// - `$key`: The key to look up in the dictionary.
97/// - `$variant`: The enum variant to match (e.g., `Op_Params::ByteArray`).
98/// - `$err`: The error string to use if the field is missing or of the wrong type.
99///
100/// # Returns
101/// - On success: a reference to the inner value of the matched variant.
102/// - On failure: returns from the enclosing function with an error message.
103///
104/// # Example
105/// ```
106/// let value = extract_field!(my_dict, "blockHeader", crate::utils::operation::Params::ByteArray, "blockHeader");
107/// ```
108macro_rules! extract_field {
109    ($dict:expr, $key:expr, $variant:path, $err:expr) => {
110        match $dict.get($key) {
111            Some($variant(val)) => val,
112            _ => return Err(format!("Missing or invalid field: {}", $key)),
113        }
114    };
115}
116
117impl Default for Transaction {
118    /// Creates a new Transaction with default values and performs automatic initialization.
119    /// 
120    /// # Example
121    /// ```
122    /// let mut tx = Transaction {
123    ///     blockchain_rid: hex::decode(brid).unwrap(),
124    ///     operations: Some(ops),
125    ///     ..Default::default()  // This will trigger auto initialization
126    /// };
127    /// ```
128    fn default() -> Self {
129        Self {
130            blockchain_rid: vec![],
131            operations: None,   
132            signers: None,      
133            signatures: None,
134            merkle_hash_version: 1
135        }
136    }
137}
138
139#[derive(Debug, PartialEq)]
140pub struct TransactionConfirmationProofData {
141  pub block_header: Vec<u8>,
142  pub hash: Vec<u8>,
143  pub tx_index: i64,
144  pub witness: Vec<u8>,
145  pub merkle_proof_tree: Vec<crate::utils::operation::Params>
146}
147
148impl Transaction {
149    /// Creates a new transaction with the specified parameters.
150    ///
151    /// # Arguments
152    /// * `blockchain_rid` - Unique identifier of the blockchain
153    /// * `operations` - Optional list of operations to be executed
154    /// * `signers` - Optional list of public keys of the signers
155    /// * `signatures` - Optional list of signatures
156    /// 
157    /// # Returns
158    /// A new Transaction instance
159    pub fn new(blockchain_rid: Vec<u8>,
160        operations: Option<Vec<Operation>>,
161        signers: Option<Vec<Vec<u8>>>,
162        signatures: Option<Vec<Vec<u8>>>) -> Self {
163        Self {
164            blockchain_rid,
165            operations,
166            signers,
167            signatures,
168            ..Default::default()
169        }
170    }
171
172    /// Returns the hex-encoded GTV (Generic Tree Value) representation of the transaction.
173    /// 
174    /// This method encodes the transaction into GTV format and returns it as a
175    /// hexadecimal string.
176    /// 
177    /// # Returns
178    /// Hex-encoded string of the GTV-encoded transaction
179    pub fn gvt_hex_encoded(&self) -> String {
180        let gtv_e = gtv::encode_tx(self);
181        
182        hex::encode(gtv_e)
183    }
184
185    /// Computes the unique identifier (RID) of this transaction.
186    /// 
187    /// The transaction RID is computed by hashing the GTV representation
188    /// of the transaction using the GTX hash function.
189    /// 
190    /// # Returns
191    /// A fixed-size 32 bytes containing the transaction RID
192    pub fn tx_rid(&self) -> Result<[u8; 32], hasher::HashError> {
193        let to_draw_gtx = gtv::to_draw_gtx(self);
194        gtv_hash(to_draw_gtx, self.merkle_hash_version)
195    }
196
197    /// Returns the hex-encoded transaction RID.
198    /// 
199    /// This is a convenience method that returns the transaction RID
200    /// as a hexadecimal string.
201    /// 
202    /// # Returns
203    /// Hex-encoded string of the transaction RID
204    pub fn tx_rid_hex(&self) -> Result<String, hasher::HashError> {
205        Ok(hex::encode(self.tx_rid()?))
206    }
207
208    /// Signs the transaction using a raw private key string.
209    /// 
210    /// # Arguments
211    /// * `private_key` - Private key as a string
212    /// 
213    /// # Returns
214    /// Result indicating success or a secp256k1 error
215    /// 
216    /// # Errors
217    /// Returns an error if the private key is invalid or signing fails
218    pub fn sign_from_raw_priv_key(&mut self, private_key: &str) -> Result<(), secp256k1::Error> {
219        let private_key_bytes = Vec::from_hex(private_key).map_err(|_| secp256k1::Error::InvalidSecretKey)?;
220        let private_key = private_key_bytes.try_into().map_err(|_| secp256k1::Error::InvalidSecretKey)?;
221        self.sign(&private_key)
222    }
223
224    /// Signs the transaction with multiple raw private key strings.
225    ///
226    /// This method iteratively signs the transaction with each provided
227    /// private key string, enabling multi-signature transactions.
228    ///
229    /// # Arguments
230    /// * `private_keys` - Slice of raw private key strings
231    ///
232    /// # Returns
233    /// Result indicating success or a secp256k1 error
234    ///
235    /// # Errors
236    /// Returns an error if any private key is invalid or signing fails
237    pub fn multi_sign_from_raw_priv_keys(&mut self, private_keys: &[&str]) -> Result<(), secp256k1::Error> {
238        let private_keys_bytes: Vec<[u8; 32]> = private_keys
239            .iter()
240            .map(|private_key_hex| {
241                let private_key_bytes = Vec::from_hex(private_key_hex).map_err(|_| secp256k1::Error::InvalidSecretKey)?;
242                private_key_bytes.try_into().map_err(|_| secp256k1::Error::InvalidSecretKey)
243            })
244            .collect::<Result<Vec<[u8; 32]>, secp256k1::Error>>()?;
245
246        let private_keys_refs: Vec<&[u8; 32]> = private_keys_bytes.iter().collect();
247
248        self.multi_sign(private_keys_refs.as_slice())
249    }
250
251    /// Signs the transaction using a private key.
252    /// 
253    /// This method:
254    /// 1. Derives the public key from the private key
255    /// 2. Adds the public key to the signers list
256    /// 3. Signs the transaction RID
257    /// 4. Adds the signature to the signatures list
258    /// 
259    /// # Arguments
260    /// * `private_key` - 32-byte private key
261    /// 
262    /// # Returns
263    /// Result indicating success or a secp256k1 error
264    /// 
265    /// # Errors
266    /// Returns an error if the private key is invalid or signing fails
267    pub fn sign(&mut self, private_key: &[u8; 32]) -> Result<(), secp256k1::Error> {
268        let public_key = get_public_key(private_key)?;
269
270        self.signers.get_or_insert_with(Vec::new).push(public_key.to_vec());
271
272        let digest = self.tx_rid().map_err(|_| secp256k1::Error::InvalidMessage)?;
273        let signature = sign(&digest, private_key)?;
274
275        self.signatures.get_or_insert_with(Vec::new).push(signature.to_vec());
276
277        Ok(())
278    }
279
280    /// Signs the transaction with multiple private keys.
281    /// 
282    /// This method iteratively signs the transaction with each provided
283    /// private key, enabling multi-signature transactions.
284    /// 
285    /// # Arguments
286    /// * `private_keys` - Slice of 32-byte private keys
287    /// 
288    /// # Returns
289    /// Result indicating success or a secp256k1 error
290    /// 
291    /// # Errors
292    /// Returns an error if any private key is invalid or signing fails
293    pub fn multi_sign(&mut self, private_keys: &[&[u8; 32]]) -> Result<(), secp256k1::Error> {
294        let public_keys = get_public_keys(private_keys)?;
295
296        self.signers.get_or_insert_with(Vec::new).extend(public_keys.iter().map(|pk| pk.to_vec()));
297
298        let digest = self.tx_rid().map_err(|_| secp256k1::Error::InvalidMessage)?;
299
300        for private_key in private_keys {
301             let signature = sign(&digest, private_key)?;
302             self.signatures.get_or_insert_with(Vec::new).push(signature.to_vec());
303        }
304
305        Ok(())
306    }
307
308    /// Decodes a hexadecimal string representation of a transaction confirmation proof
309    /// into a `TransactionConfirmationProofData` struct.
310    ///
311    /// This function is used to parse the proof data received from the blockchain
312    /// to verify the inclusion of a transaction in a block. The input `proof`
313    /// is expected to be a hex-encoded GTV (Generic Tree Value) structure
314    /// representing the confirmation proof.
315    ///
316    /// # Arguments
317    /// * `proof` - A string slice containing the hex-encoded confirmation proof data.
318    ///
319    /// # Returns
320    /// A `Result` which is:
321    /// - `Ok(TransactionConfirmationProofData)` if the proof is successfully decoded and
322    ///   parsed into the `TransactionConfirmationProofData` struct.
323    /// - `Err(String)` if the input string is not valid hexadecimal, if the GTV
324    ///   decoding fails, or if the decoded GTV structure does not match the
325    ///   expected format for a `TransactionConfirmationProofData`.
326    ///
327    /// # Errors
328    /// This function will return an error string if:
329    /// - The `proof` string cannot be hex-decoded.
330    /// - The decoded bytes cannot be successfully GTV-decoded.
331    /// - The root of the GTV-decoded data is not a dictionary (`Op_Params::Dict`).
332    /// - Any required field (`blockHeader`, `hash`, `txIndex`, `witness`, `merkleProofTree`)
333    ///   is missing or has an incorrect type within the decoded GTV dictionary.
334    ///
335    /// # Examples
336    /// ```
337    /// use crate::utils::transaction::{Transaction, TransactionConfirmationProofData};
338    /// use crate::utils::operation::Params as Op_Params;
339    ///
340    /// let proof_hex_encoded_data = "A48203AA308203A6308201230C0B626C6F636B486561646572A18201120482010EA582010A30820106A12204207A37DD331AC8FED64EEFCCA231B0F975DE7F4371CE5CA44105A5B117DF6DE251A1220420BAB0B26A302920A56F7FFB9428FA52A264657594624F12C73B1510BEB76EBCE1A12204209423052CE47270FB5ADE54B30F662AAB476BF26314680CD716C0EC1484EF5C63A308020601979ADDCE2EA306020400A926E1A0020500A48181307F30310C0B636F6E6669675F68617368A1220420C9A490594951ACBB668F05FE83287DB48CDD628811F9F5D3083BF087686C3BD4301A0C136D65726B6C655F686173685F76657273696F6EA303020102302E0C077072696D617279A123042102DD859FE30F3C6102B364A5FDEB3C8C3DA2B22F4E541015C3BEFDA753EC672E8E302A0C0468617368A1220420796D019516EB32366BAA60F08E73A78C94BBDCF9ED3724017AED6E9FC729AF923081830C0F6D65726B6C6550726F6F6654726565A570306EA303020167A303020101A3030201F6A530302EA303020165A303020100A1220420796D019516EB32366BAA60F08E73A78C94BBDCF9ED3724017AED6E9FC729AF92A52B3029A303020164A12204200000000000000000000000000000000000000000000000000000000000000000300E0C077478496E646578A303020100308201B90C077769746E657373A18201AC048201A800000004000000210202F6F59D4F007C52FB84FAF3B3E02CF7B8F9C2A4B953618047DBA2C85A17854F00000040D056BADD7014B638DB4FF06E2D86D570FF1FE712B00833FCA9D175BC926502A7613A7CDD1DA50326F9AEA3BBF94CD4043191E02CE5A4F0D81071B14CF841FD770000002102EF6254CCADB304E39244858F3E506EF58816A2769E019AD11C35842862D981F80000004062E6FD188816B85538A76990E2EE943CBDC40C161CA98A87B5B070FEDF7946CF73A6BEFB5C3F0DC3F664F52D8A53C8B79C52ADC023276F9836739FE0301BABA70000002103C146E1860AACC77EBF3B5741D04CFFBC316B37921D4029CAF2479AF5F2D573EA00000040EAD69772A61F5FA1B5C71A977D98F88B57702A6CA005D39BD72CC5064FE1B48F3C49B1CECDE24F8F6620CF2CB679314477BD96644E717C4B2F657DC7F7EEB6FB0000002102DD859FE30F3C6102B364A5FDEB3C8C3DA2B22F4E541015C3BEFDA753EC672E8E00000040D994B3945F0AF229FBC7FB3A480CA10357E8F58076BB0F375CCE6044FE36996F4755F9B5C7AA11894DBDE9AFA734E05B4501614692480820A28D52DB04F577F7";
341    /// let result = Transaction::confirmation_proof(proof_hex_encoded_data);
342    ///
343    /// assert!(result.is_ok());
344    /// let proof_data = result.unwrap();
345    /// assert_eq!(proof_data.tx_index, 0);
346    /// // Further assertions can be made on other fields
347    /// ```
348    pub fn confirmation_proof(proof: &str) -> Result<TransactionConfirmationProofData, String> {
349        let hex_decode_data = hex::decode(proof).map_err(|_| "Invalid hex".to_string())?;
350        let result = gtv_decode(&hex_decode_data).map_err(|_| "GTV decode failed".to_string())?;
351
352        if let Op_Params::Dict(ref confirmation_proof) = result {
353            let block_header = extract_field!(confirmation_proof, "blockHeader", Op_Params::ByteArray, "blockHeader").to_vec();
354            let hash = extract_field!(confirmation_proof, "hash", Op_Params::ByteArray, "hash").to_vec();
355            let tx_index = *extract_field!(confirmation_proof, "txIndex", Op_Params::Integer, "txIndex");
356            let witness = extract_field!(confirmation_proof, "witness", Op_Params::ByteArray, "witness").to_vec();
357            let merkle_proof_tree = match confirmation_proof.get("merkleProofTree") {
358                Some(Op_Params::Array(arr)) => arr.clone(),
359                _ => return Err("Missing or invalid field: merkleProofTree".to_string()),
360            };
361
362            Ok(TransactionConfirmationProofData {
363                block_header,
364                hash,
365                tx_index,
366                witness,
367                merkle_proof_tree,
368            })
369        } else {
370            Err("Invalid proof data".to_string())
371        }
372    }
373
374    /// Creates a new `Transaction` (or `Self`) instance from a raw hexadecimal string.
375    ///
376    /// This function is responsible for decoding a hexadecimal string representing transaction
377    /// data into a structured format, likely using a custom Generalized Transaction Value (GTV)
378    /// encoding scheme. It extracts key components such as the blockchain's Root ID (RID),
379    /// operations, signers, and signatures from the decoded data.
380    ///
381    /// # Arguments
382    ///
383    /// * `tx` - A string slice (`&str`) containing the raw transaction data encoded in hexadecimal format.
384    ///
385    /// # Returns
386    ///
387    /// A `Result<Self, String>` which is:
388    /// - `Ok(Self)`: If the hexadecimal string is successfully decoded and parsed into a valid
389    ///   `Transaction` (or `Self`) instance.
390    /// - `Err(String)`: If any error occurs during the process, such as:
391    ///   - The input `tx` string is not valid hexadecimal.
392    ///   - The decoded data fails to conform to the expected GTV structure.
393    ///   - Specific components (like `block_chain_rid`, `operations`, `signers`, or `signatures`)
394    ///     are missing or are of an unexpected type within the GTV structure.
395    ///
396    /// # Errors
397    ///
398    /// This function can return an error string in the following scenarios:
399    /// - "Invalid hex": If `hex::decode` fails to parse the input `tx` string.
400    /// - "GTV decode failed": If `gtv_decode` encounters an error during the GTV deserialization process.
401    /// - Panics with "Unexpected signer type": (This is a current panic, ideally this would be
402    ///   converted to a `Result::Err` for robust error handling in a production system).
403    ///   This occurs if an element within the expected signers array is not a `ByteArray`.
404    /// - Other potential errors related to unexpected data structures within the GTV `result`
405    ///   (e.g., if `val2[0]` or `val2[2]` are not arrays as expected).
406    ///
407    /// # Examples
408    ///
409    /// ```rust
410    /// // Assuming `Transaction` is the type `Self` refers to, and `gtv_decode` and `hex` are available.
411    /// // Also assuming a valid hex string for a transaction.
412    ///
413    /// // Example of a successful decode (conceptual, as actual hex depends on your GTV structure)
414    /// let valid_hex_tx = "010203..."; // Replace with a real valid hex transaction string
415    /// match Transaction::from_raw_data(valid_hex_tx) {
416    ///     Ok(transaction) => {
417    ///         println!("Successfully decoded transaction: {:?}", transaction);
418    ///         // Further assertions or usage of the transaction object
419    ///     },
420    ///     Err(e) => {
421    ///         eprintln!("Failed to decode transaction: {}", e);
422    ///     }
423    /// }
424    ///
425    /// // Example of an invalid hex string
426    /// let invalid_hex_tx = "not_a_hex_string";
427    /// if let Err(e) = Transaction::from_raw_data(invalid_hex_tx) {
428    ///     assert_eq!(e, "Invalid hex");
429    /// }
430    ///
431    /// // Example of a hex string that decodes but has invalid GTV structure
432    /// let malformed_gtv_hex = "0a0b0c..."; // Replace with a hex string that causes GTV decode or structural errors
433    /// if let Err(e) = Transaction::from_raw_data(malformed_gtv_hex) {
434    ///     assert!(e.contains("GTV decode failed") || e.contains("Unexpected"));
435    /// }
436    /// ```
437    ///
438    /// # Panics
439    ///
440    /// This function currently panics if an element within the expected signers array is not
441    /// of the `OpParams::ByteArray` type. For production-grade code, this panic should
442    /// ideally be converted into a `Result::Err` for more graceful error handling.
443    ///
444    /// # Implementation Details
445    ///
446    /// 1. Decodes the input hexadecimal string `tx` into a byte vector.
447    /// 2. Deserializes the byte vector into a `Params` object using `gtv_decode`.
448    /// 3. Extracts the `block_chain_rid` from the first element of the main GTV array.
449    /// 4. Iterates through the third element of the main GTV array to parse `signers`,
450    ///    expecting each signer to be a `ByteArray`.
451    /// 5. (Further logic for `operations` and `signatures` would be described here if visible).
452    ///
453    /// This function relies on the `hex` crate for hexadecimal decoding and a custom
454    /// `gtv_decode` function for Generalized Transaction Value (GTV) deserialization.
455    /// The `OpParams` enum is crucial for interpreting the structure of the decoded GTV data.
456    pub fn from_raw_data(tx: &str) -> Result<Self, String> {
457        let hex_decode_data = hex::decode(tx).map_err(|_| "Invalid hex".to_string())?;
458        let result = gtv_decode(&hex_decode_data).map_err(|_| "GTV decode failed".to_string())?;
459
460        let mut blockchain_rid = vec![];
461        let mut operations: Vec<Operation> = vec![];
462        let mut signers: Option<Vec<Vec<u8>>> = None;
463        let mut signatures: Option<Vec<Vec<u8>>> = None;
464
465        if let Op_Params::Array(val) = result {
466            if let Op_Params::Array(val2) = &val[0] {
467                // Blockchain RID
468                blockchain_rid = val2[0].clone().to_vec();
469
470                // Signers
471                if let Op_Params::Array(val3) = &val2[2] {
472                    if !val3.is_empty() {
473                        signers = Some(val3.iter().map(|signer| signer.clone().to_vec()).collect());
474                    }
475                }
476
477                // Operations
478                if let Op_Params::Array(val3) = &val2[1]{
479                    for operation in val3 {
480                        if let Op_Params::Array(ops) = operation {
481                            operations.push(Operation::from_list(ops[0].clone().to_string(), ops[1].clone().into()));
482                        }
483                    }
484                }
485            }
486
487            // Signatures
488            if let Op_Params::Array(val2) = &val[1] {
489                if !val2.is_empty() {
490                    signatures = Some(val2.iter().map(|signature| signature.clone().to_vec()).collect())
491                }
492            }
493        }
494
495        Ok(Self {
496            blockchain_rid,
497            operations: Some(operations),
498            signers,
499            signatures,
500            ..Default::default()
501        })
502    }
503}
504
505/// Signs a message digest using ECDSA with secp256k1.
506/// 
507/// # Arguments
508/// * `digest` - 32-byte message digest to sign
509/// * `private_key` - 32-byte private key
510/// 
511/// # Returns
512/// Result containing the 64-byte signature or a secp256k1 error
513/// 
514/// # Errors
515/// Returns an error if the private key is invalid or signing fails
516fn sign(digest: &[u8; 32], private_key: &[u8; 32]) -> Result<[u8; 64], secp256k1::Error> {
517    let secp = Secp256k1::new();
518    let secret_key = SecretKey::from_slice(private_key)?;
519    let message = Message::from_digest(*digest);
520    let signature: Signature = secp.sign_ecdsa(message, &secret_key);
521    let serialized_signature = signature.serialize_compact();
522    Ok(serialized_signature)
523}
524
525/// Derives a public key from a private key using secp256k1.
526/// 
527/// # Arguments
528/// * `private_key` - 32-byte private key
529/// 
530/// # Returns
531/// Result containing the 33-byte compressed public key or a secp256k1 error
532/// 
533/// # Errors
534/// Returns an error if the private key is invalid
535fn get_public_key(private_key: &[u8; 32]) -> Result<[u8; 33], secp256k1::Error> {
536    let secp = Secp256k1::new();
537    let secret_key = SecretKey::from_slice(private_key)?;
538    let public_key = PublicKey::from_secret_key(&secp, &secret_key).serialize();
539    Ok(public_key)
540}
541
542/// Derives multiple public keys from a slice of private keys using secp256k1.
543///
544/// # Arguments
545/// * `private_keys` - Slice of 32-byte private keys
546///
547/// # Returns
548/// Result containing a vector of 33-byte compressed public keys or a secp256k1 error
549///
550/// # Errors
551/// Returns an error if any private key is invalid
552fn get_public_keys(private_keys: &[&[u8; 32]]) -> Result<Vec<[u8; 33]>, secp256k1::Error> {
553    let mut public_keys = Vec::new();
554
555    for private_key in private_keys {
556        let public_key = get_public_key(private_key)?;
557        public_keys.push(public_key);
558    }
559
560    Ok(public_keys)
561}
562
563#[test]
564fn test_confirmation_proof() {
565    let proof_hex_encoded_data = "A48203AA308203A6308201230C0B626C6F636B486561646572A18201120482010EA582010A30820106A12204207A37DD331AC8FED64EEFCCA231B0F975DE7F4371CE5CA44105A5B117DF6DE251A1220420BAB0B26A302920A56F7FFB9428FA52A264657594624F12C73B1510BEB76EBCE1A12204209423052CE47270FB5ADE54B30F662AAB476BF26314680CD716C0EC1484EF5C63A308020601979ADDCE2EA306020400A926E1A0020500A48181307F30310C0B636F6E6669675F68617368A1220420C9A490594951ACBB668F05FE83287DB48CDD628811F9F5D3083BF087686C3BD4301A0C136D65726B6C655F686173685F76657273696F6EA303020102302E0C077072696D617279A123042102DD859FE30F3C6102B364A5FDEB3C8C3DA2B22F4E541015C3BEFDA753EC672E8E302A0C0468617368A1220420796D019516EB32366BAA60F08E73A78C94BBDCF9ED3724017AED6E9FC729AF923081830C0F6D65726B6C6550726F6F6654726565A570306EA303020167A303020101A3030201F6A530302EA303020165A303020100A1220420796D019516EB32366BAA60F08E73A78C94BBDCF9ED3724017AED6E9FC729AF92A52B3029A303020164A12204200000000000000000000000000000000000000000000000000000000000000000300E0C077478496E646578A303020100308201B90C077769746E657373A18201AC048201A800000004000000210202F6F59D4F007C52FB84FAF3B3E02CF7B8F9C2A4B953618047DBA2C85A17854F00000040D056BADD7014B638DB4FF06E2D86D570FF1FE712B00833FCA9D175BC926502A7613A7CDD1DA50326F9AEA3BBF94CD4043191E02CE5A4F0D81071B14CF841FD770000002102EF6254CCADB304E39244858F3E506EF58816A2769E019AD11C35842862D981F80000004062E6FD188816B85538A76990E2EE943CBDC40C161CA98A87B5B070FEDF7946CF73A6BEFB5C3F0DC3F664F52D8A53C8B79C52ADC023276F9836739FE0301BABA70000002103C146E1860AACC77EBF3B5741D04CFFBC316B37921D4029CAF2479AF5F2D573EA00000040EAD69772A61F5FA1B5C71A977D98F88B57702A6CA005D39BD72CC5064FE1B48F3C49B1CECDE24F8F6620CF2CB679314477BD96644E717C4B2F657DC7F7EEB6FB0000002102DD859FE30F3C6102B364A5FDEB3C8C3DA2B22F4E541015C3BEFDA753EC672E8E00000040D994B3945F0AF229FBC7FB3A480CA10357E8F58076BB0F375CCE6044FE36996F4755F9B5C7AA11894DBDE9AFA734E05B4501614692480820A28D52DB04F577F7";
566    let result = Transaction::confirmation_proof(proof_hex_encoded_data).unwrap();
567
568    assert_eq!(result.tx_index, 0);
569
570    let block_header_data = crate::utils::transaction::gtv::decode(&result.block_header).unwrap();
571
572    if let crate::utils::operation::Params::Array(bhd) = block_header_data {
573        assert_eq!(bhd[0].clone().to_hex_encode(), "7a37dd331ac8fed64eefcca231b0f975de7f4371ce5ca44105a5b117df6de251");
574        assert_eq!(bhd[1].clone().to_hex_encode(), "bab0b26a302920a56f7ffb9428fa52a264657594624f12c73b1510beb76ebce1");
575        assert_eq!(bhd[2].clone().to_hex_encode(), "9423052ce47270fb5ade54b30f662aab476bf26314680cd716c0ec1484ef5c63");
576        if let crate::utils::operation::Params::Integer(int_val) = bhd[3] {
577          assert_eq!(int_val, 1750649916974);
578        }
579    }
580
581    assert_eq!(hex::encode(result.hash), "796d019516eb32366baa60f08e73a78c94bbdcf9ed3724017aed6e9fc729af92");
582    assert_eq!(hex::encode(result.witness), "00000004000000210202f6f59d4f007c52fb84faf3b3e02cf7b8f9c2a4b953618047dba2c85a17854f00000040d056badd7014b638db4ff06e2d86d570ff1fe712b00833fca9d175bc926502a7613a7cdd1da50326f9aea3bbf94cd4043191e02ce5a4f0d81071b14cf841fd770000002102ef6254ccadb304e39244858f3e506ef58816a2769e019ad11c35842862d981f80000004062e6fd188816b85538a76990e2ee943cbdc40c161ca98a87b5b070fedf7946cf73a6befb5c3f0dc3f664f52d8a53c8b79c52adc023276f9836739fe0301baba70000002103c146e1860aacc77ebf3b5741d04cffbc316b37921d4029caf2479af5f2d573ea00000040ead69772a61f5fa1b5c71a977d98f88b57702a6ca005d39bd72cc5064fe1b48f3c49b1cecde24f8f6620cf2cb679314477bd96644e717c4b2f657dc7f7eeb6fb0000002102dd859fe30f3c6102b364a5fdeb3c8c3da2b22f4e541015c3befda753ec672e8e00000040d994b3945f0af229fbc7fb3a480ca10357e8f58076bb0f375cce6044fe36996f4755f9b5c7aa11894dbde9afa734e05b4501614692480820a28d52db04f577f7");
583
584}
585
586#[tokio::test]
587async fn get_raw_transaction_data() {
588    use crate::utils::operation::Params;
589    use crate::transport::client::RestClient;
590    use bigdecimal::FromPrimitive;
591
592    let rc = RestClient{
593        node_url: vec!["https://system.chromaway.com"],
594        ..Default::default()
595    };
596
597    let blockchain_rid = "15C0CA99BEE60A3B23829968771C50E491BD00D2E3AE448580CD48A8D71E7BBA";
598    let tx_rid = "B5AE42A1645992D74E955A17D90F275778A19ADD3EB68A90EB0DD7225641A43A";
599
600    let result = rc.get_raw_transaction_data(blockchain_rid, tx_rid).await.unwrap();
601
602    let ft4_evm_auth_ids = [
603        "62a123cf432ec739d229d804b78a7c30d39ae29101247b1e4bfd1b20cc54cc43", // account_id
604        "cc01ed41ea21a8f516742ebbf0a3a9927c5de8816ed2f289f03e6f1db8a4e8bd" // auth_descriptor_id
605        ];
606
607    let ft4_evm_auth_signatures = vec![
608        Params::Array(vec![
609            Params::ByteArray(hex::decode("d98116e2cb881ce13f0b70255549ca29420ef478af888db25a8b957d802af71d").unwrap()),
610            Params::ByteArray(hex::decode("722750f833f40eccc701697835891f80fa90c1749b4eaaa059ec9c7a5017f977").unwrap()),
611            Params::Integer(27)
612        ])
613    ];
614
615    let nop = vec![
616        Params::ByteArray(hex::decode("93C0AC18E20D1BACF3189E5BDE2F4B0F1367FC4CDFC2A3EB7168E17073EAE584").unwrap())
617    ];
618
619    let eif_hbridge_bridge_ft4_token_to_evm = vec![
620        Params::Integer(1), // network_id
621        Params::ByteArray(hex::decode("5f16d1545a0881f971b164f1601cbbf51c29efd0633b2730da18c403c3b428b5").unwrap()), // asset_id
622        Params::BigInteger(num_bigint::BigInt::from_i128(63080704247).unwrap()), // amount
623        Params::ByteArray(hex::decode("d1941a115536b619c4f528432237d35f79544cfe").unwrap()), // beneficiary
624    ];
625
626    if let Some(operations) = result.operations {
627        for op in operations {
628            if let Some("ft4.evm_auth") = op.operation_name.as_deref() {
629                if let Some(ref val) = op.list {
630                    for item in val {
631                        if let Params::ByteArray(val2) = item {
632                            assert!(ft4_evm_auth_ids.contains(&hex::encode(val2).as_str()));
633                        }
634                        if let Params::Array(val2) = item {
635                            assert_eq!(val2, &ft4_evm_auth_signatures);
636                        }
637                    }
638                }
639            }
640            if let Some("eif.hbridge.bridge_ft4_token_to_evm") = op.operation_name.as_deref() {
641                if let Some(ref val) = op.list {
642                    assert_eq!(val, &eif_hbridge_bridge_ft4_token_to_evm);
643                }
644            }
645            if let Some("nop") = op.operation_name.as_deref() {
646                assert_eq!(op.list, Some(nop.clone()));
647            }
648        }
649    }
650}