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

feat: pull latest block and key nonce #50

Merged
merged 2 commits into from
Apr 14, 2023
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
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
91 changes: 91 additions & 0 deletions mpc-recovery/src/client.rs
Original file line number Diff line number Diff line change
@@ -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<u64> {
let key = self.access_key(account_id, public_key).await?;
Ok(key.0.nonce)
}

pub async fn latest_block_hash(&self) -> anyhow::Result<CryptoHash> {
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(())
}
}
177 changes: 110 additions & 67 deletions mpc-recovery/src/leader_node/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::client::NearRpcClient;
use crate::msg::{
AddKeyRequest, AddKeyResponse, LeaderRequest, LeaderResponse, NewAccountRequest,
NewAccountResponse, SigShareRequest, SigShareResponse,
Expand All @@ -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;
Expand Down Expand Up @@ -41,6 +41,7 @@ pub async fn run(
pk_set,
sk_share,
sign_nodes,
client: NearRpcClient::testnet(),
root_secret_key,
};

Expand All @@ -63,6 +64,7 @@ struct LeaderState {
pk_set: PublicKeySet,
sk_share: SecretKeyShare,
sign_nodes: Vec<String>,
client: NearRpcClient,
// TODO: temporary solution
root_secret_key: ed25519_dalek::SecretKey,
}
Expand All @@ -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(),
}
Expand All @@ -86,7 +89,50 @@ async fn parse(response_future: ResponseFuture) -> anyhow::Result<SigShareRespon
Ok(serde_json::from_slice(&response_body)?)
}

#[tracing::instrument(level = "debug", skip_all, fields(id = state.id))]
async fn process_new_account(
state: &LeaderState,
request: &NewAccountRequest,
) -> anyhow::Result<(StatusCode, Json<NewAccountResponse>)> {
// 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<T: OAuthTokenVerifier>(
State(state): State<LeaderState>,
Json(request): Json<NewAccountRequest>,
Expand All @@ -99,42 +145,18 @@ async fn new_account<T: OAuthTokenVerifier>(
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");
Expand All @@ -148,7 +170,44 @@ async fn new_account<T: OAuthTokenVerifier>(
}
}

#[tracing::instrument(level = "debug", skip_all, fields(id = state.id))]
async fn process_add_key(
state: &LeaderState,
request: &AddKeyRequest,
) -> anyhow::Result<(StatusCode, Json<AddKeyResponse>)> {
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<T: OAuthTokenVerifier>(
State(state): State<LeaderState>,
Json(request): Json<AddKeyRequest>,
Expand All @@ -162,34 +221,18 @@ async fn add_key<T: OAuthTokenVerifier>(
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");
Expand Down
1 change: 1 addition & 0 deletions mpc-recovery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down