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: api receive current_block_number and round_pubkey_bytes #12

Merged
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
52 changes: 46 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ license = "MIT-0"

[dependencies]
rocket = { version = "0.5.1", features = ["tls", "json"] }
rocket_cors = "0.6.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
bcrypt = "0.10.1"
murmur = { package = "murmur-lib", git = "https://github.com/ideal-lab5/murmur.git" }
serde_cbor = "0.11.2"
sp-core = { git = "https://github.com/ideal-lab5/polkadot-sdk.git", branch = "testing", features = ["bls-experimental"] }
subxt-signer = "0.35.2"
subxt = "0.35.2"
subxt = "0.35.2"
hex = "0.4.3"
parity-scale-codec = "3.6.12"
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This is a simple API that allows you to interact with [Murmur Core](https://github.com/ideal-lab5/murmur).

## Usage

```bash
cargo run
```

## Environment Variables

In live environments you should set the following environment variables:
Expand Down
168 changes: 72 additions & 96 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,143 +18,119 @@
extern crate rocket;

mod store;
mod translate;
mod types;
mod utils;

use murmur::{
etf::{balances::Call, runtime_types::node_template_runtime::RuntimeCall::Balances},
BlockNumber,
use murmur::{BlockNumber, RuntimeCall};
use parity_scale_codec::Decode;
use rocket::{
http::{Cookie, CookieJar, Method, SameSite, Status},
serde::json::Json,
};
use rocket::http::Status;
use rocket::http::{Cookie, CookieJar};
use rocket::serde::{json::Json, Deserialize};
use sp_core::crypto::Ss58Codec;
use std::env;
use subxt::utils::{AccountId32, MultiAddress};
use subxt_signer::sr25519::dev;
use utils::{check_cookie, derive_seed};

fn get_salt() -> String {
env::var("SALT").unwrap_or_else(|_| "0123456789abcdef".to_string())
}

fn get_ephem_msk() -> [u8; 32] {
let ephem_msk_str = env::var("EPHEM_MSK").unwrap_or_else(|_| {
"1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1".to_string()
});
let ephem_msk_vec: Vec<u8> = ephem_msk_str
.split(',')
.map(|s| s.trim().parse().expect("Invalid integer in EPHEM_MSK"))
.collect();
let mut ephem_msk = [0u8; 32];
for (i, &byte) in ephem_msk_vec.iter().enumerate().take(32) {
ephem_msk[i] = byte;
}
ephem_msk
}

#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
}

#[derive(Deserialize)]
struct ExecuteRequest {
amount: u128,
to: String,
}
use rocket_cors::{AllowedHeaders, AllowedOrigins, CorsOptions};
use types::{AuthRequest, CreateRequest, CreateResponse, ExecuteRequest, ExecuteResponse};
use utils::{check_cookie, derive_seed, get_ephem_msk, get_salt, MurmurError};

#[post("/authenticate", data = "<auth_request>")]
/// Authenticate the user and start a session
async fn authenticate(auth_request: Json<AuthRequest>, cookies: &CookieJar<'_>) -> &'static str {
let username = &auth_request.username;
let password = &auth_request.password;
let seed = derive_seed(username, password, &get_salt());

#[derive(Deserialize)]
struct NewRequest {
validity: u32,
}
let username_cookie = Cookie::build(("username", username.clone()))
.path("/")
.same_site(SameSite::None)
.secure(true);

#[post("/login", data = "<login_request>")]
async fn login(login_request: Json<LoginRequest>, cookies: &CookieJar<'_>) -> &'static str {
let username = &login_request.username;
let password = &login_request.password;
let seed = derive_seed(username, password, &get_salt());
let seed_cookie = Cookie::build(("seed", seed.clone()))
.path("/")
.same_site(SameSite::None)
.secure(true);

cookies.add(Cookie::new("username", username.clone()));
cookies.add(Cookie::new("seed", seed.clone()));
cookies.add(username_cookie);
cookies.add(seed_cookie);

"User logged in, session started."
"User authenticated, session started."
}

#[post("/new", data = "<request>")]
/// Generate a wallet valid for the next {validity} blocks
async fn new(cookies: &CookieJar<'_>, request: Json<NewRequest>) -> Result<String, Status> {
#[post("/create", data = "<request>")]
/// Prepare and return the data needed to create a wallet
/// valid for the next {request.validity} blocks
async fn create(
cookies: &CookieJar<'_>,
request: Json<CreateRequest>,
) -> Result<CreateResponse, (Status, String)> {
check_cookie(cookies, |username, seed| async {
let (client, current_block_number, round_pubkey_bytes) =
murmur::idn_connect().await.map_err(|_| Status::InternalServerError)?;
let round_pubkey_bytes = translate::pubkey_to_bytes(&request.round_pubkey)
.map_err(|e| (Status::BadRequest, format!("`request.round_pubkey`: {:?}", e)))?;

// 1. prepare block schedule
let mut schedule: Vec<BlockNumber> = Vec::new();
for i in 2..request.validity + 2 {
// wallet is 'active' in 2 blocks
let next_block_number: BlockNumber = current_block_number + i;
schedule.push(next_block_number);
let next_block: BlockNumber = request.current_block + i;
schedule.push(next_block);
}
// 2. create mmr
let (call, mmr_store) = murmur::create(
let (payload, store) = murmur::create(
username.into(),
seed.into(),
get_ephem_msk(), // TODO: replace with an hkdf? https://github.com/ideal-lab5/murmur/issues/13
schedule,
round_pubkey_bytes,
)
.map_err(|_| Status::InternalServerError)?;
.map_err(|e| (Status::InternalServerError, MurmurError(e).to_string()))?;
// 3. add to storage
store::write(mmr_store.clone());
// sign and send the call
let from = dev::alice();
let _events = client
.tx()
.sign_and_submit_then_watch_default(&call, &from)
.await
.map_err(|_| Status::InternalServerError)?;
Ok("MMR proxy account creation successful!".to_string())
store::write(store.clone());
// 4. return the call data
Ok(CreateResponse { payload: payload.into() })
})
.await
}

#[post("/execute", data = "<request>")]
/// Execute a transaction from the wallet
async fn execute(cookies: &CookieJar<'_>, request: Json<ExecuteRequest>) -> Result<String, Status> {
async fn execute(
cookies: &CookieJar<'_>,
request: Json<ExecuteRequest>,
) -> Result<ExecuteResponse, (Status, String)> {
check_cookie(cookies, |username, seed| async {
let (client, current_block_number, _) =
murmur::idn_connect().await.map_err(|_| Status::InternalServerError)?;

let from_ss58 = sp_core::crypto::AccountId32::from_ss58check(&request.to).unwrap();

let bytes: &[u8] = from_ss58.as_ref();
let from_ss58_sized: [u8; 32] = bytes.try_into().unwrap();
let to = AccountId32::from(from_ss58_sized);
let balance_transfer_call = Balances(Call::transfer_allow_death {
dest: MultiAddress::<_, u32>::from(to),
value: request.amount,
});

let store = store::load();
let target_block_number = current_block_number + 1;
let target_block = request.current_block + 1;

let tx = murmur::prepare_execute(
let runtime_call = RuntimeCall::decode(&mut &request.runtime_call[..])
.map_err(|e| (Status::InternalServerError, e.to_string()))?;

let payload = murmur::prepare_execute(
username.into(),
seed.into(),
target_block_number,
target_block,
store,
balance_transfer_call,
runtime_call,
)
.map_err(|_| Status::InternalServerError)?;

// submit the tx using alice to sign it
let _ = client.tx().sign_and_submit_then_watch_default(&tx, &dev::alice()).await;
.map_err(|e| (Status::InternalServerError, MurmurError(e).to_string()))?;

Ok("Transaction executed".to_string())
Ok(ExecuteResponse { payload: payload.into() })
})
.await
}

#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![login, new, execute])
let cors = CorsOptions::default()
.allowed_origins(AllowedOrigins::all())
.allowed_methods(
vec![Method::Get, Method::Post, Method::Patch]
.into_iter()
.map(From::from)
.collect(),
)
.allowed_headers(AllowedHeaders::all())
.allow_credentials(true)
.to_cors()
.unwrap();

rocket::build().mount("/", routes![authenticate, create, execute]).attach(cors)
}
23 changes: 23 additions & 0 deletions src/translate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 by Ideal Labs, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

pub(crate) fn pubkey_to_bytes(pubkey: &str) -> Result<Vec<u8>, String> {
let pubkey = if pubkey.starts_with("0x") { &pubkey[2..] } else { pubkey };

let round_pubkey_bytes =
hex::decode(pubkey).map_err(|_| format!("Wrong input `{:?}`", pubkey))?;
Ok(round_pubkey_bytes)
}
Loading