From 428572d455834caa739f5abd194c9015d299a06d Mon Sep 17 00:00:00 2001 From: Salka1988 Date: Sat, 1 Mar 2025 00:20:36 +0100 Subject: [PATCH] add google wallet --- e2e/tests/aws.rs | 15 +- packages/fuels-accounts/Cargo.toml | 8 +- packages/fuels-accounts/src/kms/aws/wallet.rs | 24 +- .../fuels-accounts/src/kms/google/client.rs | 5 +- .../fuels-accounts/src/kms/google/wallet.rs | 590 +++++++++--------- packages/fuels/Cargo.toml | 2 +- 6 files changed, 322 insertions(+), 322 deletions(-) diff --git a/e2e/tests/aws.rs b/e2e/tests/aws.rs index 8953b472e..f7583d833 100644 --- a/e2e/tests/aws.rs +++ b/e2e/tests/aws.rs @@ -1,11 +1,12 @@ #[cfg(test)] mod tests { - use std::any::Any; use anyhow::Result; use e2e::e2e_helpers::start_aws_kms; - use fuels::accounts::kms::{AwsWallet, CredentialsFile}; + use fuels::accounts::kms::AwsWallet; use fuels::accounts::{Account, ViewOnlyAccount}; - use fuels::prelude::{launch_provider_and_get_wallet, AssetId, Contract, Error, LoadConfiguration, TxPolicies}; + use fuels::prelude::{ + launch_provider_and_get_wallet, AssetId, Contract, LoadConfiguration, TxPolicies, + }; use fuels::types::errors::Context; #[tokio::test(flavor = "multi_thread")] @@ -72,12 +73,4 @@ mod tests { Ok(()) } - - #[tokio::test(flavor = "multi_thread")] - async fn fund_google_wallet() -> Result<()> { - let credentials_file = CredentialsFile::new().await.map_err(|e| Error::Other(format!("Failed to create credentials file: {}", e)))?; - - - Ok(()) - } } diff --git a/packages/fuels-accounts/Cargo.toml b/packages/fuels-accounts/Cargo.toml index 4bc217fc3..8d9859e38 100644 --- a/packages/fuels-accounts/Cargo.toml +++ b/packages/fuels-accounts/Cargo.toml @@ -25,17 +25,15 @@ fuel-crypto = { workspace = true, features = ["random"] } fuel-tx = { workspace = true } fuel-types = { workspace = true, features = ["random"] } fuels-core = { workspace = true, default-features = false } +google-cloud-kms = { workspace = true, features = ["auth"], optional = true } itertools = { workspace = true } -k256 = { workspace = true, features = ["ecdsa-core"] } +k256 = { workspace = true, features = ["ecdsa-core", "pem"] } rand = { workspace = true, default-features = false } semver = { workspace = true } tai64 = { workspace = true, features = ["serde"] } thiserror = { workspace = true, default-features = false } tokio = { workspace = true, features = ["full"], optional = true } zeroize = { workspace = true, features = ["derive"] } -google-cloud-kms = { workspace = true, features = ["auth"], optional = true } -prost = "0.13.5" -tonic = "0.12.3" [dev-dependencies] fuel-tx = { workspace = true, features = ["test-helpers", "random"] } @@ -54,4 +52,4 @@ std = [ "dep:cynic", ] test-helpers = [] -kms_signer = ["dep:aws-sdk-kms", "dep:aws-config", "dep:google-cloud-kms"] +kms-signer = ["dep:aws-sdk-kms", "dep:aws-config", "dep:google-cloud-kms"] diff --git a/packages/fuels-accounts/src/kms/aws/wallet.rs b/packages/fuels-accounts/src/kms/aws/wallet.rs index 2be1c4f2c..02c20a546 100644 --- a/packages/fuels-accounts/src/kms/aws/wallet.rs +++ b/packages/fuels-accounts/src/kms/aws/wallet.rs @@ -25,9 +25,7 @@ use k256::{ PublicKey as K256PublicKey, }; -/// Error prefix for AWS KMS related operations const AWS_KMS_ERROR_PREFIX: &str = "AWS KMS Error"; -/// Expected key specification for AWS KMS keys const EXPECTED_KEY_SPEC: KeySpec = KeySpec::EccSecgP256K1; /// A wallet implementation that uses AWS KMS for signing @@ -37,7 +35,6 @@ pub struct AwsWallet { kms_key: KmsKey, } -/// Represents an AWS KMS key with Fuel-compatible address #[derive(Clone, Debug)] pub struct KmsKey { key_id: String, @@ -120,15 +117,6 @@ impl KmsKey { Ok(Bech32Address::new(FUEL_BECH32_HRP, fuel_public_key.hash())) } - /// Signs a message using the AWS KMS key - async fn sign_message(&self, message: Message) -> Result { - let signature_der = self.request_kms_signature(message).await?; - let (sig, recovery_id) = self.normalize_signature(&signature_der, message)?; - - Ok(self.convert_to_fuel_signature(sig, recovery_id)) - } - - /// Requests a signature from AWS KMS async fn request_kms_signature(&self, message: Message) -> Result> { let response = self .client @@ -152,6 +140,14 @@ impl KmsKey { }) } + /// Signs a message using the AWS KMS key + async fn sign_message(&self, message: Message) -> Result { + let signature_der = self.request_kms_signature(message).await?; + let (sig, recovery_id) = self.normalize_signature(&signature_der, message)?; + + Ok(self.convert_to_fuel_signature(sig, recovery_id)) + } + /// Normalizes a DER signature and determines the recovery ID fn normalize_signature( &self, @@ -161,7 +157,6 @@ impl KmsKey { let signature = K256Signature::from_der(signature_der) .map_err(|_| Error::Other(format!("{AWS_KMS_ERROR_PREFIX}: Invalid DER signature")))?; - // Ensure the signature is in normalized form (low-S value) let normalized_sig = signature.normalize_s().unwrap_or(signature); let recovery_id = self.determine_recovery_id(&normalized_sig, message)?; @@ -230,13 +225,10 @@ impl AwsWallet { kms_key, }) } - - /// Returns the Fuel address associated with this wallet pub fn address(&self) -> &Bech32Address { &self.kms_key.fuel_address } - /// Returns the provider associated with this wallet, if any pub fn provider(&self) -> Option<&Provider> { self.view_account.provider() } diff --git a/packages/fuels-accounts/src/kms/google/client.rs b/packages/fuels-accounts/src/kms/google/client.rs index 5b6037ec2..15dd1561a 100644 --- a/packages/fuels-accounts/src/kms/google/client.rs +++ b/packages/fuels-accounts/src/kms/google/client.rs @@ -1,6 +1,6 @@ -pub use google_cloud_kms::client::{google_cloud_auth::credentials::CredentialsFile, ClientConfig}; -use google_cloud_kms::client::Client; use fuels_core::types::errors::{Error, Result}; +use google_cloud_kms::client::Client; +pub use google_cloud_kms::client::{google_cloud_auth::credentials::CredentialsFile, ClientConfig}; #[derive(Clone, Debug)] pub struct GoogleClient { @@ -9,7 +9,6 @@ pub struct GoogleClient { impl GoogleClient { pub async fn new(config: ClientConfig) -> Result { - let config_debug = format!("{:?}", config); let client = Client::new(config).await.map_err(|e| { diff --git a/packages/fuels-accounts/src/kms/google/wallet.rs b/packages/fuels-accounts/src/kms/google/wallet.rs index 67f1f3818..c2831e989 100644 --- a/packages/fuels-accounts/src/kms/google/wallet.rs +++ b/packages/fuels-accounts/src/kms/google/wallet.rs @@ -1,286 +1,304 @@ -// use crate::accounts_utils::try_provider_error; -// use crate::kms::google::client::GoogleClient; -// use crate::provider::Provider; -// use crate::wallet::Wallet; -// use crate::{Account, ViewOnlyAccount}; -// use fuel_crypto::{Message, PublicKey, Signature}; -// use fuel_types::AssetId; -// use google_cloud_kms::grpc::kms::v1::crypto_key_version::CryptoKeyVersionAlgorithm::EcSignSecp256k1Sha256; -// use google_cloud_kms::grpc::kms::v1::{AsymmetricSignRequest, GetPublicKeyRequest}; -// use google_cloud_kms::grpc::kms::v1::digest::Digest::Sha256; -// use fuels_core::{ -// traits::Signer, -// types::{ -// bech32::{Bech32Address, FUEL_BECH32_HRP}, -// coin_type_id::CoinTypeId, -// errors::{Error, Result}, -// input::Input, -// transaction_builders::TransactionBuilder, -// }, -// }; -// use k256::{ -// ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey}, -// pkcs8::DecodePublicKey, -// PublicKey as K256PublicKey, -// }; -// use prost::Message as ProstMessage; -// use tonic::transport::Channel; -// -// /// Error prefix for Google KMS related operations -// const GOOGLE_KMS_ERROR_PREFIX: &str = "Google KMS Error"; -// -// /// A wallet implementation that uses Google Cloud KMS for signing -// #[derive(Clone, Debug)] -// pub struct GoogleWallet { -// view_account: Wallet, -// kms_key: GcpKey, -// } -// -// /// Represents a Google Cloud KMS key with Fuel-compatible address -// #[derive(Clone, Debug)] -// pub struct GcpKey { -// key_path: String, -// client: GoogleClient, -// public_key_pem: String, -// fuel_address: Bech32Address, -// } -// -// impl GcpKey { -// pub fn key_path(&self) -> &String { -// &self.key_path -// } -// -// pub fn public_key(&self) -> &String { -// &self.public_key_pem -// } -// -// pub fn fuel_address(&self) -> &Bech32Address { -// &self.fuel_address -// } -// -// /// Creates a new GcpKey from a Google Cloud KMS key path -// /// The key_path should be in the format: projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{key}/cryptoKeyVersions/{version} -// pub async fn new(key_path: String, client: &GoogleClient) -> Result { -// // Validate the key spec by attempting to retrieve the public key -// let public_key_pem = Self::retrieve_public_key(client, &key_path).await?; -// -// // Derive the Fuel address from the public key -// let fuel_address = Self::derive_fuel_address(&public_key_pem)?; -// -// Ok(Self { -// key_path, -// client: client.clone(), -// public_key_pem, -// fuel_address, -// }) -// } -// -// /// Retrieves the public key from Google Cloud KMS -// async fn retrieve_public_key(client: &GoogleClient, key_path: &str) -> Result { -// let request = GetPublicKeyRequest { -// name: key_path.to_string(), -// }; -// -// let response = client -// .inner() -// .get_public_key(request, None) -// .await -// .map_err(|e| format_gcp_error(format!("Failed to get public key: {}", e)))?; -// -// let key_spec = response.algorithm; -// // Check if the key is EC_SIGN_SECP256K1_SHA256 -// if key_spec != EcSignSecp256k1Sha256 as i32 { -// return Err(Error::Other(format!( -// "{GOOGLE_KMS_ERROR_PREFIX}: Invalid key algorithm: {}, expected EC_SIGN_SECP256K1_SHA256", -// key_spec -// ))); -// } -// -// Ok(response.pem) -// } -// -// /// Derives a Fuel address from a public key in PEM format -// fn derive_fuel_address(pem: &str) -> Result { -// let k256_key = K256PublicKey::from_public_key_pem(pem) -// .map_err(|_| Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid PEM encoding")))?; -// -// let fuel_public_key = PublicKey::from(k256_key); -// Ok(Bech32Address::new(FUEL_BECH32_HRP, fuel_public_key.hash())) -// } -// -// /// Signs a message using the Google Cloud KMS key -// async fn sign_message(&self, message: Message) -> Result { -// let signature_der = self.request_gcp_signature(message).await?; -// let (sig, recovery_id) = self.normalize_signature(&signature_der, message)?; -// -// Ok(self.convert_to_fuel_signature(sig, recovery_id)) -// } -// -// /// Requests a signature from Google Cloud KMS -// async fn request_gcp_signature(&self, message: Message) -> Result> { -// let digest = GcpDigest { -// digest: vec![ -// Sha256(message.as_ref().to_vec()), -// ], -// }; -// -// let request = AsymmetricSignRequest { -// name: self.key_path.clone(), -// digest: Some(digest), -// digest_crc32c: None, -// data: vec![], -// data_crc32c: None, -// }; -// -// let response = self -// .client -// .inner() -// .asymmetric_sign(request, None) -// .await -// .map_err(|e| format_gcp_error(format!("Signing failed: {}", e)))?; -// -// let signature = response.signature; -// if signature.is_empty() { -// return Err(Error::Other(format!( -// "{GOOGLE_KMS_ERROR_PREFIX}: Empty signature response" -// ))); -// } -// -// Ok(signature) -// } -// -// /// Normalizes a DER signature and determines the recovery ID -// fn normalize_signature( -// &self, -// signature_der: &[u8], -// message: Message, -// ) -> Result<(K256Signature, RecoveryId)> { -// let signature = K256Signature::from_der(signature_der) -// .map_err(|_| Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid DER signature")))?; -// -// // Ensure the signature is in normalized form (low-S value) -// let normalized_sig = signature.normalize_s().unwrap_or(signature); -// let recovery_id = self.determine_recovery_id(&normalized_sig, message)?; -// -// Ok((normalized_sig, recovery_id)) -// } -// -// /// Determines the correct recovery ID for the signature -// fn determine_recovery_id(&self, sig: &K256Signature, message: Message) -> Result { -// let recid_even = RecoveryId::new(false, false); -// let recid_odd = RecoveryId::new(true, false); -// -// // Get the expected public key -// let expected_pubkey = K256PublicKey::from_public_key_pem(&self.public_key_pem) -// .map_err(|_| { -// Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid cached public key")) -// })? -// .into(); -// -// // Try recovery with each recovery ID -// let recovered_even = VerifyingKey::recover_from_prehash(&*message, sig, recid_even); -// let recovered_odd = VerifyingKey::recover_from_prehash(&*message, sig, recid_odd); -// -// if recovered_even -// .map(|r| r == expected_pubkey) -// .unwrap_or(false) -// { -// Ok(recid_even) -// } else if recovered_odd.map(|r| r == expected_pubkey).unwrap_or(false) { -// Ok(recid_odd) -// } else { -// Err(Error::Other(format!( -// "{GOOGLE_KMS_ERROR_PREFIX}: Invalid signature (could not recover correct public key)" -// ))) -// } -// } -// -// /// Converts a k256 signature to a Fuel signature format -// fn convert_to_fuel_signature( -// &self, -// signature: K256Signature, -// recovery_id: RecoveryId, -// ) -> Signature { -// let recovery_byte = recovery_id.is_y_odd() as u8; -// let mut bytes: [u8; 64] = signature.to_bytes().into(); -// bytes[32] = (recovery_byte << 7) | (bytes[32] & 0x7F); -// Signature::from_bytes(bytes) -// } -// -// /// Returns the Fuel address associated with this key -// pub fn address(&self) -> &Bech32Address { -// &self.fuel_address -// } -// } -// -// impl GoogleWallet { -// /// Creates a new GoogleWallet with the given KMS key path -// /// The key_path should be in the format: projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{key}/cryptoKeyVersions/{version} -// pub async fn with_kms_key( -// key_path: impl Into, -// google_client: &GoogleClient, -// provider: Option, -// ) -> Result { -// let kms_key = GcpKey::new(key_path.into(), google_client).await?; -// -// Ok(Self { -// view_account: Wallet::from_address(kms_key.fuel_address.clone(), provider), -// kms_key, -// }) -// } -// -// /// Returns the Fuel address associated with this wallet -// pub fn address(&self) -> &Bech32Address { -// &self.kms_key.fuel_address -// } -// -// /// Returns the provider associated with this wallet, if any -// pub fn provider(&self) -> Option<&Provider> { -// self.view_account.provider() -// } -// } -// -// #[async_trait::async_trait] -// impl Signer for GoogleWallet { -// async fn sign(&self, message: Message) -> Result { -// self.kms_key.sign_message(message).await -// } -// -// fn address(&self) -> &Bech32Address { -// self.address() -// } -// } -// -// #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -// impl ViewOnlyAccount for GoogleWallet { -// fn address(&self) -> &Bech32Address { -// &self.kms_key.fuel_address -// } -// -// fn try_provider(&self) -> Result<&Provider> { -// self.provider().ok_or_else(try_provider_error) -// } -// -// async fn get_asset_inputs_for_amount( -// &self, -// asset_id: AssetId, -// amount: u64, -// excluded_coins: Option>, -// ) -> Result> { -// self.view_account -// .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) -// .await -// } -// } -// -// #[async_trait::async_trait] -// impl Account for GoogleWallet { -// fn add_witnesses(&self, tb: &mut Tb) -> Result<()> { -// tb.add_signer(self.clone())?; -// Ok(()) -// } -// } -// -// fn format_gcp_error(err: impl std::fmt::Display) -> Error { -// Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: {err}")) -// } +use crate::accounts_utils::try_provider_error; +use crate::kms::google::client::GoogleClient; +use crate::provider::Provider; +use crate::wallet::Wallet; +use crate::{Account, ViewOnlyAccount}; +use fuel_crypto::{Message, PublicKey, Signature}; +use fuel_types::AssetId; +use fuels_core::{ + traits::Signer, + types::{ + bech32::{Bech32Address, FUEL_BECH32_HRP}, + coin_type_id::CoinTypeId, + errors::{Error, Result}, + input::Input, + transaction_builders::TransactionBuilder, + }, +}; +use google_cloud_kms::grpc::kms::v1::crypto_key_version::CryptoKeyVersionAlgorithm::EcSignSecp256k1Sha256; +use google_cloud_kms::grpc::kms::v1::digest::Digest::Sha256; +use google_cloud_kms::grpc::kms::v1::{AsymmetricSignRequest, Digest, GetPublicKeyRequest}; +use k256::{ + ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey}, + pkcs8::DecodePublicKey, + PublicKey as K256PublicKey, +}; + +const GOOGLE_KMS_ERROR_PREFIX: &str = "Google KMS Error"; + +/// A wallet implementation that uses Google Cloud KMS for signing +#[derive(Clone, Debug)] +pub struct GoogleWallet { + view_account: Wallet, + kms_key: GcpKey, +} + +#[derive(Clone, Debug)] +pub struct GcpKey { + key_path: String, + client: GoogleClient, + public_key_pem: String, + fuel_address: Bech32Address, +} + +#[derive(Debug, Clone)] +pub struct CryptoKeyVersionName { + pub project_id: String, + pub location: String, + pub key_ring: String, + pub key_id: String, + pub key_version: String, +} + +impl CryptoKeyVersionName { + pub fn new( + project_id: impl Into, + location: impl Into, + key_ring: impl Into, + key_id: impl Into, + key_version: impl Into, + ) -> Self { + Self { + project_id: project_id.into(), + location: location.into(), + key_ring: key_ring.into(), + key_id: key_id.into(), + key_version: key_version.into(), + } + } +} + +impl std::fmt::Display for CryptoKeyVersionName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}/cryptoKeyVersions/{}", + self.project_id, self.location, self.key_ring, self.key_id, self.key_version + ) + } +} + +impl GcpKey { + pub fn key_path(&self) -> &String { + &self.key_path + } + + pub fn public_key(&self) -> &String { + &self.public_key_pem + } + + pub fn fuel_address(&self) -> &Bech32Address { + &self.fuel_address + } + + /// Creates a new GcpKey from a Google Cloud KMS key path. + /// The key_path should be in the format: + /// projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{key}/cryptoKeyVersions/{version} + pub async fn new(key_path: String, client: &GoogleClient) -> Result { + let public_key_pem = Self::retrieve_public_key(client, &key_path).await?; + let fuel_address = Self::derive_fuel_address(&public_key_pem)?; + Ok(Self { + key_path, + client: client.clone(), + public_key_pem, + fuel_address, + }) + } + + /// Retrieves the public key PEM from Google Cloud KMS and verifies the key algorithm. + async fn retrieve_public_key(client: &GoogleClient, key_path: &str) -> Result { + let request = GetPublicKeyRequest { + name: key_path.to_string(), + }; + + let response = client + .inner() + .get_public_key(request, None) + .await + .map_err(|e| format_gcp_error(format!("Failed to get public key: {}", e)))?; + + dbg!(&response); + + // Check that the key algorithm matches EC_SIGN_SECP256K1_SHA256. + if response.algorithm != EcSignSecp256k1Sha256 as i32 { + return Err(Error::Other(format!( + "{GOOGLE_KMS_ERROR_PREFIX}: Invalid key algorithm: {}, expected EC_SIGN_SECP256K1_SHA256", + response.algorithm + ))); + } + Ok(response.pem) + } + + /// Derives a Fuel address from the PEM-encoded public key. + fn derive_fuel_address(pem: &str) -> Result { + let k256_key = K256PublicKey::from_public_key_pem(pem).map_err(|_| { + Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid PEM encoding")) + })?; + let fuel_public_key = PublicKey::from(k256_key); + Ok(Bech32Address::new(FUEL_BECH32_HRP, fuel_public_key.hash())) + } + + /// Requests a signature from Google Cloud KMS. + async fn request_gcp_signature(&self, message: Message) -> Result> { + let digest = Digest { + digest: Some(Sha256(message.as_ref().to_vec())), + }; + + let request = AsymmetricSignRequest { + name: self.key_path.clone(), + digest: Some(digest), + digest_crc32c: None, + ..AsymmetricSignRequest::default() + }; + + let response = self + .client + .inner() + .asymmetric_sign(request, None) + .await + .map_err(|e| format_gcp_error(format!("Signing failed: {}", e)))?; + + if response.signature.is_empty() { + return Err(Error::Other(format!( + "{GOOGLE_KMS_ERROR_PREFIX}: Empty signature response" + ))); + } + Ok(response.signature) + } + + /// Signs the given message by requesting a signature from Google Cloud KMS, + /// then normalizing the DER signature and determining the recovery ID. + async fn sign_message(&self, message: Message) -> Result { + let signature_der = self.request_gcp_signature(message).await?; + let (normalized_sig, recovery_id) = self.normalize_signature(&signature_der, message)?; + Ok(self.convert_to_fuel_signature(normalized_sig, recovery_id)) + } + + /// Normalizes a DER signature and determines the recovery ID. + fn normalize_signature( + &self, + signature_der: &[u8], + message: Message, + ) -> Result<(K256Signature, RecoveryId)> { + let signature = K256Signature::from_der(signature_der).map_err(|_| { + Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: Invalid DER signature")) + })?; + + let normalized_sig = signature.normalize_s().unwrap_or(signature); + let recovery_id = self.determine_recovery_id(&normalized_sig, message)?; + + Ok((normalized_sig, recovery_id)) + } + + /// Determines the correct recovery ID for the signature by comparing + /// the recovered public key with the expected public key. + fn determine_recovery_id(&self, sig: &K256Signature, message: Message) -> Result { + let recid_even = RecoveryId::new(false, false); + let recid_odd = RecoveryId::new(true, false); + + let expected_pubkey = + K256PublicKey::from_public_key_pem(&self.public_key_pem).map_err(|_| { + Error::Other(format!( + "{GOOGLE_KMS_ERROR_PREFIX}: Invalid cached public key" + )) + })?; + let expected_verifying_key: VerifyingKey = expected_pubkey.into(); + + let recovered_even = VerifyingKey::recover_from_prehash(&*message, sig, recid_even); + let recovered_odd = VerifyingKey::recover_from_prehash(&*message, sig, recid_odd); + + if recovered_even + .map(|r| r == expected_verifying_key) + .unwrap_or(false) + { + Ok(recid_even) + } else if recovered_odd + .map(|r| r == expected_verifying_key) + .unwrap_or(false) + { + Ok(recid_odd) + } else { + Err(Error::Other(format!( + "{GOOGLE_KMS_ERROR_PREFIX}: Invalid signature (could not recover correct public key)" + ))) + } + } + + /// Converts the DER signature and recovery ID to a Fuel-compatible signature. + fn convert_to_fuel_signature( + &self, + signature: K256Signature, + recovery_id: RecoveryId, + ) -> Signature { + let recovery_byte = recovery_id.is_y_odd() as u8; + let mut bytes: [u8; 64] = signature.to_bytes().into(); + bytes[32] = (recovery_byte << 7) | (bytes[32] & 0x7F); + Signature::from_bytes(bytes) + } +} + +impl GoogleWallet { + /// Creates a new GoogleWallet with the given KMS key path. + pub async fn with_kms_key( + key_path: CryptoKeyVersionName, + google_client: &GoogleClient, + provider: Option, + ) -> Result { + let kms_key = GcpKey::new(key_path.to_string(), google_client).await?; + Ok(Self { + view_account: Wallet::from_address(kms_key.fuel_address.clone(), provider), + kms_key, + }) + } + pub fn address(&self) -> &Bech32Address { + &self.kms_key.fuel_address + } + pub fn provider(&self) -> Option<&Provider> { + self.view_account.provider() + } +} + +#[async_trait::async_trait] +impl Signer for GoogleWallet { + async fn sign(&self, message: Message) -> Result { + self.kms_key.sign_message(message).await + } + + fn address(&self) -> &Bech32Address { + self.address() + } +} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] +impl ViewOnlyAccount for GoogleWallet { + fn address(&self) -> &Bech32Address { + &self.kms_key.fuel_address + } + + fn try_provider(&self) -> Result<&Provider> { + self.provider().ok_or_else(try_provider_error) + } + + async fn get_asset_inputs_for_amount( + &self, + asset_id: AssetId, + amount: u64, + excluded_coins: Option>, + ) -> Result> { + self.view_account + .get_asset_inputs_for_amount(asset_id, amount, excluded_coins) + .await + } +} + +#[async_trait::async_trait] +impl Account for GoogleWallet { + fn add_witnesses(&self, tb: &mut Tb) -> Result<()> { + tb.add_signer(self.clone())?; + Ok(()) + } +} + +fn format_gcp_error(err: impl std::fmt::Display) -> Error { + Error::Other(format!("{GOOGLE_KMS_ERROR_PREFIX}: {err}")) +} diff --git a/packages/fuels/Cargo.toml b/packages/fuels/Cargo.toml index b5dec9ac2..6c84c2844 100644 --- a/packages/fuels/Cargo.toml +++ b/packages/fuels/Cargo.toml @@ -41,4 +41,4 @@ std = [ ] fuel-core-lib = ["fuels-test-helpers?/fuel-core-lib"] rocksdb = ["fuels-test-helpers?/rocksdb"] -kms-signer = ["fuels-accounts/kms-signer"] \ No newline at end of file +kms-signer = ["fuels-accounts/kms-signer"]