From 83c5a31675d9d2d127e7cf549a18075369477f6c Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Thu, 27 Mar 2025 02:50:43 +0800 Subject: [PATCH 01/14] feat: integrate HashiCorp Vault for mnemonic key management --- Cargo.toml | 9 ++++- src/main.rs | 12 +++++- src/vault/hashicorp.rs | 50 +++++++++++++++++++++++++ src/vault/key/mod.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/vault/mod.rs | 29 +++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/vault/hashicorp.rs create mode 100644 src/vault/key/mod.rs create mode 100644 src/vault/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 176db9f..d64ff17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ futures-util = "0.3.31" gasket = { git = "https://github.com/construkts/gasket-rs.git", features = ["derive"] } hex = "0.4.3" itertools = "0.14.0" -pallas = { git = "https://github.com/txpipe/pallas.git", features = ["phase-two"] } +pallas = { git = "https://github.com/txpipe/pallas.git", features = ["phase-two", "wallet"] } protoc-wkt = "1.0.0" serde = { version = "1.0.217", features = ["derive"] } thiserror = "2.0.11" @@ -39,3 +39,10 @@ async-stream = "0.3.6" tokio-stream = "0.1.17" rand = "0.9.0" prost = "0.13.5" +vaultrs = "0.7.4" +bip39 = "2.1.0" + +[workspace] +members = [ + "examples/tx_signing" +] diff --git a/src/main.rs b/src/main.rs index a8896a0..4085d85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,9 @@ use queue::{chaining::TxChaining, DEFAULT_QUEUE}; use serde::Deserialize; use storage::sqlite::{SqliteCursor, SqliteStorage, SqliteTransaction}; use tokio::try_join; -use tracing::Level; +use tracing::{info, Level}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; +use vault::VaultAdapter; mod ledger; mod network; @@ -18,6 +19,7 @@ mod queue; mod server; mod storage; mod validation; +mod vault; #[tokio::main] async fn main() -> Result<()> { @@ -44,6 +46,13 @@ async fn main() -> Result<()> { config.clone().queues, )); + let vault = vault::hashicorp::HashicorpVaultClient::new(config.vault.clone())?; + vault.store_key().await?; + info!("Vault key stored"); + + let mnemonic = vault.retrieve_key().await?; + info!("Retrieved mnemonic: {}", mnemonic); + let cursor_storage = Arc::new(SqliteCursor::new(Arc::clone(&storage))); let cursor = cursor_storage.current().await?.map(|c| c.into()); @@ -76,6 +85,7 @@ struct Config { #[serde(default)] queues: HashSet, u5c: ledger::u5c::Config, + vault: vault::Config, } impl Config { diff --git a/src/vault/hashicorp.rs b/src/vault/hashicorp.rs new file mode 100644 index 0000000..d701f74 --- /dev/null +++ b/src/vault/hashicorp.rs @@ -0,0 +1,50 @@ +use bip39::Mnemonic; +use serde::{Deserialize, Serialize}; +use vaultrs::{ + client::{VaultClient, VaultClientSettingsBuilder}, + kv2, +}; + +use super::{key, Config as VaultConfig, VaultAdapter, VaultError}; + +pub struct HashicorpVaultClient { + client: VaultClient, + config: VaultConfig, +} + +impl HashicorpVaultClient { + pub fn new(config: VaultConfig) -> Result { + let client_settings = VaultClientSettingsBuilder::default() + .address(&config.api_addr) + .token(&config.token) + .build() + .map_err(|e| VaultError::ConfigError(e.to_string()))?; + + let client = VaultClient::new(client_settings).map_err(VaultError::ClientError)?; + + Ok(Self { client, config }) + } +} + +#[async_trait::async_trait] +impl VaultAdapter for HashicorpVaultClient { + async fn store_key(&self) -> Result<(), VaultError> { + let mnemonic = key::generate_mnemonic(); + let secret = Secret { mnemonic }; + + kv2::set(&self.client, &self.config.mount, &self.config.path, &secret).await?; + + Ok(()) + } + + async fn retrieve_key(&self) -> Result { + let secret: Secret = kv2::read(&self.client, &self.config.mount, &self.config.path).await?; + + Ok(secret.mnemonic) + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct Secret { + mnemonic: Mnemonic, +} diff --git a/src/vault/key/mod.rs b/src/vault/key/mod.rs new file mode 100644 index 0000000..636c0b4 --- /dev/null +++ b/src/vault/key/mod.rs @@ -0,0 +1,83 @@ +use bip39::{rand_core::OsRng, Mnemonic}; +use pallas::{ + crypto::{hash::Hasher, key::ed25519::PublicKey}, + ledger::addresses::{ + Address, Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart, + }, + wallet::keystore::{ + hd::{Bip32PrivateKey, Bip32PublicKey}, + PrivateKey, + }, +}; + +pub fn generate_mnemonic() -> Mnemonic { + Bip32PrivateKey::generate_with_mnemonic(OsRng, "".into()).1 +} + +/// @TODO remove dead code after finalizing POC +#[allow(dead_code)] +pub fn generate_account_key(mnemonic: &Mnemonic) -> Bip32PrivateKey { + let root_key = Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), "".into()).unwrap(); + root_key + .derive(1852 | 0x80000000) + .derive(1815 | 0x80000000) + .derive(0x80000000) +} + +#[allow(dead_code)] +pub fn generate_payment_keypair( + account_key: &Bip32PrivateKey, +) -> (Bip32PrivateKey, Bip32PublicKey) { + let external_key = account_key.derive(0); + let private_key = external_key.derive(0); + let public_key = private_key.to_public(); + (private_key, public_key) +} + +#[allow(dead_code)] +pub fn generate_delegation_keypair( + account_key: &Bip32PrivateKey, +) -> (Bip32PrivateKey, Bip32PublicKey) { + let delegation_key = account_key.derive(2); + let private_key = delegation_key.derive(0); + let public_key = private_key.to_public(); + (private_key, public_key) +} + +#[allow(dead_code)] +pub fn generate_address(public_key: &Bip32PublicKey) -> Address { + let payment_hash = Hasher::<224>::hash(&public_key.as_bytes()[..32]); + let address = ShelleyAddress::new( + Network::Testnet, + ShelleyPaymentPart::key_hash(payment_hash), + ShelleyDelegationPart::Null, + ); + + Address::Shelley(address) +} + +#[allow(dead_code)] +pub fn generate_address_with_delegation( + account_key: &Bip32PrivateKey, + public_key: &Bip32PublicKey, +) -> Address { + let payment_hash = Hasher::<224>::hash(public_key.as_bytes().as_slice()); + + let (_, public_key) = generate_delegation_keypair(account_key); + let delegation_hash = Hasher::<224>::hash(public_key.as_bytes().as_slice()); + + let address = ShelleyAddress::new( + Network::Testnet, + ShelleyPaymentPart::key_hash(payment_hash), + ShelleyDelegationPart::key_hash(delegation_hash), + ); + + Address::Shelley(address) +} + +#[allow(dead_code)] +pub fn to_ed25519_keypair(private_key: &Bip32PrivateKey) -> (PrivateKey, PublicKey) { + let private_key = private_key.to_ed25519_private_key(); + let public_key = private_key.public_key(); + (private_key, public_key) +} diff --git a/src/vault/mod.rs b/src/vault/mod.rs new file mode 100644 index 0000000..eab162e --- /dev/null +++ b/src/vault/mod.rs @@ -0,0 +1,29 @@ +use bip39::Mnemonic; +use serde::Deserialize; +use thiserror::Error; + +pub mod hashicorp; +pub mod key; + +#[derive(Error, Debug)] +pub enum VaultError { + #[error("Vault client error: {0}")] + ClientError(#[from] vaultrs::error::ClientError), + + #[error("Configuration error: {0}")] + ConfigError(String), +} + +#[derive(Deserialize, Clone)] +pub struct Config { + pub api_addr: String, + pub token: String, + pub mount: String, + pub path: String, +} + +#[async_trait::async_trait] +pub trait VaultAdapter: Send + Sync { + async fn store_key(&self) -> Result<(), VaultError>; + async fn retrieve_key(&self) -> Result; +} From 22a439cf88ccf4bfea8896b85c51bd0c0a537d45 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Thu, 27 Mar 2025 19:03:35 +0800 Subject: [PATCH 02/14] feat: enhance VaultAdapter to support generic secret types --- src/vault/hashicorp.rs | 15 +++++++++------ src/vault/mod.rs | 5 ++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vault/hashicorp.rs b/src/vault/hashicorp.rs index d701f74..1c2117f 100644 --- a/src/vault/hashicorp.rs +++ b/src/vault/hashicorp.rs @@ -27,10 +27,12 @@ impl HashicorpVaultClient { } #[async_trait::async_trait] -impl VaultAdapter for HashicorpVaultClient { +impl VaultAdapter for HashicorpVaultClient { async fn store_key(&self) -> Result<(), VaultError> { let mnemonic = key::generate_mnemonic(); - let secret = Secret { mnemonic }; + let secret = Secret { + secret_key: mnemonic, + }; kv2::set(&self.client, &self.config.mount, &self.config.path, &secret).await?; @@ -38,13 +40,14 @@ impl VaultAdapter for HashicorpVaultClient { } async fn retrieve_key(&self) -> Result { - let secret: Secret = kv2::read(&self.client, &self.config.mount, &self.config.path).await?; + let secret: Secret = + kv2::read(&self.client, &self.config.mount, &self.config.path).await?; - Ok(secret.mnemonic) + Ok(secret.secret_key) } } #[derive(Serialize, Deserialize, Debug)] -struct Secret { - mnemonic: Mnemonic, +struct Secret { + secret_key: T, } diff --git a/src/vault/mod.rs b/src/vault/mod.rs index eab162e..5bafa11 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -1,4 +1,3 @@ -use bip39::Mnemonic; use serde::Deserialize; use thiserror::Error; @@ -23,7 +22,7 @@ pub struct Config { } #[async_trait::async_trait] -pub trait VaultAdapter: Send + Sync { +pub trait VaultAdapter: Send + Sync { async fn store_key(&self) -> Result<(), VaultError>; - async fn retrieve_key(&self) -> Result; + async fn retrieve_key(&self) -> Result; } From 2338441c7038357d9c146901fcd86823bc0c8098 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Thu, 27 Mar 2025 19:33:38 +0800 Subject: [PATCH 03/14] feat: update mnemonic generation to accept passphrase and refactor Secret struct --- src/vault/hashicorp.rs | 12 ++++++------ src/vault/key/mod.rs | 9 +++++---- src/vault/mod.rs | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vault/hashicorp.rs b/src/vault/hashicorp.rs index 1c2117f..41a898d 100644 --- a/src/vault/hashicorp.rs +++ b/src/vault/hashicorp.rs @@ -7,6 +7,11 @@ use vaultrs::{ use super::{key, Config as VaultConfig, VaultAdapter, VaultError}; +#[derive(Serialize, Deserialize, Debug)] +struct Secret { + secret_key: T, +} + pub struct HashicorpVaultClient { client: VaultClient, config: VaultConfig, @@ -29,7 +34,7 @@ impl HashicorpVaultClient { #[async_trait::async_trait] impl VaultAdapter for HashicorpVaultClient { async fn store_key(&self) -> Result<(), VaultError> { - let mnemonic = key::generate_mnemonic(); + let mnemonic = key::generate_mnemonic(&self.config.passphrase); let secret = Secret { secret_key: mnemonic, }; @@ -46,8 +51,3 @@ impl VaultAdapter for HashicorpVaultClient { Ok(secret.secret_key) } } - -#[derive(Serialize, Deserialize, Debug)] -struct Secret { - secret_key: T, -} diff --git a/src/vault/key/mod.rs b/src/vault/key/mod.rs index 636c0b4..65044cd 100644 --- a/src/vault/key/mod.rs +++ b/src/vault/key/mod.rs @@ -10,14 +10,15 @@ use pallas::{ }, }; -pub fn generate_mnemonic() -> Mnemonic { - Bip32PrivateKey::generate_with_mnemonic(OsRng, "".into()).1 +pub fn generate_mnemonic(passphrase: &str) -> Mnemonic { + Bip32PrivateKey::generate_with_mnemonic(OsRng, passphrase.into()).1 } /// @TODO remove dead code after finalizing POC #[allow(dead_code)] -pub fn generate_account_key(mnemonic: &Mnemonic) -> Bip32PrivateKey { - let root_key = Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), "".into()).unwrap(); +pub fn generate_account_key(mnemonic: &Mnemonic, passphrase: &str) -> Bip32PrivateKey { + let root_key = + Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), passphrase.into()).unwrap(); root_key .derive(1852 | 0x80000000) .derive(1815 | 0x80000000) diff --git a/src/vault/mod.rs b/src/vault/mod.rs index 5bafa11..6050187 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -19,6 +19,7 @@ pub struct Config { pub token: String, pub mount: String, pub path: String, + pub passphrase: String, } #[async_trait::async_trait] From 5ee61cc8414db140282c08e5cabb88b23386f190 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Fri, 28 Mar 2025 04:48:10 +0800 Subject: [PATCH 04/14] feat: introduce signing module with HashiCorp Vault integration for secret management --- Cargo.toml | 7 +-- src/main.rs | 15 ++--- src/server/mod.rs | 5 +- src/server/submit.rs | 37 ++++++++++-- src/signing/hashicorp.rs | 55 ++++++++++++++++++ .../key/mod.rs => signing/key/derive.rs} | 18 +++--- src/signing/key/mod.rs | 2 + src/signing/key/sign.rs | 27 +++++++++ src/signing/mod.rs | 57 +++++++++++++++++++ src/vault/hashicorp.rs | 53 ----------------- src/vault/mod.rs | 29 ---------- 11 files changed, 195 insertions(+), 110 deletions(-) create mode 100644 src/signing/hashicorp.rs rename src/{vault/key/mod.rs => signing/key/derive.rs} (82%) create mode 100644 src/signing/key/mod.rs create mode 100644 src/signing/key/sign.rs create mode 100644 src/signing/mod.rs delete mode 100644 src/vault/hashicorp.rs delete mode 100644 src/vault/mod.rs diff --git a/Cargo.toml b/Cargo.toml index d64ff17..60c1989 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,4 @@ tokio-stream = "0.1.17" rand = "0.9.0" prost = "0.13.5" vaultrs = "0.7.4" -bip39 = "2.1.0" - -[workspace] -members = [ - "examples/tx_signing" -] +bip39 = "2.1.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4085d85..1175c1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,9 +8,8 @@ use queue::{chaining::TxChaining, DEFAULT_QUEUE}; use serde::Deserialize; use storage::sqlite::{SqliteCursor, SqliteStorage, SqliteTransaction}; use tokio::try_join; -use tracing::{info, Level}; +use tracing::Level; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use vault::VaultAdapter; mod ledger; mod network; @@ -19,7 +18,7 @@ mod queue; mod server; mod storage; mod validation; -mod vault; +mod signing; #[tokio::main] async fn main() -> Result<()> { @@ -46,12 +45,7 @@ async fn main() -> Result<()> { config.clone().queues, )); - let vault = vault::hashicorp::HashicorpVaultClient::new(config.vault.clone())?; - vault.store_key().await?; - info!("Vault key stored"); - - let mnemonic = vault.retrieve_key().await?; - info!("Retrieved mnemonic: {}", mnemonic); + let secret_adapter = Arc::new(signing::hashicorp::HashicorpVaultClient::new(config.signing.clone())?); let cursor_storage = Arc::new(SqliteCursor::new(Arc::clone(&storage))); let cursor = cursor_storage.current().await?.map(|c| c.into()); @@ -67,6 +61,7 @@ async fn main() -> Result<()> { let server = server::run( config.server, u5c_data_adapter.clone(), + secret_adapter.clone(), Arc::clone(&tx_storage), Arc::clone(&tx_chaining), ); @@ -85,7 +80,7 @@ struct Config { #[serde(default)] queues: HashSet, u5c: ledger::u5c::Config, - vault: vault::Config, + signing: signing::Config, } impl Config { diff --git a/src/server/mod.rs b/src/server/mod.rs index 4fbc721..45bab35 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,7 @@ use std::{net::SocketAddr, sync::Arc}; use anyhow::Result; +use bip39::Mnemonic; use serde::Deserialize; use spec::boros::v1 as spec; use tonic::transport::Server; @@ -8,7 +9,7 @@ use tracing::{error, info}; use crate::{ ledger::u5c::U5cDataAdapter, queue::chaining::TxChaining, - storage::sqlite::SqliteTransaction, + storage::sqlite::SqliteTransaction, signing::SecretAdapter, }; mod submit; @@ -16,6 +17,7 @@ mod submit; pub async fn run( config: Config, u5c_adapter: Arc, + secret_adapter: Arc>, tx_storage: Arc, tx_chaining: Arc, ) -> Result<()> { @@ -30,6 +32,7 @@ pub async fn run( Arc::clone(&tx_storage), Arc::clone(&tx_chaining), Arc::clone(&u5c_adapter), + Arc::clone(&secret_adapter), ); let submit_service = spec::submit::submit_service_server::SubmitServiceServer::new(submit_service); diff --git a/src/server/submit.rs b/src/server/submit.rs index 12ee709..c350293 100644 --- a/src/server/submit.rs +++ b/src/server/submit.rs @@ -1,5 +1,6 @@ use std::{pin::Pin, sync::Arc}; +use bip39::Mnemonic; use pallas::ledger::traverse::MultiEraTx; use spec::boros::v1::submit::{ submit_service_server::SubmitService, LockStateRequest, LockStateResponse, SubmitTxRequest, @@ -13,6 +14,13 @@ use tracing::{error, info}; use crate::{ ledger::u5c::U5cDataAdapter, queue::chaining::TxChaining, + signing::{ + key::{ + derive::get_signing_key, + sign::{sign_transaction, to_built_transaction}, + }, + SecretAdapter, SecretDataKey, + }, storage::{sqlite::SqliteTransaction, Transaction}, validation::{evaluate_tx, validate_tx}, }; @@ -21,6 +29,7 @@ pub struct SubmitServiceImpl { tx_storage: Arc, tx_chaining: Arc, u5c_adapter: Arc, + secret_adapter: Arc>, } impl SubmitServiceImpl { @@ -28,11 +37,13 @@ impl SubmitServiceImpl { tx_storage: Arc, tx_chaining: Arc, u5c_adapter: Arc, + secret_adapter: Arc>, ) -> Self { Self { tx_storage, tx_chaining, u5c_adapter, + secret_adapter, } } } @@ -49,26 +60,44 @@ impl SubmitService for SubmitServiceImpl { let mut hashes = vec![]; let mut chained_queues = Vec::new(); + let mnemonic = self + .secret_adapter + .retrieve_secret(SecretDataKey::Mnemonic.into()) + .await + .map_err(|error| { + error!(?error); + Status::internal("Error retrieving mnemonic") + })?; + for (idx, tx) in message.tx.into_iter().enumerate() { let metx = MultiEraTx::decode(&tx.raw).map_err(|error| { error!(?error); Status::failed_precondition(format!("invalid tx at index {idx}")) })?; - if let Err(error) = validate_tx(&metx, self.u5c_adapter.clone()).await { + let signing_key = get_signing_key(&mnemonic); + let built_tx = to_built_transaction(&metx); + let signed_tx = sign_transaction(built_tx, signing_key); + + let signed_metx = MultiEraTx::decode(&signed_tx.tx_bytes.0).map_err(|error| { + error!(?error); + Status::failed_precondition(format!("invalid tx at index {idx}")) + })?; + + if let Err(error) = validate_tx(&signed_metx, self.u5c_adapter.clone()).await { error!(?error); continue; } - if let Err(error) = evaluate_tx(&metx, self.u5c_adapter.clone()).await { + if let Err(error) = evaluate_tx(&signed_metx, self.u5c_adapter.clone()).await { error!(?error); continue; } - let hash = metx.hash(); + let hash = signed_metx.hash(); hashes.push(hash.to_vec().into()); - let mut tx_storage = Transaction::new(hash.to_string(), tx.raw.to_vec()); + let mut tx_storage = Transaction::new(hash.to_string(), signed_metx.encode()); if let Some(queue) = tx.queue { if self.tx_chaining.is_chained_queue(&queue) { diff --git a/src/signing/hashicorp.rs b/src/signing/hashicorp.rs new file mode 100644 index 0000000..83c1b39 --- /dev/null +++ b/src/signing/hashicorp.rs @@ -0,0 +1,55 @@ +use bip39::Mnemonic; +use vaultrs::{ + client::{VaultClient, VaultClientSettingsBuilder}, + kv2, +}; + +use super::{Config as VaultConfig, Secret, SecretAdapter, SigningError}; + +pub enum SecretEngine { + KV2, +} + +impl SecretEngine { + pub fn as_str(&self) -> &'static str { + match self { + Self::KV2 => "secret", + } + } +} + +pub struct HashicorpVaultClient { + client: VaultClient, + config: VaultConfig, +} + +impl HashicorpVaultClient { + pub fn new(config: VaultConfig) -> Result { + let client_settings = VaultClientSettingsBuilder::default() + .address(&config.api_addr) + .token(&config.token) + .build() + .map_err(|e| SigningError::Config(e.to_string()))?; + + let client = VaultClient::new(client_settings).map_err(SigningError::Client)?; + + Ok(Self { client, config }) + } +} + +#[async_trait::async_trait] +impl SecretAdapter for HashicorpVaultClient { + async fn retrieve_secret(&self, key: String) -> Result { + let secret: Secret = + kv2::read(&self.client, SecretEngine::KV2.as_str(), &self.config.path).await?; + + let value = secret + .value(key) + .ok_or_else(|| SigningError::SecretNotFound("Mnemonic not found in secret".into()))?; + + let mnemonic = serde_json::from_value(value.clone()) + .map_err(|e| SigningError::Config(e.to_string()))?; + + Ok(mnemonic) + } +} diff --git a/src/vault/key/mod.rs b/src/signing/key/derive.rs similarity index 82% rename from src/vault/key/mod.rs rename to src/signing/key/derive.rs index 65044cd..33b6008 100644 --- a/src/vault/key/mod.rs +++ b/src/signing/key/derive.rs @@ -1,4 +1,4 @@ -use bip39::{rand_core::OsRng, Mnemonic}; +use bip39::Mnemonic; use pallas::{ crypto::{hash::Hasher, key::ed25519::PublicKey}, ledger::addresses::{ @@ -10,15 +10,19 @@ use pallas::{ }, }; -pub fn generate_mnemonic(passphrase: &str) -> Mnemonic { - Bip32PrivateKey::generate_with_mnemonic(OsRng, passphrase.into()).1 +/// @TODO remove dead code after finalizing POC +#[allow(dead_code)] +pub fn get_signing_key(mnemonic: &Mnemonic) -> PrivateKey { + let account_key = generate_account_key(mnemonic); + let (private_key, _) = generate_payment_keypair(&account_key); + let (signing_key, _) = to_ed25519_keypair(&private_key); + + signing_key } -/// @TODO remove dead code after finalizing POC #[allow(dead_code)] -pub fn generate_account_key(mnemonic: &Mnemonic, passphrase: &str) -> Bip32PrivateKey { - let root_key = - Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), passphrase.into()).unwrap(); +pub fn generate_account_key(mnemonic: &Mnemonic) -> Bip32PrivateKey { + let root_key = Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), "".into()).unwrap(); root_key .derive(1852 | 0x80000000) .derive(1815 | 0x80000000) diff --git a/src/signing/key/mod.rs b/src/signing/key/mod.rs new file mode 100644 index 0000000..8fbf499 --- /dev/null +++ b/src/signing/key/mod.rs @@ -0,0 +1,2 @@ +pub mod sign; +pub mod derive; \ No newline at end of file diff --git a/src/signing/key/sign.rs b/src/signing/key/sign.rs new file mode 100644 index 0000000..5982801 --- /dev/null +++ b/src/signing/key/sign.rs @@ -0,0 +1,27 @@ +use pallas::{ + ledger::traverse::MultiEraTx, + txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}, + wallet::keystore::PrivateKey, +}; + +pub fn to_built_transaction(metx: &MultiEraTx) -> BuiltTransaction { + let staging_tx = StagingTransaction::new(); + let built_tx = staging_tx.build_conway_raw().unwrap(); + + BuiltTransaction { + version: built_tx.version, + era: built_tx.era, + status: built_tx.status, + tx_hash: Bytes32(*metx.hash()), + tx_bytes: Bytes(metx.encode()), + signatures: None, + } +} + +pub fn sign_transaction(built_tx: BuiltTransaction, signing_key: PrivateKey) -> BuiltTransaction { + let signed_tx = built_tx.sign(signing_key); + match signed_tx { + Ok(tx) => tx, + _ => panic!("Failed to sign transaction"), + } +} diff --git a/src/signing/mod.rs b/src/signing/mod.rs new file mode 100644 index 0000000..ef03a19 --- /dev/null +++ b/src/signing/mod.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use thiserror::Error; + +pub mod hashicorp; +pub mod key; + +#[derive(Error, Debug)] +pub enum SigningError { + #[error("Vault client error: {0}")] + Client(#[from] vaultrs::error::ClientError), + + #[error("Configuration error: {0}")] + Config(String), + + #[error("Secret not found error: {0}")] + SecretNotFound(String), +} + +#[derive(Deserialize, Clone)] +pub struct Config { + pub api_addr: String, + pub token: String, + pub path: String, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Secret { + #[serde(flatten)] + secret: HashMap, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)] +pub enum SecretDataKey { + Mnemonic, +} + +impl From for String { + fn from(key: SecretDataKey) -> Self { + match key { + SecretDataKey::Mnemonic => "mnemonic".to_string(), + } + } +} + +impl Secret { + fn value(&self, key: String) -> Option<&Value> { + self.secret.get(&key) + } +} + +#[async_trait::async_trait] +pub trait SecretAdapter: Send + Sync { + async fn retrieve_secret(&self, key: String) -> Result; +} diff --git a/src/vault/hashicorp.rs b/src/vault/hashicorp.rs deleted file mode 100644 index 41a898d..0000000 --- a/src/vault/hashicorp.rs +++ /dev/null @@ -1,53 +0,0 @@ -use bip39::Mnemonic; -use serde::{Deserialize, Serialize}; -use vaultrs::{ - client::{VaultClient, VaultClientSettingsBuilder}, - kv2, -}; - -use super::{key, Config as VaultConfig, VaultAdapter, VaultError}; - -#[derive(Serialize, Deserialize, Debug)] -struct Secret { - secret_key: T, -} - -pub struct HashicorpVaultClient { - client: VaultClient, - config: VaultConfig, -} - -impl HashicorpVaultClient { - pub fn new(config: VaultConfig) -> Result { - let client_settings = VaultClientSettingsBuilder::default() - .address(&config.api_addr) - .token(&config.token) - .build() - .map_err(|e| VaultError::ConfigError(e.to_string()))?; - - let client = VaultClient::new(client_settings).map_err(VaultError::ClientError)?; - - Ok(Self { client, config }) - } -} - -#[async_trait::async_trait] -impl VaultAdapter for HashicorpVaultClient { - async fn store_key(&self) -> Result<(), VaultError> { - let mnemonic = key::generate_mnemonic(&self.config.passphrase); - let secret = Secret { - secret_key: mnemonic, - }; - - kv2::set(&self.client, &self.config.mount, &self.config.path, &secret).await?; - - Ok(()) - } - - async fn retrieve_key(&self) -> Result { - let secret: Secret = - kv2::read(&self.client, &self.config.mount, &self.config.path).await?; - - Ok(secret.secret_key) - } -} diff --git a/src/vault/mod.rs b/src/vault/mod.rs deleted file mode 100644 index 6050187..0000000 --- a/src/vault/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::Deserialize; -use thiserror::Error; - -pub mod hashicorp; -pub mod key; - -#[derive(Error, Debug)] -pub enum VaultError { - #[error("Vault client error: {0}")] - ClientError(#[from] vaultrs::error::ClientError), - - #[error("Configuration error: {0}")] - ConfigError(String), -} - -#[derive(Deserialize, Clone)] -pub struct Config { - pub api_addr: String, - pub token: String, - pub mount: String, - pub path: String, - pub passphrase: String, -} - -#[async_trait::async_trait] -pub trait VaultAdapter: Send + Sync { - async fn store_key(&self) -> Result<(), VaultError>; - async fn retrieve_key(&self) -> Result; -} From b5616d8051d2976be58689068076a57fadc9b43d Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Sat, 29 Mar 2025 03:13:56 +0800 Subject: [PATCH 05/14] refactor: get key via config and remove unnecessary enums --- src/server/submit.rs | 4 ++-- src/signing/hashicorp.rs | 28 ++++++++-------------------- src/signing/mod.rs | 27 ++++++--------------------- 3 files changed, 16 insertions(+), 43 deletions(-) diff --git a/src/server/submit.rs b/src/server/submit.rs index c350293..54dfbfd 100644 --- a/src/server/submit.rs +++ b/src/server/submit.rs @@ -19,7 +19,7 @@ use crate::{ derive::get_signing_key, sign::{sign_transaction, to_built_transaction}, }, - SecretAdapter, SecretDataKey, + SecretAdapter, }, storage::{sqlite::SqliteTransaction, Transaction}, validation::{evaluate_tx, validate_tx}, @@ -62,7 +62,7 @@ impl SubmitService for SubmitServiceImpl { let mnemonic = self .secret_adapter - .retrieve_secret(SecretDataKey::Mnemonic.into()) + .retrieve_secret() .await .map_err(|error| { error!(?error); diff --git a/src/signing/hashicorp.rs b/src/signing/hashicorp.rs index 83c1b39..6e418ac 100644 --- a/src/signing/hashicorp.rs +++ b/src/signing/hashicorp.rs @@ -6,18 +6,6 @@ use vaultrs::{ use super::{Config as VaultConfig, Secret, SecretAdapter, SigningError}; -pub enum SecretEngine { - KV2, -} - -impl SecretEngine { - pub fn as_str(&self) -> &'static str { - match self { - Self::KV2 => "secret", - } - } -} - pub struct HashicorpVaultClient { client: VaultClient, config: VaultConfig, @@ -39,16 +27,16 @@ impl HashicorpVaultClient { #[async_trait::async_trait] impl SecretAdapter for HashicorpVaultClient { - async fn retrieve_secret(&self, key: String) -> Result { - let secret: Secret = - kv2::read(&self.client, SecretEngine::KV2.as_str(), &self.config.path).await?; + async fn retrieve_secret(&self) -> Result { + let secret: Secret = kv2::read(&self.client, "secret", &self.config.path) + .await + .map_err(SigningError::Client)?; - let value = secret - .value(key) - .ok_or_else(|| SigningError::SecretNotFound("Mnemonic not found in secret".into()))?; + let value = secret.values.get(&self.config.key).ok_or_else(|| { + SigningError::SecretNotFound(format!("Key {} not found in secret", self.config.key)) + })?; - let mnemonic = serde_json::from_value(value.clone()) - .map_err(|e| SigningError::Config(e.to_string()))?; + let mnemonic = serde_json::from_value(value.clone())?; Ok(mnemonic) } diff --git a/src/signing/mod.rs b/src/signing/mod.rs index ef03a19..cbdb257 100644 --- a/src/signing/mod.rs +++ b/src/signing/mod.rs @@ -17,6 +17,9 @@ pub enum SigningError { #[error("Secret not found error: {0}")] SecretNotFound(String), + + #[error("Deserialization error: {0}")] + Deserialization(#[from] serde_json::Error), } #[derive(Deserialize, Clone)] @@ -24,34 +27,16 @@ pub struct Config { pub api_addr: String, pub token: String, pub path: String, + pub key: String, } #[derive(Serialize, Deserialize, Debug)] struct Secret { #[serde(flatten)] - secret: HashMap, -} - -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)] -pub enum SecretDataKey { - Mnemonic, -} - -impl From for String { - fn from(key: SecretDataKey) -> Self { - match key { - SecretDataKey::Mnemonic => "mnemonic".to_string(), - } - } -} - -impl Secret { - fn value(&self, key: String) -> Option<&Value> { - self.secret.get(&key) - } + values: HashMap, } #[async_trait::async_trait] pub trait SecretAdapter: Send + Sync { - async fn retrieve_secret(&self, key: String) -> Result; + async fn retrieve_secret(&self) -> Result; } From dc599d7985d7be6bbf36b5f82d699d70ecc20a93 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Sat, 29 Mar 2025 04:30:24 +0800 Subject: [PATCH 06/14] refactor: reorganize module imports and improve formatting in main.rs --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1175c1f..20d35f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,9 @@ mod network; mod pipeline; mod queue; mod server; +mod signing; mod storage; mod validation; -mod signing; #[tokio::main] async fn main() -> Result<()> { @@ -45,7 +45,9 @@ async fn main() -> Result<()> { config.clone().queues, )); - let secret_adapter = Arc::new(signing::hashicorp::HashicorpVaultClient::new(config.signing.clone())?); + let secret_adapter = Arc::new(signing::hashicorp::HashicorpVaultClient::new( + config.signing.clone(), + )?); let cursor_storage = Arc::new(SqliteCursor::new(Arc::clone(&storage))); let cursor = cursor_storage.current().await?.map(|c| c.into()); From 41331e398788b8861b1882a050fb350785cb8884 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Tue, 1 Apr 2025 01:48:13 +0800 Subject: [PATCH 07/14] feat: update SecretAdapter to accept a key parameter and enhance Config with server_signing option --- src/pipeline/ingest.rs | 43 ++++++++++++++++--- src/pipeline/mod.rs | 14 +++---- src/queue/chaining.rs | 9 ++++ src/queue/mod.rs | 2 + src/queue/priority.rs | 4 ++ src/server/mod.rs | 14 +++---- src/server/submit.rs | 90 +++++++++++++++++++--------------------- src/signing/hashicorp.rs | 4 +- src/signing/key/sign.rs | 17 ++++++-- src/signing/mod.rs | 2 +- 10 files changed, 123 insertions(+), 76 deletions(-) diff --git a/src/pipeline/ingest.rs b/src/pipeline/ingest.rs index 1d64e90..72d989c 100644 --- a/src/pipeline/ingest.rs +++ b/src/pipeline/ingest.rs @@ -1,5 +1,6 @@ use std::{sync::Arc, time::Duration}; +use bip39::Mnemonic; use gasket::framework::*; use gasket::messaging::{Message, OutputPort}; use pallas::ledger::traverse::MultiEraTx; @@ -7,7 +8,11 @@ use tokio::time::sleep; use tracing::info; use super::CAP; +use crate::signing::key::derive::get_signing_key; +use crate::signing::key::sign::{sign_transaction, to_built_transaction}; +use crate::signing::SecretAdapter; use crate::validation::{evaluate_tx, validate_tx}; +use crate::Config; use crate::{ ledger::u5c::U5cDataAdapter, queue::priority::Priority, @@ -20,6 +25,8 @@ pub struct Stage { storage: Arc, priority: Arc, u5c_adapter: Arc, + secret_adapter: Arc>, + config: Config, pub output: OutputPort>, } @@ -28,22 +35,31 @@ impl Stage { storage: Arc, priority: Arc, u5c_adapter: Arc, + secret_adapter: Arc>, + config: Config, ) -> Self { Self { storage, priority, u5c_adapter, + secret_adapter, + config, output: Default::default(), } } } -pub struct Worker; +pub struct Worker { + mnemonic: Mnemonic, +} #[async_trait::async_trait(?Send)] impl gasket::framework::Worker for Worker { - async fn bootstrap(_stage: &Stage) -> Result { - Ok(Self) + async fn bootstrap(stage: &Stage) -> Result { + let key = stage.config.signing.key.clone(); + let mnemonic = stage.secret_adapter.retrieve_secret(key).await.or_retry()?; + + Ok(Self { mnemonic }) } async fn schedule( @@ -76,8 +92,23 @@ impl gasket::framework::Worker for Worker { unit: &Vec, stage: &mut Stage, ) -> Result<(), WorkerError> { - for tx in unit { - let mut tx = tx.clone(); + for mut tx in unit.iter().cloned() { + let should_sign = stage + .config + .queues + .get(&tx.queue) + .map(|config| config.server_signing) + .unwrap_or(false); + + if should_sign { + info!("Signing transaction {} with server key", tx.id); + let signing_key = get_signing_key(&self.mnemonic); + let built_tx = to_built_transaction(tx.clone()); + let signed_tx = sign_transaction(built_tx, signing_key); + tx.raw = signed_tx.tx_bytes.0.clone(); + info!("Transaction {} signed successfully", tx.id); + } + let metx = MultiEraTx::decode(&tx.raw).map_err(|_| WorkerError::Recv)?; if let Err(e) = validate_tx(&metx, stage.u5c_adapter.clone()).await { @@ -100,7 +131,6 @@ impl gasket::framework::Worker for Worker { info!("Failed to broadcast transaction: {}", e); } else { info!("Transaction {} broadcasted to receivers", tx.id); - let tip = stage.u5c_adapter.fetch_tip().await.or_retry()?; tx.status = TransactionStatus::InFlight; tx.slot = Some(tip.0); @@ -119,6 +149,7 @@ mod ingest_tests { use std::sync::Arc; use anyhow::Ok; + use pallas::codec::utils::KeyValuePairs; use pallas::crypto::hash::Hash; use pallas::ledger::primitives::conway::{ diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 93061d9..76cd3ab 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,20 +1,17 @@ use std::sync::Arc; use anyhow::Result; +use bip39::Mnemonic; use gasket::messaging::tokio::ChannelSendAdapter; use crate::{ ledger::{ relay::{MockRelayDataAdapter, RelayDataAdapter}, u5c::{Point, U5cDataAdapter}, - }, - network::peer_manager::PeerManager, - queue::priority::Priority, - storage::{ + }, network::peer_manager::PeerManager, queue::priority::Priority, signing::SecretAdapter, storage::{ sqlite::{SqliteCursor, SqliteTransaction}, Cursor, - }, - Config, + }, Config }; pub mod ingest; @@ -26,6 +23,7 @@ const CAP: u16 = 50; pub async fn run( config: Config, u5c_data_adapter: Arc, + secret_adapter: Arc>, tx_storage: Arc, cursor_storage: Arc, ) -> Result<()> { @@ -46,12 +44,14 @@ pub async fn run( let peer_manager = Arc::new(peer_manager); - let priority = Arc::new(Priority::new(tx_storage.clone(), config.queues)); + let priority = Arc::new(Priority::new(tx_storage.clone(), config.queues.clone())); let mut ingest = ingest::Stage::new( tx_storage.clone(), priority.clone(), u5c_data_adapter.clone(), + secret_adapter.clone(), + config.clone(), ); ingest.output.connect(sender); diff --git a/src/queue/chaining.rs b/src/queue/chaining.rs index 4c6ab00..5b3f096 100644 --- a/src/queue/chaining.rs +++ b/src/queue/chaining.rs @@ -176,6 +176,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -206,6 +207,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -253,11 +255,13 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }, Config { name: "orange".into(), weight: 1, chained: true, + server_signing: false, }, ]); @@ -305,6 +309,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -340,6 +345,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -356,6 +362,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: false, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -372,6 +379,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); @@ -406,6 +414,7 @@ mod chaining_tests { name: "banana".into(), weight: 1, chained: true, + server_signing: false, }]); let chaining = TxChaining::new(Arc::clone(&storage), queues); diff --git a/src/queue/mod.rs b/src/queue/mod.rs index 639e893..0804ce9 100644 --- a/src/queue/mod.rs +++ b/src/queue/mod.rs @@ -15,6 +15,7 @@ pub struct Config { pub weight: u8, #[serde(default)] pub chained: bool, + pub server_signing: bool, } impl Default for Config { fn default() -> Self { @@ -22,6 +23,7 @@ impl Default for Config { name: DEFAULT_QUEUE.into(), weight: DEFAULT_WEIGHT, chained: false, + server_signing: false, } } } diff --git a/src/queue/priority.rs b/src/queue/priority.rs index b101b99..a2e0408 100644 --- a/src/queue/priority.rs +++ b/src/queue/priority.rs @@ -113,16 +113,19 @@ mod priority_tests { name: "default".into(), weight: 1, chained: false, + server_signing: false, }, Config { name: "banana".into(), weight: 2, chained: false, + server_signing: false, }, Config { name: "orange".into(), weight: 1, chained: false, + server_signing: false, }, ]), ); @@ -155,6 +158,7 @@ mod priority_tests { name: "default".into(), weight: 1, chained: false, + server_signing: false, }]), ); diff --git a/src/server/mod.rs b/src/server/mod.rs index 45bab35..ec024ca 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,23 +1,21 @@ use std::{net::SocketAddr, sync::Arc}; use anyhow::Result; -use bip39::Mnemonic; use serde::Deserialize; use spec::boros::v1 as spec; use tonic::transport::Server; use tracing::{error, info}; use crate::{ - ledger::u5c::U5cDataAdapter, queue::chaining::TxChaining, - storage::sqlite::SqliteTransaction, signing::SecretAdapter, + ledger::u5c::U5cDataAdapter, queue::chaining::TxChaining, storage::sqlite::SqliteTransaction, + Config as BorosConfig, }; mod submit; pub async fn run( - config: Config, + config: BorosConfig, u5c_adapter: Arc, - secret_adapter: Arc>, tx_storage: Arc, tx_chaining: Arc, ) -> Result<()> { @@ -32,20 +30,20 @@ pub async fn run( Arc::clone(&tx_storage), Arc::clone(&tx_chaining), Arc::clone(&u5c_adapter), - Arc::clone(&secret_adapter), + config.queues, ); let submit_service = spec::submit::submit_service_server::SubmitServiceServer::new(submit_service); info!( - address = config.listen_address.to_string(), + address = config.server.listen_address.to_string(), "GRPC server running" ); let result = Server::builder() .add_service(reflection) .add_service(submit_service) - .serve(config.listen_address) + .serve(config.server.listen_address) .await; if let Err(error) = result { diff --git a/src/server/submit.rs b/src/server/submit.rs index 54dfbfd..f51098b 100644 --- a/src/server/submit.rs +++ b/src/server/submit.rs @@ -1,6 +1,5 @@ -use std::{pin::Pin, sync::Arc}; +use std::{collections::HashSet, pin::Pin, sync::Arc}; -use bip39::Mnemonic; use pallas::ledger::traverse::MultiEraTx; use spec::boros::v1::submit::{ submit_service_server::SubmitService, LockStateRequest, LockStateResponse, SubmitTxRequest, @@ -9,18 +8,11 @@ use spec::boros::v1::submit::{ use tokio::sync::mpsc; use tokio_stream::{wrappers::ReceiverStream, Stream}; use tonic::{Response, Status}; -use tracing::{error, info}; +use tracing::{error, info, warn}; use crate::{ ledger::u5c::U5cDataAdapter, - queue::chaining::TxChaining, - signing::{ - key::{ - derive::get_signing_key, - sign::{sign_transaction, to_built_transaction}, - }, - SecretAdapter, - }, + queue::{chaining::TxChaining, Config, DEFAULT_QUEUE}, storage::{sqlite::SqliteTransaction, Transaction}, validation::{evaluate_tx, validate_tx}, }; @@ -29,7 +21,7 @@ pub struct SubmitServiceImpl { tx_storage: Arc, tx_chaining: Arc, u5c_adapter: Arc, - secret_adapter: Arc>, + queues: HashSet, } impl SubmitServiceImpl { @@ -37,13 +29,13 @@ impl SubmitServiceImpl { tx_storage: Arc, tx_chaining: Arc, u5c_adapter: Arc, - secret_adapter: Arc>, + queues: HashSet, ) -> Self { Self { tx_storage, tx_chaining, u5c_adapter, - secret_adapter, + queues, } } } @@ -60,61 +52,63 @@ impl SubmitService for SubmitServiceImpl { let mut hashes = vec![]; let mut chained_queues = Vec::new(); - let mnemonic = self - .secret_adapter - .retrieve_secret() - .await - .map_err(|error| { - error!(?error); - Status::internal("Error retrieving mnemonic") - })?; - for (idx, tx) in message.tx.into_iter().enumerate() { let metx = MultiEraTx::decode(&tx.raw).map_err(|error| { error!(?error); Status::failed_precondition(format!("invalid tx at index {idx}")) })?; - let signing_key = get_signing_key(&mnemonic); - let built_tx = to_built_transaction(&metx); - let signed_tx = sign_transaction(built_tx, signing_key); - - let signed_metx = MultiEraTx::decode(&signed_tx.tx_bytes.0).map_err(|error| { - error!(?error); - Status::failed_precondition(format!("invalid tx at index {idx}")) - })?; - - if let Err(error) = validate_tx(&signed_metx, self.u5c_adapter.clone()).await { - error!(?error); - continue; - } + let hash = metx.hash(); + + let queue_config = tx.queue.as_ref().and_then(|queue_name| { + self.queues + .get(queue_name) + .map(|config| (queue_name.clone(), config.clone())) + .or_else(|| { + warn!(queue = ?queue_name, "Queue not found, using default queue"); + self.queues + .iter() + .find(|q| q.name == *DEFAULT_QUEUE) + .map(|config| (DEFAULT_QUEUE.to_string(), config.clone())) + }) + }); + + let should_validate = queue_config + .as_ref() + .is_none_or(|(_, config)| !config.server_signing); + + if should_validate { + if let Err(error) = validate_tx(&metx, self.u5c_adapter.clone()).await { + error!(?error); + continue; + } - if let Err(error) = evaluate_tx(&signed_metx, self.u5c_adapter.clone()).await { - error!(?error); - continue; + if let Err(error) = evaluate_tx(&metx, self.u5c_adapter.clone()).await { + error!(?error); + continue; + } } - let hash = signed_metx.hash(); - - hashes.push(hash.to_vec().into()); - let mut tx_storage = Transaction::new(hash.to_string(), signed_metx.encode()); + let mut tx_storage = Transaction::new(hash.to_string(), tx.raw.to_vec()); - if let Some(queue) = tx.queue { - if self.tx_chaining.is_chained_queue(&queue) { - chained_queues.push(queue.clone()); + if let Some((queue_name, _)) = queue_config { + if self.tx_chaining.is_chained_queue(&queue_name) { + chained_queues.push(queue_name.clone()); + let lock_token = tx.lock_token.unwrap_or_default(); if !self .tx_chaining - .is_valid_token(&queue, &tx.lock_token.unwrap_or_default()) + .is_valid_token(&queue_name, &lock_token) .await { return Err(Status::permission_denied("invalid lock token")); } } - tx_storage.queue = queue; + tx_storage.queue = queue_name; } + hashes.push(hash.to_vec().into()); txs.push(tx_storage) } diff --git a/src/signing/hashicorp.rs b/src/signing/hashicorp.rs index 6e418ac..80555fe 100644 --- a/src/signing/hashicorp.rs +++ b/src/signing/hashicorp.rs @@ -27,12 +27,12 @@ impl HashicorpVaultClient { #[async_trait::async_trait] impl SecretAdapter for HashicorpVaultClient { - async fn retrieve_secret(&self) -> Result { + async fn retrieve_secret(&self, key: String) -> Result { let secret: Secret = kv2::read(&self.client, "secret", &self.config.path) .await .map_err(SigningError::Client)?; - let value = secret.values.get(&self.config.key).ok_or_else(|| { + let value = secret.values.get(&key).ok_or_else(|| { SigningError::SecretNotFound(format!("Key {} not found in secret", self.config.key)) })?; diff --git a/src/signing/key/sign.rs b/src/signing/key/sign.rs index 5982801..3ba8560 100644 --- a/src/signing/key/sign.rs +++ b/src/signing/key/sign.rs @@ -1,19 +1,28 @@ use pallas::{ - ledger::traverse::MultiEraTx, txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}, wallet::keystore::PrivateKey, }; -pub fn to_built_transaction(metx: &MultiEraTx) -> BuiltTransaction { +use crate::storage::Transaction; + +pub fn to_built_transaction(tx: Transaction) -> BuiltTransaction { let staging_tx = StagingTransaction::new(); let built_tx = staging_tx.build_conway_raw().unwrap(); + + let tx_hash: [u8; 32] = match hex::decode(tx.id) { + Ok(hash) => match hash.try_into() { + Ok(hash) => hash, + Err(_) => panic!("Failed to convert transaction ID to hash"), + }, + Err(_) => panic!("Failed to decode transaction ID"), + }; BuiltTransaction { version: built_tx.version, era: built_tx.era, status: built_tx.status, - tx_hash: Bytes32(*metx.hash()), - tx_bytes: Bytes(metx.encode()), + tx_hash: Bytes32(tx_hash), + tx_bytes: Bytes(tx.raw), signatures: None, } } diff --git a/src/signing/mod.rs b/src/signing/mod.rs index cbdb257..ffb4dc2 100644 --- a/src/signing/mod.rs +++ b/src/signing/mod.rs @@ -38,5 +38,5 @@ struct Secret { #[async_trait::async_trait] pub trait SecretAdapter: Send + Sync { - async fn retrieve_secret(&self) -> Result; + async fn retrieve_secret(&self, key: String) -> Result; } From 053e0c6b0538a94478a08bd97e40610d32629518 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Tue, 1 Apr 2025 19:18:09 +0800 Subject: [PATCH 08/14] refactor: update transaction signing to use mnemonic directly and clean up imports --- src/main.rs | 4 ++-- src/pipeline/ingest.rs | 4 +--- src/signing/key/sign.rs | 13 +++++++------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 20d35f2..435c945 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,13 +57,13 @@ async fn main() -> Result<()> { let pipeline = pipeline::run( config.clone(), u5c_data_adapter.clone(), + secret_adapter, Arc::clone(&tx_storage), Arc::clone(&cursor_storage), ); let server = server::run( - config.server, + config, u5c_data_adapter.clone(), - secret_adapter.clone(), Arc::clone(&tx_storage), Arc::clone(&tx_chaining), ); diff --git a/src/pipeline/ingest.rs b/src/pipeline/ingest.rs index 72d989c..569a763 100644 --- a/src/pipeline/ingest.rs +++ b/src/pipeline/ingest.rs @@ -8,7 +8,6 @@ use tokio::time::sleep; use tracing::info; use super::CAP; -use crate::signing::key::derive::get_signing_key; use crate::signing::key::sign::{sign_transaction, to_built_transaction}; use crate::signing::SecretAdapter; use crate::validation::{evaluate_tx, validate_tx}; @@ -102,9 +101,8 @@ impl gasket::framework::Worker for Worker { if should_sign { info!("Signing transaction {} with server key", tx.id); - let signing_key = get_signing_key(&self.mnemonic); let built_tx = to_built_transaction(tx.clone()); - let signed_tx = sign_transaction(built_tx, signing_key); + let signed_tx = sign_transaction(built_tx, &self.mnemonic); tx.raw = signed_tx.tx_bytes.0.clone(); info!("Transaction {} signed successfully", tx.id); } diff --git a/src/signing/key/sign.rs b/src/signing/key/sign.rs index 3ba8560..1b3bf0c 100644 --- a/src/signing/key/sign.rs +++ b/src/signing/key/sign.rs @@ -1,14 +1,14 @@ -use pallas::{ - txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}, - wallet::keystore::PrivateKey, -}; +use bip39::Mnemonic; +use pallas::txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}; use crate::storage::Transaction; +use super::derive::get_signing_key; + pub fn to_built_transaction(tx: Transaction) -> BuiltTransaction { let staging_tx = StagingTransaction::new(); let built_tx = staging_tx.build_conway_raw().unwrap(); - + let tx_hash: [u8; 32] = match hex::decode(tx.id) { Ok(hash) => match hash.try_into() { Ok(hash) => hash, @@ -27,7 +27,8 @@ pub fn to_built_transaction(tx: Transaction) -> BuiltTransaction { } } -pub fn sign_transaction(built_tx: BuiltTransaction, signing_key: PrivateKey) -> BuiltTransaction { +pub fn sign_transaction(built_tx: BuiltTransaction, mnemonic: &Mnemonic) -> BuiltTransaction { + let signing_key = get_signing_key(mnemonic); let signed_tx = built_tx.sign(signing_key); match signed_tx { Ok(tx) => tx, From 0ae4be22abb93c37456251efc96b86e2c1c52fb8 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Tue, 1 Apr 2025 19:27:49 +0800 Subject: [PATCH 09/14] refactor: remove direct instantiation of HashiCorpVaultClient in main and move to pipeline run function --- src/main.rs | 5 ----- src/pipeline/mod.rs | 14 +++++++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 435c945..d39a2f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,10 +45,6 @@ async fn main() -> Result<()> { config.clone().queues, )); - let secret_adapter = Arc::new(signing::hashicorp::HashicorpVaultClient::new( - config.signing.clone(), - )?); - let cursor_storage = Arc::new(SqliteCursor::new(Arc::clone(&storage))); let cursor = cursor_storage.current().await?.map(|c| c.into()); @@ -57,7 +53,6 @@ async fn main() -> Result<()> { let pipeline = pipeline::run( config.clone(), u5c_data_adapter.clone(), - secret_adapter, Arc::clone(&tx_storage), Arc::clone(&cursor_storage), ); diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 76cd3ab..6401c87 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,17 +1,21 @@ use std::sync::Arc; use anyhow::Result; -use bip39::Mnemonic; use gasket::messaging::tokio::ChannelSendAdapter; use crate::{ ledger::{ relay::{MockRelayDataAdapter, RelayDataAdapter}, u5c::{Point, U5cDataAdapter}, - }, network::peer_manager::PeerManager, queue::priority::Priority, signing::SecretAdapter, storage::{ + }, + network::peer_manager::PeerManager, + queue::priority::Priority, + signing::hashicorp::HashicorpVaultClient, + storage::{ sqlite::{SqliteCursor, SqliteTransaction}, Cursor, - }, Config + }, + Config, }; pub mod ingest; @@ -23,7 +27,6 @@ const CAP: u16 = 50; pub async fn run( config: Config, u5c_data_adapter: Arc, - secret_adapter: Arc>, tx_storage: Arc, cursor_storage: Arc, ) -> Result<()> { @@ -45,12 +48,13 @@ pub async fn run( let peer_manager = Arc::new(peer_manager); let priority = Arc::new(Priority::new(tx_storage.clone(), config.queues.clone())); + let secret_adapter = Arc::new(HashicorpVaultClient::new(config.signing.clone())?); let mut ingest = ingest::Stage::new( tx_storage.clone(), priority.clone(), u5c_data_adapter.clone(), - secret_adapter.clone(), + secret_adapter, config.clone(), ); From c9e75b43169d0538602a057b44d2a32de5662853 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Tue, 1 Apr 2025 21:30:01 +0800 Subject: [PATCH 10/14] refactor: replace SecretAdapter with SigningAdapter and streamline transaction signing process --- src/pipeline/ingest.rs | 32 +++++++++++----------------- src/signing/hashicorp.rs | 44 ++++++++++++++++++++++++++++++++------- src/signing/key/derive.rs | 13 ++---------- src/signing/key/mod.rs | 3 ++- src/signing/key/sign.rs | 25 ++++++++-------------- src/signing/mod.rs | 9 ++++++-- 6 files changed, 68 insertions(+), 58 deletions(-) diff --git a/src/pipeline/ingest.rs b/src/pipeline/ingest.rs index 569a763..bd9a411 100644 --- a/src/pipeline/ingest.rs +++ b/src/pipeline/ingest.rs @@ -1,6 +1,5 @@ use std::{sync::Arc, time::Duration}; -use bip39::Mnemonic; use gasket::framework::*; use gasket::messaging::{Message, OutputPort}; use pallas::ledger::traverse::MultiEraTx; @@ -8,8 +7,7 @@ use tokio::time::sleep; use tracing::info; use super::CAP; -use crate::signing::key::sign::{sign_transaction, to_built_transaction}; -use crate::signing::SecretAdapter; +use crate::signing::SigningAdapter; use crate::validation::{evaluate_tx, validate_tx}; use crate::Config; use crate::{ @@ -24,7 +22,7 @@ pub struct Stage { storage: Arc, priority: Arc, u5c_adapter: Arc, - secret_adapter: Arc>, + secret_adapter: Arc, config: Config, pub output: OutputPort>, } @@ -34,7 +32,7 @@ impl Stage { storage: Arc, priority: Arc, u5c_adapter: Arc, - secret_adapter: Arc>, + secret_adapter: Arc, config: Config, ) -> Self { Self { @@ -48,17 +46,12 @@ impl Stage { } } -pub struct Worker { - mnemonic: Mnemonic, -} +pub struct Worker; #[async_trait::async_trait(?Send)] impl gasket::framework::Worker for Worker { - async fn bootstrap(stage: &Stage) -> Result { - let key = stage.config.signing.key.clone(); - let mnemonic = stage.secret_adapter.retrieve_secret(key).await.or_retry()?; - - Ok(Self { mnemonic }) + async fn bootstrap(_stage: &Stage) -> Result { + Ok(Self) } async fn schedule( @@ -91,7 +84,9 @@ impl gasket::framework::Worker for Worker { unit: &Vec, stage: &mut Stage, ) -> Result<(), WorkerError> { - for mut tx in unit.iter().cloned() { + for tx in unit { + let mut tx = tx.clone(); + let should_sign = stage .config .queues @@ -101,9 +96,7 @@ impl gasket::framework::Worker for Worker { if should_sign { info!("Signing transaction {} with server key", tx.id); - let built_tx = to_built_transaction(tx.clone()); - let signed_tx = sign_transaction(built_tx, &self.mnemonic); - tx.raw = signed_tx.tx_bytes.0.clone(); + tx.raw = stage.secret_adapter.sign(&tx.raw).await.or_retry()?; info!("Transaction {} signed successfully", tx.id); } @@ -142,13 +135,12 @@ impl gasket::framework::Worker for Worker { #[cfg(test)] mod ingest_tests { - use std::collections::HashMap; + use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; use std::sync::Arc; use anyhow::Ok; - use pallas::codec::utils::KeyValuePairs; use pallas::crypto::hash::Hash; use pallas::ledger::primitives::conway::{ CostModels, DRepVotingThresholds, PoolVotingThresholds, @@ -449,7 +441,7 @@ mod ingest_tests { 20744, 32, 25933, 32, 24623, 32, 43053543, 10, 53384111, 14333, 10, 43574283, 26308, 10, ]), - unknown: KeyValuePairs::from(vec![]), + unknown: BTreeMap::new(), }, execution_costs: ExUnitPrices { mem_price: RationalNumber { diff --git a/src/signing/hashicorp.rs b/src/signing/hashicorp.rs index 80555fe..0923a98 100644 --- a/src/signing/hashicorp.rs +++ b/src/signing/hashicorp.rs @@ -1,10 +1,17 @@ use bip39::Mnemonic; +use pallas::{crypto::key::ed25519::Signature, ledger::traverse::MultiEraTx}; use vaultrs::{ client::{VaultClient, VaultClientSettingsBuilder}, kv2, }; -use super::{Config as VaultConfig, Secret, SecretAdapter, SigningError}; +use super::{ + key::{ + derive::get_ed25519_keypair, + sign::{sign_transaction, to_built_transaction}, + }, + Config as VaultConfig, Secret, SigningAdapter, SigningError, +}; pub struct HashicorpVaultClient { client: VaultClient, @@ -23,21 +30,42 @@ impl HashicorpVaultClient { Ok(Self { client, config }) } -} -#[async_trait::async_trait] -impl SecretAdapter for HashicorpVaultClient { - async fn retrieve_secret(&self, key: String) -> Result { + async fn get_mnemonic(&self, key: &str) -> Result { let secret: Secret = kv2::read(&self.client, "secret", &self.config.path) .await .map_err(SigningError::Client)?; - let value = secret.values.get(&key).ok_or_else(|| { - SigningError::SecretNotFound(format!("Key {} not found in secret", self.config.key)) + let value = secret.values.get(key).ok_or_else(|| { + SigningError::SecretNotFound(format!("Key {} not found in secret", key)) })?; let mnemonic = serde_json::from_value(value.clone())?; - Ok(mnemonic) } } + +#[async_trait::async_trait] +impl SigningAdapter for HashicorpVaultClient { + async fn sign(&self, data: &[u8]) -> Result, SigningError> { + let mnemonic = self.get_mnemonic(&self.config.key).await?; + + let metx = MultiEraTx::decode(data) + .map_err(|e| SigningError::Decoding(format!("Failed to decode transaction: {}", e)))?; + + let built_tx = to_built_transaction(&metx); + let signed_tx = sign_transaction(built_tx, &mnemonic); + + Ok(signed_tx.tx_bytes.0) + } + + async fn verify(&self, data: &[u8], signature: &[u8]) -> Result { + let mnemonic = self.get_mnemonic(&self.config.key).await?; + let (_, public_key) = get_ed25519_keypair(&mnemonic); + + let signature = Signature::try_from(signature) + .map_err(|e| SigningError::Decoding(format!("Failed to decode signature: {}", e)))?; + + Ok(public_key.verify(data, &signature)) + } +} diff --git a/src/signing/key/derive.rs b/src/signing/key/derive.rs index 33b6008..66dea8f 100644 --- a/src/signing/key/derive.rs +++ b/src/signing/key/derive.rs @@ -10,17 +10,13 @@ use pallas::{ }, }; -/// @TODO remove dead code after finalizing POC -#[allow(dead_code)] -pub fn get_signing_key(mnemonic: &Mnemonic) -> PrivateKey { +pub fn get_ed25519_keypair(mnemonic: &Mnemonic) -> (PrivateKey, PublicKey) { let account_key = generate_account_key(mnemonic); let (private_key, _) = generate_payment_keypair(&account_key); - let (signing_key, _) = to_ed25519_keypair(&private_key); - signing_key + to_ed25519_keypair(&private_key) } -#[allow(dead_code)] pub fn generate_account_key(mnemonic: &Mnemonic) -> Bip32PrivateKey { let root_key = Bip32PrivateKey::from_bip39_mnenomic(mnemonic.to_string(), "".into()).unwrap(); root_key @@ -29,7 +25,6 @@ pub fn generate_account_key(mnemonic: &Mnemonic) -> Bip32PrivateKey { .derive(0x80000000) } -#[allow(dead_code)] pub fn generate_payment_keypair( account_key: &Bip32PrivateKey, ) -> (Bip32PrivateKey, Bip32PublicKey) { @@ -39,7 +34,6 @@ pub fn generate_payment_keypair( (private_key, public_key) } -#[allow(dead_code)] pub fn generate_delegation_keypair( account_key: &Bip32PrivateKey, ) -> (Bip32PrivateKey, Bip32PublicKey) { @@ -49,7 +43,6 @@ pub fn generate_delegation_keypair( (private_key, public_key) } -#[allow(dead_code)] pub fn generate_address(public_key: &Bip32PublicKey) -> Address { let payment_hash = Hasher::<224>::hash(&public_key.as_bytes()[..32]); let address = ShelleyAddress::new( @@ -61,7 +54,6 @@ pub fn generate_address(public_key: &Bip32PublicKey) -> Address { Address::Shelley(address) } -#[allow(dead_code)] pub fn generate_address_with_delegation( account_key: &Bip32PrivateKey, public_key: &Bip32PublicKey, @@ -80,7 +72,6 @@ pub fn generate_address_with_delegation( Address::Shelley(address) } -#[allow(dead_code)] pub fn to_ed25519_keypair(private_key: &Bip32PrivateKey) -> (PrivateKey, PublicKey) { let private_key = private_key.to_ed25519_private_key(); let public_key = private_key.public_key(); diff --git a/src/signing/key/mod.rs b/src/signing/key/mod.rs index 8fbf499..f4fc9e7 100644 --- a/src/signing/key/mod.rs +++ b/src/signing/key/mod.rs @@ -1,2 +1,3 @@ +#[allow(unused)] +pub mod derive; pub mod sign; -pub mod derive; \ No newline at end of file diff --git a/src/signing/key/sign.rs b/src/signing/key/sign.rs index 1b3bf0c..c12e48d 100644 --- a/src/signing/key/sign.rs +++ b/src/signing/key/sign.rs @@ -1,34 +1,27 @@ use bip39::Mnemonic; -use pallas::txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}; +use pallas::{ + ledger::traverse::MultiEraTx, + txbuilder::{BuildConway, BuiltTransaction, Bytes, Bytes32, StagingTransaction}, +}; -use crate::storage::Transaction; +use super::derive::get_ed25519_keypair; -use super::derive::get_signing_key; - -pub fn to_built_transaction(tx: Transaction) -> BuiltTransaction { +pub fn to_built_transaction(tx: &MultiEraTx) -> BuiltTransaction { let staging_tx = StagingTransaction::new(); let built_tx = staging_tx.build_conway_raw().unwrap(); - let tx_hash: [u8; 32] = match hex::decode(tx.id) { - Ok(hash) => match hash.try_into() { - Ok(hash) => hash, - Err(_) => panic!("Failed to convert transaction ID to hash"), - }, - Err(_) => panic!("Failed to decode transaction ID"), - }; - BuiltTransaction { version: built_tx.version, era: built_tx.era, status: built_tx.status, - tx_hash: Bytes32(tx_hash), - tx_bytes: Bytes(tx.raw), + tx_hash: Bytes32(*tx.hash()), + tx_bytes: Bytes(tx.encode()), signatures: None, } } pub fn sign_transaction(built_tx: BuiltTransaction, mnemonic: &Mnemonic) -> BuiltTransaction { - let signing_key = get_signing_key(mnemonic); + let (signing_key, _) = get_ed25519_keypair(mnemonic); let signed_tx = built_tx.sign(signing_key); match signed_tx { Ok(tx) => tx, diff --git a/src/signing/mod.rs b/src/signing/mod.rs index ffb4dc2..8bc98a5 100644 --- a/src/signing/mod.rs +++ b/src/signing/mod.rs @@ -20,6 +20,9 @@ pub enum SigningError { #[error("Deserialization error: {0}")] Deserialization(#[from] serde_json::Error), + + #[error("Decoding error: {0}")] + Decoding(String), } #[derive(Deserialize, Clone)] @@ -37,6 +40,8 @@ struct Secret { } #[async_trait::async_trait] -pub trait SecretAdapter: Send + Sync { - async fn retrieve_secret(&self, key: String) -> Result; +pub trait SigningAdapter: Send + Sync { + async fn sign(&self, data: &[u8]) -> Result, SigningError>; + #[allow(unused)] // to make clippy happy only + async fn verify(&self, data: &[u8], signature: &[u8]) -> Result; } From 81fd3f34ff1afe9e81f782be41cc91b659c165ef Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Tue, 1 Apr 2025 21:50:39 +0800 Subject: [PATCH 11/14] refactor: update SigningAdapter to accept owned data and improve transaction signing logic --- src/pipeline/ingest.rs | 2 +- src/server/submit.rs | 36 +++++++++++++----------------------- src/signing/hashicorp.rs | 8 ++++---- src/signing/mod.rs | 4 ++-- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/pipeline/ingest.rs b/src/pipeline/ingest.rs index bd9a411..e663f6c 100644 --- a/src/pipeline/ingest.rs +++ b/src/pipeline/ingest.rs @@ -96,7 +96,7 @@ impl gasket::framework::Worker for Worker { if should_sign { info!("Signing transaction {} with server key", tx.id); - tx.raw = stage.secret_adapter.sign(&tx.raw).await.or_retry()?; + tx.raw = stage.secret_adapter.sign(tx.raw).await.or_retry()?; info!("Transaction {} signed successfully", tx.id); } diff --git a/src/server/submit.rs b/src/server/submit.rs index f51098b..eca468e 100644 --- a/src/server/submit.rs +++ b/src/server/submit.rs @@ -60,22 +60,16 @@ impl SubmitService for SubmitServiceImpl { let hash = metx.hash(); - let queue_config = tx.queue.as_ref().and_then(|queue_name| { - self.queues - .get(queue_name) - .map(|config| (queue_name.clone(), config.clone())) - .or_else(|| { + let should_validate = tx + .queue + .as_ref() + .and_then(|queue_name| { + self.queues.get(queue_name).or_else(|| { warn!(queue = ?queue_name, "Queue not found, using default queue"); - self.queues - .iter() - .find(|q| q.name == *DEFAULT_QUEUE) - .map(|config| (DEFAULT_QUEUE.to_string(), config.clone())) + self.queues.iter().find(|q| q.name == *DEFAULT_QUEUE) }) - }); - - let should_validate = queue_config - .as_ref() - .is_none_or(|(_, config)| !config.server_signing); + }) + .is_none_or(|config| !config.server_signing); if should_validate { if let Err(error) = validate_tx(&metx, self.u5c_adapter.clone()).await { @@ -91,21 +85,17 @@ impl SubmitService for SubmitServiceImpl { let mut tx_storage = Transaction::new(hash.to_string(), tx.raw.to_vec()); - if let Some((queue_name, _)) = queue_config { - if self.tx_chaining.is_chained_queue(&queue_name) { - chained_queues.push(queue_name.clone()); + if let Some(queue) = tx.queue { + if self.tx_chaining.is_chained_queue(&queue) { + chained_queues.push(queue.clone()); let lock_token = tx.lock_token.unwrap_or_default(); - if !self - .tx_chaining - .is_valid_token(&queue_name, &lock_token) - .await - { + if !self.tx_chaining.is_valid_token(&queue, &lock_token).await { return Err(Status::permission_denied("invalid lock token")); } } - tx_storage.queue = queue_name; + tx_storage.queue = queue; } hashes.push(hash.to_vec().into()); diff --git a/src/signing/hashicorp.rs b/src/signing/hashicorp.rs index 0923a98..91ea4bc 100644 --- a/src/signing/hashicorp.rs +++ b/src/signing/hashicorp.rs @@ -47,10 +47,10 @@ impl HashicorpVaultClient { #[async_trait::async_trait] impl SigningAdapter for HashicorpVaultClient { - async fn sign(&self, data: &[u8]) -> Result, SigningError> { + async fn sign(&self, data: Vec) -> Result, SigningError> { let mnemonic = self.get_mnemonic(&self.config.key).await?; - let metx = MultiEraTx::decode(data) + let metx = MultiEraTx::decode(&data) .map_err(|e| SigningError::Decoding(format!("Failed to decode transaction: {}", e)))?; let built_tx = to_built_transaction(&metx); @@ -59,11 +59,11 @@ impl SigningAdapter for HashicorpVaultClient { Ok(signed_tx.tx_bytes.0) } - async fn verify(&self, data: &[u8], signature: &[u8]) -> Result { + async fn verify(&self, data: Vec, signature: Vec) -> Result { let mnemonic = self.get_mnemonic(&self.config.key).await?; let (_, public_key) = get_ed25519_keypair(&mnemonic); - let signature = Signature::try_from(signature) + let signature = Signature::try_from(signature.as_slice()) .map_err(|e| SigningError::Decoding(format!("Failed to decode signature: {}", e)))?; Ok(public_key.verify(data, &signature)) diff --git a/src/signing/mod.rs b/src/signing/mod.rs index 8bc98a5..3585e13 100644 --- a/src/signing/mod.rs +++ b/src/signing/mod.rs @@ -41,7 +41,7 @@ struct Secret { #[async_trait::async_trait] pub trait SigningAdapter: Send + Sync { - async fn sign(&self, data: &[u8]) -> Result, SigningError>; + async fn sign(&self, data: Vec) -> Result, SigningError>; #[allow(unused)] // to make clippy happy only - async fn verify(&self, data: &[u8], signature: &[u8]) -> Result; + async fn verify(&self, data: Vec, signature: Vec) -> Result; } From 63baea2426319f21b4758a5da64bc8af67da5825 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Wed, 2 Apr 2025 20:19:15 +0800 Subject: [PATCH 12/14] refactor: replace KeyValuePairs with BTreeMap type --- src/ledger/u5c/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ledger/u5c/mod.rs b/src/ledger/u5c/mod.rs index 2abfb3a..1931b44 100644 --- a/src/ledger/u5c/mod.rs +++ b/src/ledger/u5c/mod.rs @@ -1,10 +1,14 @@ -use std::{collections::HashMap, pin::Pin, str::FromStr, vec}; +use std::{ + collections::{BTreeMap, HashMap}, + pin::Pin, + str::FromStr, + vec, +}; use anyhow::bail; use async_stream::stream; use futures::{Stream, TryStreamExt}; use pallas::{ - codec::utils::KeyValuePairs, crypto::hash::Hash, interop::utxorpc::spec::{ cardano::{CostModel, Tx}, @@ -322,7 +326,7 @@ fn map_conway_pparams(pparams: &Params) -> ConwayProtParams { .unwrap_or(CostModel { values: vec![] }) .values, ), - unknown: KeyValuePairs::from(vec![]), + unknown: BTreeMap::new(), }, execution_costs: ExUnitPrices { mem_price: primitives::RationalNumber { From 7020a946c18c069fdd8736e83a93d9a8aab40409 Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Wed, 2 Apr 2025 20:47:56 +0800 Subject: [PATCH 13/14] chore: update pallas dependency feature flag --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60c1989..e6a41fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ futures-util = "0.3.31" gasket = { git = "https://github.com/construkts/gasket-rs.git", features = ["derive"] } hex = "0.4.3" itertools = "0.14.0" -pallas = { git = "https://github.com/txpipe/pallas.git", features = ["phase-two", "wallet"] } +pallas = { git = "https://github.com/txpipe/pallas.git", features = ["phase2", "wallet"] } protoc-wkt = "1.0.0" serde = { version = "1.0.217", features = ["derive"] } thiserror = "2.0.11" @@ -40,4 +40,4 @@ tokio-stream = "0.1.17" rand = "0.9.0" prost = "0.13.5" vaultrs = "0.7.4" -bip39 = "2.1.0" \ No newline at end of file +bip39 = "2.1.0" From bbe76101f6e471ae2952265b29b85938829134bb Mon Sep 17 00:00:00 2001 From: Lance Vincent Salera Date: Wed, 2 Apr 2025 20:50:02 +0800 Subject: [PATCH 14/14] refactor: update phase one and phase two validation functions to use new feature flag names --- src/validation/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 97fae19..4510a63 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -6,8 +6,8 @@ use pallas::{ primitives::TransactionInput, traverse::{wellknown::GenesisValues, MultiEraInput, MultiEraOutput, MultiEraTx}, validate::{ - phase_one, phase_two, - uplc::{script_context::SlotConfig, EvalReport}, + phase1, + phase2::{self, script_context::SlotConfig, EvalReport}, utils::{AccountState, CertState, Environment, UTxOs}, }, }, @@ -61,7 +61,7 @@ pub async fn validate_tx( pallas_utxos.insert(input, output); } - phase_one::validate_tx(tx, 0, &env, &pallas_utxos, &mut CertState::default())?; + phase1::validate_tx(tx, 0, &env, &pallas_utxos, &mut CertState::default())?; Ok(()) } @@ -89,7 +89,7 @@ pub async fn evaluate_tx( .map(|((tx_hash, index), eracbor)| (From::from((*tx_hash, *index)), eracbor.clone())) .collect(); - let report = phase_two::evaluate_tx(tx, &pparams, &utxos, &slot_config) + let report = phase2::evaluate_tx(tx, &pparams, &utxos, &slot_config) .map_err(|e| anyhow::anyhow!("Error evaluating transaction: {:?}", e))?; Ok(report)