From 8c87e192c7fd9b92af60b104bcf7eb37eb4856f5 Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Fri, 14 Apr 2023 18:42:05 +1000 Subject: [PATCH 1/2] pull latest block and key nonce --- mpc-recovery/src/client.rs | 91 ++++++++++++++ mpc-recovery/src/leader_node/mod.rs | 177 +++++++++++++++++----------- mpc-recovery/src/lib.rs | 1 + 3 files changed, 202 insertions(+), 67 deletions(-) create mode 100644 mpc-recovery/src/client.rs diff --git a/mpc-recovery/src/client.rs b/mpc-recovery/src/client.rs new file mode 100644 index 000000000..647e61ad4 --- /dev/null +++ b/mpc-recovery/src/client.rs @@ -0,0 +1,91 @@ +use near_jsonrpc_client::{methods, JsonRpcClient}; +use near_jsonrpc_primitives::types::query::QueryResponseKind; +use near_primitives::hash::CryptoHash; +use near_primitives::types::{AccountId, Finality}; +use near_primitives::views::{AccessKeyView, QueryRequest}; + +#[derive(Clone)] +pub struct NearRpcClient { + rpc_client: JsonRpcClient, +} + +impl NearRpcClient { + pub fn testnet() -> Self { + Self { + rpc_client: JsonRpcClient::connect("https://rpc.testnet.near.org"), + } + } + + async fn access_key( + &self, + account_id: AccountId, + public_key: near_crypto::PublicKey, + ) -> anyhow::Result<(AccessKeyView, CryptoHash)> { + let query_resp = self + .rpc_client + .call(&methods::query::RpcQueryRequest { + block_reference: Finality::None.into(), + request: QueryRequest::ViewAccessKey { + account_id, + public_key, + }, + }) + .await + .map_err(|e| anyhow::anyhow!("failed to query access key {}", e))?; + + match query_resp.kind { + QueryResponseKind::AccessKey(access_key) => Ok((access_key, query_resp.block_hash)), + _ => Err(anyhow::anyhow!( + "query returned invalid data while querying access key" + )), + } + } + + pub async fn access_key_nonce( + &self, + account_id: AccountId, + public_key: near_crypto::PublicKey, + ) -> anyhow::Result { + let key = self.access_key(account_id, public_key).await?; + Ok(key.0.nonce) + } + + pub async fn latest_block_hash(&self) -> anyhow::Result { + let block_view = self + .rpc_client + .call(&methods::block::RpcBlockRequest { + block_reference: Finality::Final.into(), + }) + .await?; + Ok(block_view.header.hash) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_latest_block() -> anyhow::Result<()> { + let testnet = NearRpcClient::testnet(); + let block_hash = testnet.latest_block_hash().await?; + + assert!(block_hash.0.len() == 32); + Ok(()) + } + + #[tokio::test] + async fn test_access_key() -> anyhow::Result<()> { + let testnet = NearRpcClient::testnet(); + let nonce = testnet + .access_key_nonce( + "dev-1636354824855-78504059330123".parse()?, + "ed25519:8n5HXTibTDtXKAnEUPFUXXJoKqa5A1c2vWXt6LbRAcGn".parse()?, + ) + .await?; + + // Assuming no one will use this account ever again + assert_eq!(nonce, 70526114000002); + Ok(()) + } +} diff --git a/mpc-recovery/src/leader_node/mod.rs b/mpc-recovery/src/leader_node/mod.rs index 7930ff63f..9e4838e55 100644 --- a/mpc-recovery/src/leader_node/mod.rs +++ b/mpc-recovery/src/leader_node/mod.rs @@ -1,3 +1,4 @@ +use crate::client::NearRpcClient; use crate::msg::{ AddKeyRequest, AddKeyResponse, LeaderRequest, LeaderResponse, NewAccountRequest, NewAccountResponse, SigShareRequest, SigShareResponse, @@ -12,7 +13,6 @@ use futures::stream::FuturesUnordered; use hyper::client::ResponseFuture; use hyper::{Body, Client, Method, Request}; use near_crypto::{PublicKey, SecretKey}; -use near_primitives::hash::CryptoHash; use near_primitives::types::AccountId; use std::collections::btree_map::Entry; use std::collections::BTreeMap; @@ -41,6 +41,7 @@ pub async fn run( pk_set, sk_share, sign_nodes, + client: NearRpcClient::testnet(), root_secret_key, }; @@ -63,6 +64,7 @@ struct LeaderState { pk_set: PublicKeySet, sk_share: SecretKeyShare, sign_nodes: Vec, + client: NearRpcClient, // TODO: temporary solution root_secret_key: ed25519_dalek::SecretKey, } @@ -74,6 +76,7 @@ impl Clone for LeaderState { pk_set: self.pk_set.clone(), sk_share: self.sk_share.clone(), sign_nodes: self.sign_nodes.clone(), + client: self.client.clone(), root_secret_key: ed25519_dalek::SecretKey::from_bytes(self.root_secret_key.as_bytes()) .unwrap(), } @@ -86,7 +89,50 @@ async fn parse(response_future: ResponseFuture) -> anyhow::Result anyhow::Result<(StatusCode, Json)> { + // This is the account that is doing the function calls to creates new accounts. + // TODO: Create such an account for testnet and mainnet + // TODO: Store this account secret key in GCP Secret Manager + let account_creator_id: AccountId = "account_creator.testnet".parse().unwrap(); + let account_creator_sk: SecretKey = "secret_key".parse().unwrap(); + let account_creator_pk: PublicKey = "public_key".parse().unwrap(); + + // Get nonce and recent block hash + let nonce = state + .client + .access_key_nonce(account_creator_id.clone(), account_creator_pk.clone()) + .await?; + let block_hash = state.client.latest_block_hash().await?; + + // Create/generate a public key for for this user + // TODO: use key derivation or other techniques to generate a key + let mpc_recovery_user_pk: PublicKey = "".parse().unwrap(); + + // Create a transaction to create new NEAR account + let new_user_account_id: AccountId = request.account_id.clone().parse().unwrap(); + let create_acc_tx = new_create_account_transaction( + new_user_account_id, + mpc_recovery_user_pk, + account_creator_id.clone(), + account_creator_pk, + nonce, + block_hash, + crate::transaction::NetworkType::Testnet, + ); + + // Sign the transaction + let _signed_create_acc_tx = + sign_transaction(create_acc_tx, account_creator_id, account_creator_sk); + + //TODO: Send transaction to the relayer + + Ok((StatusCode::OK, Json(NewAccountResponse::Ok))) +} + +#[tracing::instrument(level = "info", skip_all, fields(id = state.id))] async fn new_account( State(state): State, Json(request): Json, @@ -99,42 +145,18 @@ async fn new_account( match T::verify_token(&request.id_token).await { Ok(_) => { tracing::info!("access token is valid"); - - // This is the account that is doing the function calls to creates new accounts. - // TODO: Create such an account for testnet and mainnet - // TODO: Store this account secret key in GCP Secret Manager - let account_creator_id: AccountId = "account_creator.testnet".parse().unwrap(); - let account_creator_sk: SecretKey = "secret_key".parse().unwrap(); - let account_creator_pk: PublicKey = "public_key".parse().unwrap(); - - // Get nonce and recent block hash - // TODO: get real nonce and block hash - let nonce = 0; - let block_hash: CryptoHash = "".parse().unwrap(); - - // Create/generate a public key for for this user - // TODO: use key derivation or other techniques to generate a key - let mpc_recovery_user_pk: PublicKey = "".parse().unwrap(); - - // Create a transaction to create new NEAR account - let new_user_account_id: AccountId = request.account_id.clone().parse().unwrap(); - let create_acc_tx = new_create_account_transaction( - new_user_account_id, - mpc_recovery_user_pk, - account_creator_id.clone(), - account_creator_pk, - nonce, - block_hash, - crate::transaction::NetworkType::Testnet, - ); - - // Sign the transaction - let _signed_create_acc_tx = - sign_transaction(create_acc_tx, account_creator_id, account_creator_sk); - - //TODO: Send transaction to the relayer - - (StatusCode::OK, Json(NewAccountResponse::Ok)) + match process_new_account(&state, &request).await { + Ok(result) => result, + Err(e) => { + tracing::error!(err = ?e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(NewAccountResponse::Err { + msg: "internal error".to_string(), + }), + ) + } + } } Err(_) => { tracing::error!("access token verification failed"); @@ -148,7 +170,44 @@ async fn new_account( } } -#[tracing::instrument(level = "debug", skip_all, fields(id = state.id))] +async fn process_add_key( + state: &LeaderState, + request: &AddKeyRequest, +) -> anyhow::Result<(StatusCode, Json)> { + let user_account_id: AccountId = request.account_id.parse().unwrap(); + + // Create/generate a public key for for this user + // TODO: use key derivation or other techniques to generate a key + let mpc_recovery_user_pk: PublicKey = "".parse().unwrap(); + + // Get nonce and recent block hash + let nonce = state + .client + .access_key_nonce(user_account_id.clone(), mpc_recovery_user_pk.clone()) + .await?; + let block_hash = state.client.latest_block_hash().await?; + + // Create a transaction to create a new account + let new_user_pk: PublicKey = request.public_key.clone().parse().unwrap(); + let add_key_tx = new_add_fa_key_transaction( + user_account_id.clone(), + mpc_recovery_user_pk, + new_user_pk, + nonce, + block_hash, + ); + + // Sign the transaction + // TODO: use key derivation or other techniques to generate a key + let mpc_recovery_user_sk: SecretKey = "".parse().unwrap(); + let _signed_add_key_tx = sign_transaction(add_key_tx, user_account_id, mpc_recovery_user_sk); + + //TODO: Send transaction to the relayer + + Ok((StatusCode::OK, Json(AddKeyResponse::Ok))) +} + +#[tracing::instrument(level = "info", skip_all, fields(id = state.id))] async fn add_key( State(state): State, Json(request): Json, @@ -162,34 +221,18 @@ async fn add_key( match T::verify_token(&request.id_token).await { Ok(_) => { tracing::info!("access token is valid"); - // TODO: Get nonce and recent block hash - let nonce = 0; - let block_hash: CryptoHash = "".parse().unwrap(); - - // Create/generate a public key for for this user - // TODO: use key derivation or other techniques to generate a key - let mpc_recovery_user_pk: PublicKey = "".parse().unwrap(); - - // Create a transaction to create a new account - let user_account_id: AccountId = request.account_id.parse().unwrap(); - let new_user_pk: PublicKey = request.public_key.clone().parse().unwrap(); - let add_key_tx = new_add_fa_key_transaction( - user_account_id.clone(), - mpc_recovery_user_pk, - new_user_pk, - nonce, - block_hash, - ); - - // Sign the transaction - // TODO: use key derivation or other techniques to generate a key - let mpc_recovery_user_sk: SecretKey = "".parse().unwrap(); - let _signed_add_key_tx = - sign_transaction(add_key_tx, user_account_id, mpc_recovery_user_sk); - - //TODO: Send transaction to the relayer - - (StatusCode::OK, Json(AddKeyResponse::Ok)) + match process_add_key(&state, &request).await { + Ok(result) => result, + Err(e) => { + tracing::error!(err = ?e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(AddKeyResponse::Err { + msg: "internal error".to_string(), + }), + ) + } + } } Err(_) => { tracing::error!("access token verification failed"); diff --git a/mpc-recovery/src/lib.rs b/mpc-recovery/src/lib.rs index 0ae40f2fb..18ec3b668 100644 --- a/mpc-recovery/src/lib.rs +++ b/mpc-recovery/src/lib.rs @@ -2,6 +2,7 @@ use ed25519_dalek::SecretKey; use rand::rngs::OsRng; use threshold_crypto::{PublicKeySet, SecretKeySet, SecretKeyShare}; +pub(crate) mod client; mod leader_node; pub mod msg; mod oauth; From e59211e40fdb9177c9a3897cfc8806bcfd4d8d8e Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Fri, 14 Apr 2023 19:05:16 +1000 Subject: [PATCH 2/2] install libssl for docker image --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 7a39dbf49..3cc43c446 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ COPY . . RUN cargo build --release --package mpc-recovery FROM debian:buster-slim as runtime +RUN apt-get update && apt-get install --assume-yes libssl-dev COPY --from=builder /usr/src/app/target/release/mpc-recovery /usr/local/bin/mpc-recovery WORKDIR /usr/local/bin