lssa/common/src/transaction.rs

424 lines
14 KiB
Rust
Raw Normal View History

use k256::{
ecdsa::{
2025-07-21 17:50:08 -03:00
signature::hazmat::{PrehashSigner, PrehashVerifier},
Signature, SigningKey, VerifyingKey,
},
EncodedPoint, Scalar,
};
use log::info;
2025-07-14 09:37:00 -03:00
use secp256k1_zkp::{PedersenCommitment, Tweak};
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};
use std::fmt;
2024-10-10 14:09:31 +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>>;
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)]
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
pub struct TransactionBody {
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>,
///Tx output utxo commitments
pub utxo_commitments_created_hashes: Vec<TreeHashType>,
///Tx output nullifiers
pub nullifier_created_hashes: Vec<TreeHashType>,
///Execution proof (private part)
pub execution_proof_private: String,
2024-12-22 16:14:52 +02:00
///Encoded blobs of data
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>,
///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)
}
}
}
}
impl TransactionBody {
2025-07-10 12:52:48 -03:00
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
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.
let raw_data = serde_json::to_vec(&self).unwrap();
let mut hasher = sha2::Sha256::new();
hasher.update(&raw_data);
TreeHashType::from(hasher.finalize_fixed())
}
pub fn log(&self) {
info!("Transaction hash is {:?}", hex::encode(self.hash()));
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 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];
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
/// 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 {
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 {
/// 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 {
let hash = body.hash();
2025-07-21 17:50:08 -03:00
let signature: TransactionSignature = private_key.sign_prehash(&hash).unwrap();
let public_key = VerifyingKey::from(&private_key);
Self {
body,
2025-07-16 10:04:23 -03:00
signature,
public_key,
}
2025-07-14 09:37:00 -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-16 10:04:23 -03:00
self.public_key
2025-07-21 17:50:08 -03:00
.verify_prehash(&hash, &self.signature)
.map_err(|_| TransactionSignatureError::InvalidSignature)?;
Ok(AuthenticatedTransaction {
hash,
2025-07-16 11:36:20 -03:00
transaction: self,
})
}
/// Returns the body of the transaction
pub fn body(&self) -> &TransactionBody {
&self.body
}
}
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
}
impl AuthenticatedTransaction {
/// Returns the underlying transaction
pub fn transaction(&self) -> &Transaction {
2025-07-16 11:36:20 -03:00
&self.transaction
}
/// 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 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
}
}
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]
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();
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();
let signature = authenticated_tx.transaction().signature;
2025-07-16 11:36:20 -03:00
let hash = authenticated_tx.hash();
assert_eq!(authenticated_tx.transaction(), &transaction);
assert_eq!(hash, &transaction.body.hash());
2025-07-16 11:36:20 -03:00
assert!(authenticated_tx
.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:36:20 -03:00
matches!(
transaction.into_authenticated(),
Err(TransactionSignatureError::InvalidSignature)
);
}
#[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
}