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}