2025-07-15 15:48:59 -03:00
|
|
|
use k256::{
|
|
|
|
|
ecdsa::{
|
2025-07-21 17:50:08 -03:00
|
|
|
signature::hazmat::{PrehashSigner, PrehashVerifier},
|
2025-07-15 15:48:59 -03:00
|
|
|
Signature, SigningKey, VerifyingKey,
|
|
|
|
|
},
|
|
|
|
|
EncodedPoint, Scalar,
|
|
|
|
|
};
|
2024-12-30 07:35:05 +01:00
|
|
|
use log::info;
|
2025-07-14 09:37:00 -03:00
|
|
|
use secp256k1_zkp::{PedersenCommitment, Tweak};
|
2025-07-15 15:48:59 -03:00
|
|
|
use serde::de::{Error as DeError, Visitor};
|
|
|
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
|
|
|
|
2024-12-22 16:14:52 +02:00
|
|
|
use sha2::{digest::FixedOutput, Digest};
|
2025-07-15 15:48:59 -03:00
|
|
|
use std::fmt;
|
2024-10-10 14:09:31 +03:00
|
|
|
|
2024-10-14 13:12:59 +03:00
|
|
|
use crate::merkle_tree_public::TreeHashType;
|
2024-10-10 14:09:31 +03:00
|
|
|
|
2024-12-22 16:14:52 +02:00
|
|
|
use elliptic_curve::{
|
|
|
|
|
consts::{B0, B1},
|
|
|
|
|
generic_array::GenericArray,
|
|
|
|
|
};
|
|
|
|
|
use sha2::digest::typenum::{UInt, UTerm};
|
|
|
|
|
|
2025-07-14 09:37:00 -03:00
|
|
|
use crate::TransactionSignatureError;
|
|
|
|
|
|
2024-12-22 16:14:52 +02:00
|
|
|
pub type CipherText = Vec<u8>;
|
|
|
|
|
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
|
2025-03-28 17:12:28 +02:00
|
|
|
pub type Tag = u8;
|
2024-12-22 16:14:52 +02:00
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
2024-11-28 22:05:14 +02:00
|
|
|
pub enum TxKind {
|
|
|
|
|
Public,
|
|
|
|
|
Private,
|
|
|
|
|
Shielded,
|
|
|
|
|
Deshielded,
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
2024-10-10 14:09:31 +03:00
|
|
|
///General transaction object
|
2025-07-14 08:45:38 -03:00
|
|
|
pub struct TransactionBody {
|
2024-11-28 22:05:14 +02:00
|
|
|
pub tx_kind: TxKind,
|
|
|
|
|
///Tx input data (public part)
|
|
|
|
|
pub execution_input: Vec<u8>,
|
|
|
|
|
///Tx output data (public_part)
|
|
|
|
|
pub execution_output: Vec<u8>,
|
2024-12-22 16:14:52 +02:00
|
|
|
///Tx input utxo commitments
|
|
|
|
|
pub utxo_commitments_spent_hashes: Vec<TreeHashType>,
|
2024-11-28 22:05:14 +02:00
|
|
|
///Tx output utxo commitments
|
|
|
|
|
pub utxo_commitments_created_hashes: Vec<TreeHashType>,
|
|
|
|
|
///Tx output nullifiers
|
|
|
|
|
pub nullifier_created_hashes: Vec<TreeHashType>,
|
2024-11-29 12:28:08 +02:00
|
|
|
///Execution proof (private part)
|
|
|
|
|
pub execution_proof_private: String,
|
2024-12-22 16:14:52 +02:00
|
|
|
///Encoded blobs of data
|
2025-03-28 17:12:28 +02:00
|
|
|
pub encoded_data: Vec<(CipherText, Vec<u8>, Tag)>,
|
2024-12-25 09:50:54 +02:00
|
|
|
///Transaction senders ephemeral pub key
|
|
|
|
|
pub ephemeral_pub_key: Vec<u8>,
|
2025-04-09 01:28:56 -04:00
|
|
|
///Public (Pedersen) commitment
|
|
|
|
|
pub commitment: Vec<PedersenCommitment>,
|
|
|
|
|
///tweak
|
|
|
|
|
pub tweak: Tweak,
|
|
|
|
|
///secret_r
|
|
|
|
|
pub secret_r: [u8; 32],
|
2025-04-24 15:51:34 +03:00
|
|
|
///Hex-encoded address of a smart contract account called
|
|
|
|
|
pub sc_addr: String,
|
|
|
|
|
///Recorded changes in state of smart contract
|
|
|
|
|
///
|
|
|
|
|
/// First value represents vector of changes, second is new length of a state
|
|
|
|
|
pub state_changes: (serde_json::Value, usize),
|
2024-12-22 16:14:52 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-03 12:43:05 +02:00
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct MintMoneyPublicTx {
|
|
|
|
|
pub acc: [u8; 32],
|
|
|
|
|
pub amount: u128,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct SendMoneyShieldedTx {
|
|
|
|
|
pub acc_sender: [u8; 32],
|
|
|
|
|
pub amount: u128,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct SendMoneyDeshieldedTx {
|
|
|
|
|
pub receiver_data: Vec<(u128, [u8; 32])>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct OwnedUTXO {
|
|
|
|
|
pub hash: [u8; 32],
|
|
|
|
|
pub owner: [u8; 32],
|
|
|
|
|
pub amount: u128,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct OwnedUTXOForPublication {
|
|
|
|
|
pub hash: String,
|
|
|
|
|
pub owner: String,
|
|
|
|
|
pub amount: u128,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<OwnedUTXO> for OwnedUTXOForPublication {
|
|
|
|
|
fn from(value: OwnedUTXO) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
hash: hex::encode(value.hash),
|
|
|
|
|
owner: hex::encode(value.owner),
|
|
|
|
|
amount: value.amount,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct UTXOPublication {
|
|
|
|
|
pub utxos: Vec<OwnedUTXO>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub enum ActionData {
|
|
|
|
|
MintMoneyPublicTx(MintMoneyPublicTx),
|
|
|
|
|
SendMoneyShieldedTx(SendMoneyShieldedTx),
|
|
|
|
|
SendMoneyDeshieldedTx(SendMoneyDeshieldedTx),
|
|
|
|
|
UTXOPublication(UTXOPublication),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActionData {
|
|
|
|
|
pub fn into_hexed_print(self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
ActionData::MintMoneyPublicTx(action) => {
|
|
|
|
|
format!(
|
|
|
|
|
"Account {:?} minted {:?} balance",
|
|
|
|
|
hex::encode(action.acc),
|
|
|
|
|
action.amount
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
ActionData::SendMoneyDeshieldedTx(action) => {
|
|
|
|
|
format!(
|
|
|
|
|
"Receivers receipt {:?}",
|
|
|
|
|
action
|
|
|
|
|
.receiver_data
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(amount, rec)| (amount, hex::encode(rec)))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
ActionData::SendMoneyShieldedTx(action) => {
|
|
|
|
|
format!(
|
|
|
|
|
"Shielded send from {:?} for {:?} balance",
|
|
|
|
|
hex::encode(action.acc_sender),
|
|
|
|
|
action.amount
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
ActionData::UTXOPublication(action) => {
|
|
|
|
|
let pub_own_utxo: Vec<OwnedUTXOForPublication> = action
|
|
|
|
|
.utxos
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|owned_utxo| owned_utxo.into())
|
|
|
|
|
.collect();
|
|
|
|
|
format!("Published utxos {:?}", pub_own_utxo)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 08:45:38 -03:00
|
|
|
impl TransactionBody {
|
2025-07-10 12:52:48 -03:00
|
|
|
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
|
2025-07-10 12:15:37 -03:00
|
|
|
pub fn hash(&self) -> TreeHashType {
|
2025-07-10 13:00:27 -03:00
|
|
|
// TODO: Remove `unwrap` by implementing a `to_bytes` method
|
|
|
|
|
// that deterministically encodes all transaction fields to bytes
|
|
|
|
|
// and guarantees serialization will succeed.
|
2025-07-10 12:15:37 -03:00
|
|
|
let raw_data = serde_json::to_vec(&self).unwrap();
|
|
|
|
|
let mut hasher = sha2::Sha256::new();
|
|
|
|
|
hasher.update(&raw_data);
|
|
|
|
|
TreeHashType::from(hasher.finalize_fixed())
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 07:35:05 +01:00
|
|
|
pub fn log(&self) {
|
2025-07-10 11:35:46 -03:00
|
|
|
info!("Transaction hash is {:?}", hex::encode(self.hash()));
|
2024-12-30 07:35:05 +01:00
|
|
|
info!("Transaction tx_kind is {:?}", self.tx_kind);
|
2025-01-03 12:43:05 +02:00
|
|
|
info!("Transaction execution_input is {:?}", {
|
|
|
|
|
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_input) {
|
|
|
|
|
action.into_hexed_print()
|
|
|
|
|
} else {
|
|
|
|
|
"".to_string()
|
2025-01-03 10:44:06 +02:00
|
|
|
}
|
2025-01-03 12:43:05 +02:00
|
|
|
});
|
|
|
|
|
info!("Transaction execution_output is {:?}", {
|
|
|
|
|
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_output) {
|
|
|
|
|
action.into_hexed_print()
|
|
|
|
|
} else {
|
|
|
|
|
"".to_string()
|
2025-01-03 10:44:06 +02:00
|
|
|
}
|
2025-01-03 12:43:05 +02:00
|
|
|
});
|
2024-12-30 09:10:04 +02:00
|
|
|
info!(
|
|
|
|
|
"Transaction utxo_commitments_spent_hashes is {:?}",
|
|
|
|
|
self.utxo_commitments_spent_hashes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|val| hex::encode(val.clone()))
|
2025-01-03 08:13:59 +02:00
|
|
|
.collect::<Vec<_>>()
|
2024-12-30 09:10:04 +02:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Transaction utxo_commitments_created_hashes is {:?}",
|
|
|
|
|
self.utxo_commitments_created_hashes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|val| hex::encode(val.clone()))
|
2025-01-03 08:13:59 +02:00
|
|
|
.collect::<Vec<_>>()
|
2024-12-30 09:10:04 +02:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Transaction nullifier_created_hashes is {:?}",
|
|
|
|
|
self.nullifier_created_hashes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|val| hex::encode(val.clone()))
|
2025-01-03 08:13:59 +02:00
|
|
|
.collect::<Vec<_>>()
|
2024-12-30 09:10:04 +02:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Transaction encoded_data is {:?}",
|
|
|
|
|
self.encoded_data
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone())))
|
2025-01-03 08:13:59 +02:00
|
|
|
.collect::<Vec<_>>()
|
2024-12-30 09:10:04 +02:00
|
|
|
);
|
|
|
|
|
info!(
|
|
|
|
|
"Transaction ephemeral_pub_key is {:?}",
|
|
|
|
|
hex::encode(self.ephemeral_pub_key.clone())
|
|
|
|
|
);
|
2024-12-30 07:35:05 +01:00
|
|
|
}
|
2024-12-30 09:10:04 +02:00
|
|
|
}
|
2025-07-10 12:09:01 -03:00
|
|
|
|
2025-07-16 10:04:23 -03:00
|
|
|
type TransactionHash = [u8; 32];
|
2025-07-15 15:48:59 -03:00
|
|
|
pub type TransactionSignature = Signature;
|
2025-07-16 10:04:23 -03:00
|
|
|
pub type SignaturePublicKey = VerifyingKey;
|
|
|
|
|
pub type SignaturePrivateKey = SigningKey;
|
2025-07-14 09:37:00 -03:00
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
/// A container for a transaction body with a signature.
|
2025-07-14 09:37:00 -03:00
|
|
|
/// Meant to be sent through the network to the sequencer
|
2025-07-16 11:36:20 -03:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct Transaction {
|
2025-07-16 11:54:11 -03:00
|
|
|
body: TransactionBody,
|
2025-07-16 10:04:23 -03:00
|
|
|
signature: TransactionSignature,
|
|
|
|
|
public_key: VerifyingKey,
|
2025-07-14 09:37:00 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
impl Transaction {
|
2025-07-16 11:54:11 -03:00
|
|
|
/// Returns a new transaction signed with the provided `private_key`.
|
|
|
|
|
/// The signature is generated over the hash of the body as computed by `body.hash()`
|
2025-07-16 11:36:20 -03:00
|
|
|
pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction {
|
2025-07-15 15:48:59 -03:00
|
|
|
let hash = body.hash();
|
2025-07-21 17:50:08 -03:00
|
|
|
let signature: TransactionSignature = private_key.sign_prehash(&hash).unwrap();
|
2025-07-15 15:48:59 -03:00
|
|
|
let public_key = VerifyingKey::from(&private_key);
|
|
|
|
|
Self {
|
|
|
|
|
body,
|
2025-07-16 10:04:23 -03:00
|
|
|
signature,
|
|
|
|
|
public_key,
|
2025-07-15 15:48:59 -03:00
|
|
|
}
|
2025-07-14 09:37:00 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
/// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature.
|
|
|
|
|
/// Returns an error if the signature verification fails.
|
2025-07-14 09:37:00 -03:00
|
|
|
pub fn into_authenticated(self) -> Result<AuthenticatedTransaction, TransactionSignatureError> {
|
|
|
|
|
let hash = self.body.hash();
|
2025-07-15 15:48:59 -03:00
|
|
|
|
2025-07-16 10:04:23 -03:00
|
|
|
self.public_key
|
2025-07-21 17:50:08 -03:00
|
|
|
.verify_prehash(&hash, &self.signature)
|
2025-07-15 15:48:59 -03:00
|
|
|
.map_err(|_| TransactionSignatureError::InvalidSignature)?;
|
|
|
|
|
|
|
|
|
|
Ok(AuthenticatedTransaction {
|
|
|
|
|
hash,
|
2025-07-16 11:36:20 -03:00
|
|
|
transaction: self,
|
2025-07-15 15:48:59 -03:00
|
|
|
})
|
2025-07-14 08:45:38 -03:00
|
|
|
}
|
2025-07-16 11:54:11 -03:00
|
|
|
|
|
|
|
|
/// Returns the body of the transaction
|
|
|
|
|
pub fn body(&self) -> &TransactionBody {
|
|
|
|
|
&self.body
|
|
|
|
|
}
|
2025-07-14 08:45:38 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-14 09:37:00 -03:00
|
|
|
/// A transaction with a valid signature over the hash of its body.
|
2025-07-16 11:36:20 -03:00
|
|
|
/// Can only be constructed from an `Transaction`
|
2025-07-14 09:37:00 -03:00
|
|
|
/// if the signature is valid
|
2025-07-16 11:36:20 -03:00
|
|
|
#[derive(Debug, Clone)]
|
2025-07-14 09:37:00 -03:00
|
|
|
pub struct AuthenticatedTransaction {
|
|
|
|
|
hash: TransactionHash,
|
2025-07-16 11:36:20 -03:00
|
|
|
transaction: Transaction,
|
2025-07-14 09:37:00 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-14 08:45:38 -03:00
|
|
|
impl AuthenticatedTransaction {
|
2025-07-16 11:54:11 -03:00
|
|
|
/// Returns the underlying transaction
|
|
|
|
|
pub fn transaction(&self) -> &Transaction {
|
2025-07-16 11:36:20 -03:00
|
|
|
&self.transaction
|
2025-07-14 08:45:38 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
/// Returns the precomputed hash over the body of the transaction
|
2025-07-14 09:37:00 -03:00
|
|
|
pub fn hash(&self) -> &TransactionHash {
|
|
|
|
|
&self.hash
|
2025-07-10 14:53:25 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-10 12:09:01 -03:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2025-07-16 11:36:20 -03:00
|
|
|
use super::*;
|
2025-07-21 17:50:08 -03:00
|
|
|
use k256::{ecdsa::signature::Signer, FieldBytes};
|
2025-07-10 12:09:01 -03:00
|
|
|
use secp256k1_zkp::{constants::SECRET_KEY_SIZE, Tweak};
|
|
|
|
|
use sha2::{digest::FixedOutput, Digest};
|
|
|
|
|
|
2025-07-10 12:16:05 -03:00
|
|
|
use crate::{
|
|
|
|
|
merkle_tree_public::TreeHashType,
|
2025-07-16 11:36:20 -03:00
|
|
|
transaction::{Transaction, TransactionBody, TxKind},
|
2025-07-10 12:16:05 -03:00
|
|
|
};
|
2025-07-10 12:09:01 -03:00
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
fn test_transaction_body() -> TransactionBody {
|
|
|
|
|
TransactionBody {
|
2025-07-10 12:09:01 -03:00
|
|
|
tx_kind: TxKind::Public,
|
|
|
|
|
execution_input: vec![1, 2, 3, 4],
|
|
|
|
|
execution_output: vec![5, 6, 7, 8],
|
|
|
|
|
utxo_commitments_spent_hashes: vec![[9; 32], [10; 32], [11; 32], [12; 32]],
|
|
|
|
|
utxo_commitments_created_hashes: vec![[13; 32]],
|
|
|
|
|
nullifier_created_hashes: vec![[0; 32], [1; 32], [2; 32], [3; 32]],
|
|
|
|
|
execution_proof_private: "loremipsum".to_string(),
|
2025-07-10 12:16:05 -03:00
|
|
|
encoded_data: vec![(vec![255, 255, 255], vec![254, 254, 254], 1)],
|
2025-07-10 12:09:01 -03:00
|
|
|
ephemeral_pub_key: vec![5; 32],
|
|
|
|
|
commitment: vec![],
|
|
|
|
|
tweak: Tweak::from_slice(&[7; SECRET_KEY_SIZE]).unwrap(),
|
|
|
|
|
secret_r: [8; 32],
|
|
|
|
|
sc_addr: "someAddress".to_string(),
|
|
|
|
|
state_changes: (serde_json::Value::Null, 10),
|
2025-07-16 11:36:20 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
fn test_transaction() -> Transaction {
|
|
|
|
|
let body = test_transaction_body();
|
|
|
|
|
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
|
|
|
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
|
|
|
Transaction::new(body, private_key)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
#[test]
|
|
|
|
|
fn test_transaction_hash_is_sha256_of_json_bytes() {
|
|
|
|
|
let body = test_transaction_body();
|
2025-07-10 12:09:01 -03:00
|
|
|
let expected_hash = {
|
2025-07-16 11:36:20 -03:00
|
|
|
let data = serde_json::to_vec(&body).unwrap();
|
2025-07-10 12:09:01 -03:00
|
|
|
let mut hasher = sha2::Sha256::new();
|
|
|
|
|
hasher.update(&data);
|
|
|
|
|
TreeHashType::from(hasher.finalize_fixed())
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
let hash = body.hash();
|
2025-07-10 12:09:01 -03:00
|
|
|
|
|
|
|
|
assert_eq!(expected_hash, hash);
|
|
|
|
|
}
|
2025-07-16 11:36:20 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_transaction_constructor() {
|
|
|
|
|
let body = test_transaction_body();
|
|
|
|
|
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
|
|
|
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
|
|
|
let transaction = Transaction::new(body.clone(), private_key.clone());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
transaction.public_key,
|
|
|
|
|
SignaturePublicKey::from(&private_key)
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(transaction.body, body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-07-16 11:54:11 -03:00
|
|
|
fn test_transaction_body_getter() {
|
2025-07-16 11:36:20 -03:00
|
|
|
let body = test_transaction_body();
|
|
|
|
|
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
|
|
|
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
2025-07-16 11:54:11 -03:00
|
|
|
let transaction = Transaction::new(body.clone(), private_key.clone());
|
|
|
|
|
assert_eq!(transaction.body(), &body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_into_authenticated_succeeds_for_valid_signature() {
|
|
|
|
|
let transaction = test_transaction();
|
2025-07-16 11:36:20 -03:00
|
|
|
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
let signature = authenticated_tx.transaction().signature;
|
2025-07-16 11:36:20 -03:00
|
|
|
let hash = authenticated_tx.hash();
|
|
|
|
|
|
2025-07-16 11:54:11 -03:00
|
|
|
assert_eq!(authenticated_tx.transaction(), &transaction);
|
|
|
|
|
assert_eq!(hash, &transaction.body.hash());
|
2025-07-16 11:36:20 -03:00
|
|
|
assert!(authenticated_tx
|
2025-07-16 11:54:11 -03:00
|
|
|
.transaction()
|
2025-07-16 11:36:20 -03:00
|
|
|
.public_key
|
2025-07-21 17:50:08 -03:00
|
|
|
.verify_prehash(hash, &signature)
|
2025-07-16 11:36:20 -03:00
|
|
|
.is_ok());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_into_authenticated_fails_for_invalid_signature() {
|
|
|
|
|
let body = test_transaction_body();
|
|
|
|
|
let key_bytes = FieldBytes::from_slice(&[37; 32]);
|
|
|
|
|
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
|
|
|
|
|
let transaction = {
|
|
|
|
|
let mut this = Transaction::new(body, private_key.clone());
|
|
|
|
|
// Modify the signature to make it invalid
|
|
|
|
|
// We do this by changing it to the signature of something else
|
|
|
|
|
this.signature = private_key.sign(b"deadbeef");
|
|
|
|
|
this
|
|
|
|
|
};
|
2025-07-16 11:54:11 -03:00
|
|
|
|
2025-07-16 11:36:20 -03:00
|
|
|
matches!(
|
|
|
|
|
transaction.into_authenticated(),
|
|
|
|
|
Err(TransactionSignatureError::InvalidSignature)
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-16 11:54:11 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_authenticated_transaction_getter() {
|
|
|
|
|
let transaction = test_transaction();
|
|
|
|
|
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
|
|
|
assert_eq!(authenticated_tx.transaction(), &transaction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_authenticated_transaction_hash_getter() {
|
|
|
|
|
let transaction = test_transaction();
|
|
|
|
|
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
|
|
|
|
|
assert_eq!(authenticated_tx.hash(), &transaction.body.hash());
|
|
|
|
|
}
|
2025-07-10 12:16:05 -03:00
|
|
|
}
|