Skip to content

Commit

Permalink
Add multiple enclave support
Browse files Browse the repository at this point in the history
  • Loading branch information
ssantos21 committed Jun 17, 2024
1 parent 89b7fe0 commit 8b6255f
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 43 deletions.
25 changes: 23 additions & 2 deletions server/Settings.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
lockbox = "http://0.0.0.0:18080"
network = "testnet"
lockheight_init = 1000
lh_decrement = 10
connection_string = "postgresql://postgres:postgres@localhost/mercury"
batch_timeout = 120 # seconds
batch_timeout = 120 # seconds

[[enclaves]]
url = "http://0.0.0.0:18080"
allow_deposit = true

[[enclaves]]
url = "http://0.0.0.0:18080"
allow_deposit = false

[[enclaves]]
url = "http://0.0.0.0:18080"
allow_deposit = true

[[enclaves]]
url = "http://0.0.0.0:18080"
allow_deposit = true

[[enclaves]]
url = "http://0.0.0.0:18080"
allow_deposit = true

# env var: ENCLAVES='[{"url": "http://0.0.0.0:18080", "allow_deposit": true}, {"url": "http://0.0.0.0:18080", "allow_deposit": false}]'
1 change: 1 addition & 0 deletions server/migrations/0001_key_data_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CREATE TABLE public.statechain_data (
auth_xonly_public_key bytea NULL,
server_public_key bytea NULL UNIQUE,
statechain_id varchar NULL UNIQUE,
enclave_index integer NOT NULL,
CONSTRAINT statechain_data_pkey PRIMARY KEY (id),
CONSTRAINT statechain_data_server_public_key_ukey UNIQUE (server_public_key)
);
Expand Down
5 changes: 3 additions & 2 deletions server/src/database/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ pub async fn check_existing_key(pool: &sqlx::PgPool, auth_key: &XOnlyPublicKey)
}
}

pub async fn insert_new_deposit(pool: &sqlx::PgPool, token_id: &str, auth_key: &XOnlyPublicKey, server_public_key: &PublicKey, statechain_id: &String) {
pub async fn insert_new_deposit(pool: &sqlx::PgPool, token_id: &str, auth_key: &XOnlyPublicKey, server_public_key: &PublicKey, statechain_id: &String, enclave_index: i32) {

let query = "INSERT INTO statechain_data (token_id, auth_xonly_public_key, server_public_key, statechain_id) VALUES ($1, $2, $3, $4)";
let query = "INSERT INTO statechain_data (token_id, auth_xonly_public_key, server_public_key, statechain_id, enclave_index) VALUES ($1, $2, $3, $4, $5)";

let _ = sqlx::query(query)
.bind(token_id)
.bind(&auth_key.serialize())
.bind(&server_public_key.serialize())
.bind(statechain_id)
.bind(enclave_index)
.execute(pool)
.await
.unwrap();
Expand Down
1 change: 1 addition & 0 deletions server/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod transfer_sender;
pub mod transfer_receiver;
pub mod transfer;
pub mod deposit;
pub mod utils;
24 changes: 24 additions & 0 deletions server/src/database/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use sqlx::Row;

pub async fn get_enclave_index_from_database(pool: &sqlx::PgPool, statechain_id: &str) -> Option<i32> {

let query = "SELECT enclave_index \
FROM statechain_data \
WHERE statechain_id = $1";

let row = sqlx::query(query)
.bind(statechain_id)
.fetch_optional(pool)
.await
.unwrap();

if row.is_none() {
return None;
}

let row = row.unwrap();

let enclave_index: i32 = row.get("enclave_index");

Some(enclave_index)
}
51 changes: 43 additions & 8 deletions server/src/endpoints/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use std::str::FromStr;

use bitcoin::hashes::sha256;
use bitcoin::hashes::{sha256, Hash};
use rocket::{serde::json::Json, response::status, State, http::Status};
use secp256k1_zkp::{XOnlyPublicKey, schnorr::Signature, Message, Secp256k1, PublicKey};
use serde::{Serialize, Deserialize};
use serde_json::{Value, json};
use crate::server::StateChainEntity;
use crate::{server::StateChainEntity, server_config::Enclave};

#[get("/deposit/get_token")]
pub async fn get_token(statechain_entity: &State<StateChainEntity>) -> status::Custom<Json<Value>> {

if statechain_entity.config.network == "mainnet" {
let config = crate::server_config::ServerConfig::load();

if config.network == "mainnet" {
let response_body = json!({
"error": "Internal Server Error",
"message": "Token generation not supported on mainnet."
Expand All @@ -35,7 +37,9 @@ pub async fn get_token(statechain_entity: &State<StateChainEntity>) -> status::C
#[get("/tokens/token_init")]
pub async fn token_init(statechain_entity: &State<StateChainEntity>) -> status::Custom<Json<Value>> {

if statechain_entity.config.network == "mainnet" {
let config = crate::server_config::ServerConfig::load();

if config.network == "mainnet" {
let response_body = json!({
"error": "Internal Server Error",
"message": "Token generation not supported on mainnet."
Expand Down Expand Up @@ -71,6 +75,33 @@ pub async fn token_init(statechain_entity: &State<StateChainEntity>) -> status::
return status::Custom(Status::Ok, Json(response_body));
}

fn get_random_enclave_index(statechain_id: &str, enclaves: &Vec<Enclave>) -> Result<usize, String> {
let index_from_statechain_id = get_enclave_index_from_statechain_id(statechain_id, enclaves.len() as u32);

let selected_enclave = enclaves.get(index_from_statechain_id).unwrap();
if selected_enclave.allow_deposit {
return Ok(index_from_statechain_id);
} else {
for (i, enclave) in enclaves.iter().enumerate() {
if enclave.allow_deposit {
return Ok(i);
}
}
}

Err("No valid enclave found with allow_deposit set to true".to_string())
}

fn get_enclave_index_from_statechain_id(statechain_id: &str, enclave_array_len: u32) -> usize {
let hash = sha256::Hash::hash(statechain_id.as_bytes());
let hash_bytes = hash.as_byte_array();
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&hash_bytes[..16]);
let random_number = u128::from_be_bytes(bytes);

return (random_number % enclave_array_len as u128) as usize;
}

#[post("/deposit/init/pod", format = "json", data = "<deposit_msg1>")]
pub async fn post_deposit(statechain_entity: &State<StateChainEntity>, deposit_msg1: Json<mercurylib::deposit::DepositMsg1>) -> status::Custom<Json<Value>> {

Expand Down Expand Up @@ -123,14 +154,18 @@ pub async fn post_deposit(statechain_entity: &State<StateChainEntity>, deposit_m
return status::Custom(Status::Gone, Json(response_body));
}

let statechain_id = uuid::Uuid::new_v4().as_simple().to_string();

#[derive(Debug, Serialize, Deserialize)]
pub struct GetPublicKeyRequestPayload {
statechain_id: String,
}

let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap();
let statechain_id = uuid::Uuid::new_v4().as_simple().to_string();

let config = crate::server_config::ServerConfig::load();

let enclave_index = get_random_enclave_index(&statechain_id, &config.enclaves).unwrap();

let lockbox_endpoint = config.enclaves.get(enclave_index).unwrap().url.clone();
let path = "get_public_key";

let client: reqwest::Client = reqwest::Client::new();
Expand Down Expand Up @@ -170,7 +205,7 @@ pub async fn post_deposit(statechain_entity: &State<StateChainEntity>, deposit_m

let server_pubkey = PublicKey::from_str(&server_pubkey_hex).unwrap();

crate::database::deposit::insert_new_deposit(&statechain_entity.pool, &token_id, &auth_key, &server_pubkey, &statechain_id).await;
crate::database::deposit::insert_new_deposit(&statechain_entity.pool, &token_id, &auth_key, &server_pubkey, &statechain_id, enclave_index as i32).await;

crate::database::deposit::set_token_spent(&statechain_entity.pool, &token_id).await;

Expand Down
10 changes: 5 additions & 5 deletions server/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use chrono::{DateTime, Duration, Utc};
use rocket::State;

use crate::server::StateChainEntity;

pub mod deposit;
pub mod sign;
Expand All @@ -11,8 +8,11 @@ pub mod transfer_receiver;
pub mod withdraw;


fn is_batch_expired(statechain_entity: &State<StateChainEntity>, batch_time: DateTime<Utc>) -> bool {
let batch_timeout = statechain_entity.config.batch_timeout;
fn is_batch_expired(batch_time: DateTime<Utc>) -> bool {

let config = crate::server_config::ServerConfig::load();

let batch_timeout = config.batch_timeout;

let expiration_time = batch_time + Duration::seconds(batch_timeout as i64);

Expand Down
44 changes: 40 additions & 4 deletions server/src/endpoints/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,33 @@ pub async fn insert_new_signature_data(pool: &sqlx::PgPool, server_pubnonce: &st
#[post("/sign/first", format = "json", data = "<sign_first_request_payload>")]
pub async fn sign_first(statechain_entity: &State<StateChainEntity>, sign_first_request_payload: Json<SignFirstRequestPayload>) -> status::Custom<Json<Value>> {

let config = crate::server_config::ServerConfig::load();

let statechain_id = sign_first_request_payload.0.statechain_id.clone();

let statechain_entity = statechain_entity.inner();

let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap();
let enclave_index = crate::database::utils::get_enclave_index_from_database(&statechain_entity.pool, &statechain_id).await;

let enclave_index = match enclave_index {
Some(index) => index,
None => {
let response_body = json!({
"message": format!("Enclave index for statechain {} ID not found.", statechain_id)
});

return status::Custom(Status::InternalServerError, Json(response_body));
}
};

let enclave_index = enclave_index as usize;

let lockbox_endpoint = config.enclaves.get(enclave_index).unwrap().url.clone();
let path = "get_public_nonce";

let client: reqwest::Client = reqwest::Client::new();
let request = client.post(&format!("{}/{}", lockbox_endpoint, path));

let statechain_id = sign_first_request_payload.0.statechain_id.clone();
let signed_statechain_id = sign_first_request_payload.0.signed_statechain_id.clone();

if !crate::endpoints::utils::validate_signature(&statechain_entity.pool, &signed_statechain_id, &statechain_id).await {
Expand Down Expand Up @@ -128,15 +146,33 @@ pub async fn update_signature_data_challenge(pool: &sqlx::PgPool, server_pub_non
#[post("/sign/second", format = "json", data = "<partial_signature_request_payload>")]
pub async fn sign_second (statechain_entity: &State<StateChainEntity>, partial_signature_request_payload: Json<mercurylib::transaction::PartialSignatureRequestPayload>) -> status::Custom<Json<Value>> {

let statechain_id = partial_signature_request_payload.0.statechain_id.clone();

let statechain_entity = statechain_entity.inner();

let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap();
let config = crate::server_config::ServerConfig::load();

let enclave_index = crate::database::utils::get_enclave_index_from_database(&statechain_entity.pool, &statechain_id).await;

let enclave_index = match enclave_index {
Some(index) => index,
None => {
let response_body = json!({
"message": format!("Enclave index for statechain {} ID not found.", statechain_id)
});

return status::Custom(Status::InternalServerError, Json(response_body));
}
};

let enclave_index = enclave_index as usize;

let lockbox_endpoint = config.enclaves.get(enclave_index).unwrap().url.clone();
let path = "get_partial_signature";

let client: reqwest::Client = reqwest::Client::new();
let request = client.post(&format!("{}/{}", lockbox_endpoint, path));

let statechain_id = partial_signature_request_payload.0.statechain_id.clone();
let signed_statechain_id = partial_signature_request_payload.0.signed_statechain_id.clone();

if !crate::endpoints::utils::validate_signature(&statechain_entity.pool, &signed_statechain_id, &statechain_id).await {
Expand Down
39 changes: 36 additions & 3 deletions server/src/endpoints/transfer_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,24 @@ pub async fn statechain_info(statechain_entity: &State<StateChainEntity>, statec

let enclave_public_key = enclave_public_key.unwrap();

let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap();
let config = crate::server_config::ServerConfig::load();

let enclave_index = crate::database::utils::get_enclave_index_from_database(&statechain_entity.pool, &statechain_id).await;

let enclave_index = match enclave_index {
Some(index) => index,
None => {
let response_body = json!({
"message": format!("Enclave index for statechain {} ID not found.", statechain_id)
});

return status::Custom(Status::InternalServerError, Json(response_body));
}
};

let enclave_index = enclave_index as usize;

let lockbox_endpoint = config.enclaves.get(enclave_index).unwrap().url.clone();
let path = "signature_count";

let client: reqwest::Client = reqwest::Client::new();
Expand Down Expand Up @@ -143,7 +160,7 @@ pub async fn validate_batch(statechain_entity: &State<StateChainEntity>, statech

let (batch_id, batch_time) = batch_info.unwrap();

if is_batch_expired(&statechain_entity, batch_time) {
if is_batch_expired(batch_time) {
// the batch time has not expired. It is possible to add a new coin to the batch.
return BatchTransferReceiveValidationResult::ExpiredBatchTimeError("Batch time has expired".to_string());
} else {
Expand Down Expand Up @@ -256,8 +273,24 @@ pub async fn transfer_receiver(statechain_entity: &State<StateChainEntity>, tran
x1: x1_hex,
};

let config = crate::server_config::ServerConfig::load();

let enclave_index = crate::database::utils::get_enclave_index_from_database(&statechain_entity.pool, &statechain_id).await;

let enclave_index = match enclave_index {
Some(index) => index,
None => {
let response_body = json!({
"message": format!("Enclave index for statechain {} ID not found.", statechain_id)
});

return status::Custom(Status::InternalServerError, Json(response_body));
}
};

let enclave_index = enclave_index as usize;

let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap();
let lockbox_endpoint = config.enclaves.get(enclave_index).unwrap().url.clone();
let path = "keyupdate";

let client: reqwest::Client = reqwest::Client::new();
Expand Down
4 changes: 2 additions & 2 deletions server/src/endpoints/transfer_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub async fn validate_batch_transfer(statechain_entity: &State<StateChainEntity>

let (batch_id, batch_time) = batch_info.unwrap();

if !is_batch_expired(&statechain_entity, batch_time) {
if !is_batch_expired(batch_time) {

// TODO: check if the batch is complete. If complete, should return success.

Expand Down Expand Up @@ -59,7 +59,7 @@ pub async fn validate_batch_transfer(statechain_entity: &State<StateChainEntity>
if batch_time.is_some() {
let batch_time = batch_time.unwrap();

if !is_batch_expired(&statechain_entity, batch_time) {
if !is_batch_expired(batch_time) {
// the batch time has not expired. It is possible to add a new coin to the batch.
return BatchTransferValidationResult::Success
} else {
Expand Down
Loading

0 comments on commit 8b6255f

Please sign in to comment.