Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

funds-manager: Ingest API changes from key rotation #39

Merged
merged 1 commit into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 40 additions & 118 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion funds-manager/funds-manager-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ postgres-native-tls = "0.5"
tokio-postgres = "0.7.7"

# === Blockchain Interaction === #
alloy-sol-types = "0.3.1"
alloy-sol-types = "=0.7.7"
ethers = "2"

# === Renegade Dependencies === #
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl CustodyClient {

// Filter out those that don't need refilling
let mut wallets_to_fill: Vec<(String, f64)> = Vec::new(); // (address, fill amount)
for wallet in active_wallets {
for wallet in gas_wallets.into_iter() {
let bal = self.get_ether_balance(&wallet.address).await?;
if bal + GAS_REFILL_TOLERANCE < fill_to {
wallets_to_fill.push((wallet.address, fill_to - bal));
Expand Down Expand Up @@ -92,7 +92,7 @@ impl CustodyClient {

// Update the gas wallet to be active, top up wallets, and return the key
self.mark_gas_wallet_active(&gas_wallet.address, peer_id).await?;
self.refill_gas_for_active_wallets(DEFAULT_TOP_UP_AMOUNT).await?;
self.refill_gas_wallets(DEFAULT_TOP_UP_AMOUNT).await?;
Ok(secret_value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use diesel::{ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use renegade_util::err_str;
use tracing::info;
use uuid::Uuid;

use crate::db::models::{GasWallet, GasWalletStatus, HotWallet};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl CustodyClient {
// Execute the erc20 transfer
let tx = self.erc20_transfer(token_address, destination_address, amount, wallet).await?;
info!(
"Withdrew {amount} {token_address} from hot wallet to {destination_address}. Tx: {:#}",
"Withdrew {amount} {token_address} from hot wallet to {destination_address}. Tx: {:?}",
tx.transaction_hash
);

Expand Down
21 changes: 14 additions & 7 deletions funds-manager/funds-manager-server/src/fee_indexer/fee_balances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use ethers::{
utils::keccak256,
};
use num_bigint::BigUint;
use renegade_api::{http::wallet::WithdrawBalanceRequest, types::ApiWallet};
use renegade_api::{
http::wallet::{WalletUpdateAuthorization, WithdrawBalanceRequest},
types::ApiWallet,
};
use renegade_arbitrum_client::{
conversion::to_contract_external_transfer, helpers::serialize_calldata,
};
Expand Down Expand Up @@ -52,16 +55,15 @@ impl Indexer {
let wallet_metadata = self.get_wallet_by_id(&wallet_id).await?;
let api_wallet = self.fetch_wallet(wallet_metadata.clone()).await?;
let old_wallet = Wallet::try_from(api_wallet).map_err(FundsManagerError::custom)?;
let root_key =
old_wallet.key_chain.secret_keys.sk_root.as_ref().expect("root key not present");
let wallet_key = old_wallet.key_chain.symmetric_key();

// Get the deposit address for the fee withdrawal
let deposit_address =
self.custody_client.get_deposit_address(DepositWithdrawSource::FeeRedemption).await?;

// Send a withdrawal request to the relayer
let req = Self::build_withdrawal_request(&mint, &deposit_address, &old_wallet)?;
self.relayer_client.withdraw_balance(wallet_metadata.id, mint, req, root_key).await?;
self.relayer_client.withdraw_balance(wallet_metadata.id, mint, req, &wallet_key).await?;

Ok(())
}
Expand All @@ -85,12 +87,12 @@ impl Indexer {
// Derive the wallet keychain
let wallet_keychain =
derive_wallet_keychain(&eth_key, self.chain_id).map_err(FundsManagerError::custom)?;
let root_key = wallet_keychain.secret_keys.sk_root.clone().expect("root key not present");
let wallet_key = wallet_keychain.symmetric_key();

// Fetch the wallet from the relayer and replace the keychain so that we have
// access to the full set of secret keys
let mut wallet =
self.relayer_client.get_wallet(wallet_metadata.id, &root_key).await?.wallet;
self.relayer_client.get_wallet(wallet_metadata.id, &wallet_key).await?.wallet;
wallet.key_chain = wallet_keychain.into();

Ok(wallet)
Expand Down Expand Up @@ -130,10 +132,15 @@ impl Indexer {
dest_bigint.clone(),
)?;

let update_auth = WalletUpdateAuthorization {
statement_sig: commitment_sig.to_vec(),
new_root_key: None,
};

Ok(WithdrawBalanceRequest {
destination_addr: dest_bigint,
amount: BigUint::from(bal.amount),
wallet_commitment_sig: commitment_sig.to_vec(),
update_auth,
external_transfer_sig: transfer_sig.to_vec(),
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use renegade_circuit_types::wallet::NoteCommitment;
use renegade_constants::Scalar;
use renegade_crypto::fields::{scalar_to_biguint, scalar_to_u128, u256_to_scalar};
use renegade_util::err_str;
use tracing::info;
use tracing::{info, warn};

use crate::db::models::NewFee;
use crate::error::FundsManagerError;
Expand All @@ -29,10 +29,12 @@ impl Indexer {
let block_number = self.get_latest_block().await?;
info!("indexing fees from block {block_number}");

let darkpool_addr = self.arbitrum_client.get_darkpool_client().address();
let filter = self
.arbitrum_client
.get_darkpool_client()
.event::<NotePostedFilter>()
.address(darkpool_addr.into())
.from_block(block_number);

let events = filter
Expand Down Expand Up @@ -154,6 +156,11 @@ impl Indexer {
note: &ElGamalCiphertext<NOTE_CIPHERTEXT_SIZE>,
note_comm: NoteCommitment,
) -> Option<Note> {
if !note.is_valid_ciphertext() {
warn!("invalid note ciphertext, skipping decryption...");
return None;
}

// The ciphertext stores all note values except the encryption key
for key in self.decryption_keys.iter() {
let note = self.decrypt_note_with_key(note, key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl Indexer {
// Get the wallet key for the given wallet
let eth_key = self.get_wallet_private_key(&wallet).await?;
let wallet_keychain = derive_wallet_keychain(&eth_key, self.chain_id).unwrap();
let root_key = wallet_keychain.secret_keys.sk_root.clone().unwrap();
let wallet_key = wallet_keychain.symmetric_key();

self.relayer_client.check_wallet_indexed(wallet.id, self.chain_id, &eth_key).await?;

Expand All @@ -152,7 +152,7 @@ impl Indexer {

// Redeem the note through the relayer
let req = RedeemNoteRequest { note: note.clone(), decryption_key: *key };
self.relayer_client.redeem_note(wallet.id, req, &root_key).await?;
self.relayer_client.redeem_note(wallet.id, req, &wallet_key).await?;

// Mark the fee as redeemed
self.maybe_mark_redeemed(&tx, &note).await?;
Expand All @@ -172,6 +172,7 @@ impl Indexer {
.await
.map_err(err_str!(FundsManagerError::Arbitrum))?
{
warn!("nullifier not seen on-chain after redemption, tx: {tx_hash}");
return Ok(());
}

Expand Down
51 changes: 24 additions & 27 deletions funds-manager/funds-manager-server/src/relayer_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use std::time::Duration;

use base64::engine::{general_purpose as b64_general_purpose, Engine};
use ethers::{
core::k256::ecdsa::{signature::Signer, Signature, SigningKey},
signers::LocalWallet,
};
use ethers::signers::LocalWallet;
use http::{HeaderMap, HeaderValue};
use renegade_api::{
http::{
Expand All @@ -19,16 +16,17 @@ use renegade_api::{
REDEEM_NOTE_ROUTE, WITHDRAW_BALANCE_ROUTE,
},
},
types::ApiKeychain,
RENEGADE_AUTH_HEADER_NAME, RENEGADE_SIG_EXPIRATION_HEADER_NAME,
};
use renegade_circuit_types::keychain::SecretSigningKey;
use renegade_common::types::{
exchange::PriceReporterState,
token::Token,
wallet::{
derivation::{
derive_blinder_seed, derive_share_seed, derive_wallet_id, derive_wallet_keychain,
},
keychain::HmacKey,
Wallet, WalletIdentifier,
},
};
Expand Down Expand Up @@ -90,11 +88,11 @@ impl RelayerClient {
pub async fn get_wallet(
&self,
wallet_id: WalletIdentifier,
root_key: &SecretSigningKey,
wallet_key: &HmacKey,
) -> Result<GetWalletResponse, FundsManagerError> {
let mut path = GET_WALLET_ROUTE.to_string();
path = path.replace(":wallet_id", &wallet_id.to_string());
self.get_relayer_with_auth::<GetWalletResponse>(&path, root_key).await
self.get_relayer_with_auth::<GetWalletResponse>(&path, wallet_key).await
}

/// Check that the relayer has a given wallet, lookup the wallet if not
Expand All @@ -108,8 +106,8 @@ impl RelayerClient {
path = path.replace(":wallet_id", &wallet_id.to_string());

let keychain = derive_wallet_keychain(eth_key, chain_id).unwrap();
let root_key = keychain.secret_keys.sk_root.unwrap();
if self.get_relayer_with_auth::<GetWalletResponse>(&path, &root_key).await.is_ok() {
let wallet_key = keychain.symmetric_key();
if self.get_relayer_with_auth::<GetWalletResponse>(&path, &wallet_key).await.is_ok() {
return Ok(());
}

Expand All @@ -128,16 +126,17 @@ impl RelayerClient {
let blinder_seed = derive_blinder_seed(eth_key).unwrap();
let share_seed = derive_share_seed(eth_key).unwrap();
let keychain = derive_wallet_keychain(eth_key, chain_id).unwrap();
let root_key = keychain.secret_keys.sk_root.clone().unwrap();
let wallet_key = keychain.symmetric_key();

let body = FindWalletRequest {
wallet_id,
secret_share_seed: scalar_to_biguint(&share_seed),
blinder_seed: scalar_to_biguint(&blinder_seed),
key_chain: keychain.into(),
private_keychain: ApiKeychain::from(keychain).private_keys,
};

let resp: FindWalletResponse = self.post_relayer_with_auth(&path, &body, &root_key).await?;
let resp: FindWalletResponse =
self.post_relayer_with_auth(&path, &body, &wallet_key).await?;
self.await_relayer_task(resp.task_id).await
}

Expand All @@ -154,12 +153,12 @@ impl RelayerClient {
&self,
wallet_id: WalletIdentifier,
req: RedeemNoteRequest,
root_key: &SecretSigningKey,
wallet_key: &HmacKey,
) -> Result<(), FundsManagerError> {
let mut path = REDEEM_NOTE_ROUTE.to_string();
path = path.replace(":wallet_id", &wallet_id.to_string());

let resp: RedeemNoteResponse = self.post_relayer_with_auth(&path, &req, root_key).await?;
let resp: RedeemNoteResponse = self.post_relayer_with_auth(&path, &req, wallet_key).await?;
self.await_relayer_task(resp.task_id).await
}

Expand All @@ -169,7 +168,7 @@ impl RelayerClient {
wallet_id: WalletIdentifier,
mint: String,
req: WithdrawBalanceRequest,
root_key: &SecretSigningKey,
root_key: &HmacKey,
) -> Result<(), FundsManagerError> {
let mut path = WITHDRAW_BALANCE_ROUTE.to_string();
path = path.replace(":wallet_id", &wallet_id.to_string());
Expand Down Expand Up @@ -202,15 +201,15 @@ impl RelayerClient {
&self,
path: &str,
body: &Req,
root_key: &SecretSigningKey,
wallet_key: &HmacKey,
) -> Result<Resp, FundsManagerError>
where
Req: Serialize,
Resp: for<'de> Deserialize<'de>,
{
let body_ser = serde_json::to_vec(body).map_err(err_str!(FundsManagerError::Custom))?;
let headers =
build_auth_headers(root_key, &body_ser).map_err(err_str!(FundsManagerError::custom))?;
let headers = build_auth_headers(wallet_key, &body_ser)
.map_err(err_str!(FundsManagerError::custom))?;
self.post_relayer_with_headers(path, body, &headers).await
}

Expand Down Expand Up @@ -261,13 +260,13 @@ impl RelayerClient {
async fn get_relayer_with_auth<Resp>(
&self,
path: &str,
root_key: &SecretSigningKey,
wallet_key: &HmacKey,
) -> Result<Resp, FundsManagerError>
where
Resp: for<'de> Deserialize<'de>,
{
let headers =
build_auth_headers(root_key, &[]).map_err(err_str!(FundsManagerError::Custom))?;
build_auth_headers(wallet_key, &[]).map_err(err_str!(FundsManagerError::Custom))?;
self.get_relayer_with_headers(path, &headers).await
}

Expand Down Expand Up @@ -336,21 +335,19 @@ fn reqwest_client() -> Result<Client, FundsManagerError> {
}

/// Build authentication headers for a request
fn build_auth_headers(key: &SecretSigningKey, req_bytes: &[u8]) -> Result<HeaderMap, String> {
fn build_auth_headers(key: &HmacKey, req_bytes: &[u8]) -> Result<HeaderMap, String> {
let mut headers = HeaderMap::new();
let expiration = get_current_time_millis() + SIG_EXPIRATION_BUFFER_MS;
headers.insert(RENEGADE_SIG_EXPIRATION_HEADER_NAME, expiration.into());

let root_key: SigningKey = key.try_into()?;

// Sign the concatenation of the message and the expiration timestamp
// Concatenate the message and the timestamp
let body = Body::from(req_bytes.to_vec());
let msg_bytes = body.as_bytes().unwrap();
let payload = [msg_bytes, &expiration.to_le_bytes()].concat();

let signature: Signature = root_key.sign(&payload);
let encoded_sig = b64_general_purpose::STANDARD_NO_PAD.encode(signature.to_bytes());

// Compute the hmac
let hmac = key.compute_mac(&payload);
let encoded_sig = b64_general_purpose::STANDARD_NO_PAD.encode(hmac);
headers.insert(RENEGADE_AUTH_HEADER_NAME, HeaderValue::from_str(&encoded_sig).unwrap());

Ok(headers)
Expand Down
Loading