add prepare mulitsig info orchestration

This commit is contained in:
creating2morrow 2023-06-01 10:14:15 -04:00
parent 06cab5460e
commit e97f3394bc
10 changed files with 308 additions and 125 deletions

View file

@ -75,8 +75,9 @@ NEVidebla-MESago (invisible message)
* nevmes-auth - `internal` auth server
* nevmes-contact - `internal` add contacts server
* nevmes-gui - primary user interface
* nevmes-market - `internal` marketplace admin server
* nevmes-message - `internal` message tx/read etc. server
* nevmes - `external` primary server for contact share, payment, message rx etc.
* nevmes - `external` primary server for contact share, payment, market, message rx etc.
* [monerod](https://www.getmonero.org/downloads/#cli) - (not included) monero-wallet-rpc needs this
* can be overriden with remote node
* use the `--remote-node` flag

View file

@ -6,7 +6,7 @@ use crate::{
i2p,
models::*,
reqres,
utils,
utils, monero,
};
use log::{
debug,
@ -17,9 +17,25 @@ use reqwest::StatusCode;
use rocket::serde::json::Json;
use std::error::Error;
#[derive(PartialEq)]
pub enum MessageType {
Normal,
Multisig,
}
struct MultisigMessageData {
info: String,
sub_type: String,
orid: String,
}
/// Create a new message
pub async fn create(m: Json<Message>, jwp: String) -> Message {
let f_mid: String = format!("m{}", utils::generate_rnd());
pub async fn create(m: Json<Message>, jwp: String, m_type: MessageType) -> Message {
let rnd = utils::generate_rnd();
let mut f_mid: String = format!("m{}", &rnd);
if m_type == MessageType::Multisig {
f_mid = format!("msig{}", &rnd);
}
info!("creating message: {}", &f_mid);
let created = chrono::offset::Utc::now().timestamp();
// get contact public gpg key and encrypt the message
@ -47,7 +63,7 @@ pub async fn create(m: Json<Message>, jwp: String) -> Message {
debug!("writing message index {} for id: {}", msg_list, list_key);
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list);
info!("attempting to send message");
let send = send_message(&new_message, &jwp).await;
let send = send_message(&new_message, &jwp, m_type).await;
send.unwrap();
new_message
}
@ -88,6 +104,68 @@ pub async fn rx(m: Json<Message>) {
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list);
}
/// Parse the multisig message type and info
fn parse_multisig_message(mid: String) -> MultisigMessageData {
let d: reqres::DecryptedMessageBody = decrypt_body(mid);
let mut bytes = hex::decode(d.body.into_bytes()).unwrap_or(Vec::new());
let decoded = String::from_utf8(bytes).unwrap_or(utils::empty_string());
let values = decoded.split(":");
let mut v: Vec<String> = values.map(|s| String::from(s)).collect();
let sub_type: String = v.remove(0);
let orid: String = v.remove(0);
let info: String = v.remove(0);
bytes = Vec::new();
debug!("zero decryption bytes: {:?}", bytes);
MultisigMessageData { info, sub_type, orid }
}
/// Rx multisig message
///
/// Upon multisig message receipt the message is automatically
///
/// decrypted for convenience sake. The client must determine which
///
/// .b32.i2p address belongs to the vendor / mediator.
///
/// ### Example
///
/// ```rust
/// // lookup prepare info for vendor
/// let s = db::Interface::open();
/// let key = "prepare-o123-test.b32.i2p";
/// db::Interface::read(&s.env, &s.handle, &key);
/// ```
pub async fn rx_multisig(m: Json<Message>) {
// make sure the message isn't something strange
let is_valid = validate_message(&m);
if !is_valid {
return;
}
// don't allow messages from outside the contact list
let is_in_contact_list = contact::exists(&m.from);
if !is_in_contact_list {
return;
}
let f_mid: String = format!("m{}", utils::generate_rnd());
let new_message = Message {
mid: String::from(&f_mid),
uid: String::from("rx"),
from: String::from(&m.from),
body: m.body.iter().cloned().collect(),
created: chrono::offset::Utc::now().timestamp(),
to: String::from(&m.to),
};
debug!("insert multisig message: {:?}", &new_message);
let s = db::Interface::open();
let k = &new_message.mid;
db::Interface::async_write(&s.env, &s.handle, k, &Message::to_db(&new_message)).await;
let data: MultisigMessageData = parse_multisig_message(new_message.mid);
debug!("writing multisig message type {} for order {}", &data.sub_type, &data.orid);
// lookup msig message data by {type}-{order id}-{contact .b32.i2p address}
let msig_key = format!("{}-{}-{}", &data.sub_type, &data.orid, &m.from);
db::Interface::write(&s.env, &s.handle, &msig_key, &data.info);
}
/// Message lookup
pub fn find(mid: &String) -> Message {
let s = db::Interface::open();
@ -134,18 +212,21 @@ pub fn find_all() -> Vec<Message> {
}
/// Tx message
async fn send_message(out: &Message, jwp: &str) -> Result<(), Box<dyn Error>> {
async fn send_message(out: &Message, jwp: &str, m_type: MessageType) -> Result<(), Box<dyn Error>> {
let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build();
let mut url = format!("http://{}/message/rx", out.to);
if m_type == MessageType::Multisig {
url = format!("http://{}/message/rx/multisig", out.to)
}
// check if the contact is online
let is_online: bool = is_contact_online(&out.to, String::from(jwp))
.await
.unwrap_or(false);
if is_online {
return match client?
.post(format!("http://{}/message/rx", out.to))
.post(url)
.header("proof", jwp)
.json(&out)
.send()
@ -314,7 +395,12 @@ pub async fn retry_fts() {
let k = format!("{}-{}", "fts-jwp", &message.to);
let jwp = db::Interface::read(&s.env, &s.handle, &k);
if jwp != utils::empty_string() {
send_message(&message, &jwp).await.unwrap();
let m_type = if message.mid.contains("misg") {
MessageType::Multisig
} else {
MessageType::Normal
};
send_message(&message, &jwp, m_type).await.unwrap();
} else {
error!("not jwp found for fts id: {}", &message.mid);
}
@ -339,6 +425,24 @@ fn is_fts_clear(r: String) -> bool {
v.len() >= 2 && v[v.len() - 1] == utils::empty_string() && v[0] == utils::empty_string()
}
pub async fn send_prepare_info(orid:String, contact:String) {
let s = db::Interface::open();
let prepare_info = monero::prepare_wallet().await;
let k = format!("{}-{}", "fts-jwp", &contact);
let jwp = db::Interface::read(&s.env, &s.handle, &k);
let body_str = format!("prepare:{}:{}", &orid, &prepare_info.result.multisig_info);
let message: Message = Message {
mid: utils::empty_string(),
uid: utils::empty_string(),
body: body_str.into_bytes(),
created: chrono::Utc::now().timestamp(),
from: utils::empty_string(),
to: String::from(&contact),
};
let j_message: Json<Message> = utils::message_to_json(&message);
create(j_message, jwp, MessageType::Multisig).await;
}
// Tests
//-------------------------------------------------------------------------------
@ -366,7 +470,7 @@ mod tests {
let j_message = utils::message_to_json(&message);
let jwp = String::from("test-jwp");
tokio::spawn(async move {
let test_message = create(j_message, jwp).await;
let test_message = create(j_message, jwp, MessageType::Normal).await;
let expected: Message = Default::default();
assert_eq!(test_message.body, expected.body);
cleanup(&test_message.mid).await;

View file

@ -315,17 +315,20 @@ pub struct Order {
pub cust_msig_txset: String,
pub date: i64,
pub deliver_date: i64,
/// Transaction hash from vendor or customer signed txset
pub hash: String,
pub mediator_kex_1: String,
pub mediator_kex_2: String,
pub mediator_kex_3: String,
pub mediator_msig_make: String,
pub mediator_msig_prepare: String,
/// Address gpg key encrypted bytes
pub ship_address: Vec<u8>,
pub ship_date: i64,
/// This is the final destination for the payment
pub subaddress: String,
pub status: String,
pub quantity: i64,
pub quantity: u64,
pub vend_kex_1: String,
pub vend_kex_2: String,
pub vend_kex_3: String,
@ -350,13 +353,14 @@ impl Default for Order {
cust_msig_txset: utils::empty_string(),
date: 0,
deliver_date: 0,
ship_date: 0,
hash: utils::empty_string(),
mediator_kex_1: utils::empty_string(),
mediator_kex_2: utils::empty_string(),
mediator_kex_3: utils::empty_string(),
mediator_msig_make: utils::empty_string(),
mediator_msig_prepare: utils::empty_string(),
ship_address: Vec::new(),
ship_date: 0,
subaddress: utils::empty_string(),
status: utils::empty_string(),
quantity: 0,
@ -372,8 +376,9 @@ impl Default for Order {
impl Order {
pub fn to_db(o: &Order) -> String {
let ship_address = hex::encode(&o.ship_address);
format!(
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
o.cid,
o.pid,
o.cust_kex_1,
@ -390,6 +395,7 @@ impl Order {
o.mediator_kex_1,
o.mediator_kex_2,
o.mediator_kex_3,
ship_address,
o.ship_date,
o.subaddress,
o.status,
@ -429,13 +435,14 @@ impl Order {
let mediator_kex_1 = v.remove(0);
let mediator_kex_2 = v.remove(0);
let mediator_kex_3 = v.remove(0);
let ship_address = hex::decode(v.remove(0)).unwrap_or(Vec::new());
let ship_date = match v.remove(0).parse::<i64>() {
Ok(d) => d,
Err(_) => 0,
};
let subaddress = v.remove(0);
let status = v.remove(0);
let quantity = match v.remove(0).parse::<i64>() {
let quantity = match v.remove(0).parse::<u64>() {
Ok(d) => d,
Err(_) => 0,
};
@ -464,6 +471,7 @@ impl Order {
mediator_kex_3,
mediator_msig_make,
mediator_msig_prepare,
ship_address,
ship_date,
subaddress,
status,
@ -496,6 +504,7 @@ impl Order {
mediator_kex_3: String::from(&o.mediator_kex_3),
mediator_msig_make: String::from(&o.mediator_msig_make),
mediator_msig_prepare: String::from(&o.mediator_msig_prepare),
ship_address: o.ship_address.iter().cloned().collect(),
ship_date: o.ship_date,
subaddress: String::from(&o.subaddress),
status: String::from(&o.status),

View file

@ -1080,3 +1080,24 @@ impl Default for ErrorResponse {
}
}
}
/// Handle intial information for request
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "rocket::serde")]
pub struct OrderRequest {
pub cid: String,
pub pid: String,
pub ship_address: Vec<u8>,
pub quantity: u64,
}
impl Default for OrderRequest {
fn default() -> Self {
OrderRequest {
cid: utils::empty_string(),
pid: utils::empty_string(),
ship_address: Vec::new(),
quantity: 0,
}
}
}

View file

@ -778,7 +778,8 @@ fn send_message_req(tx: Sender<bool>, ctx: egui::Context, body: String, to: Stri
};
let j_message = utils::message_to_json(&m);
tokio::spawn(async move {
let result = message::create(j_message, jwp).await;
let m_type = message::MessageType::Normal;
let result = message::create(j_message, jwp, m_type).await;
if result.mid != utils::empty_string() {
log::info!("sent message: {}", result.mid);
let _ = tx.send(true);

View file

@ -13,7 +13,7 @@ async fn rocket() -> _ {
..rocket::Config::debug_default()
};
env_logger::init();
log::info!("nevmes-auth is online");
log::info!("nevmes-market is online");
rocket::custom(&config)
.mount(
"/dispute",

View file

@ -1,109 +1,94 @@
// use nevmes_core::*;
// use log::{debug, error, info};
// use rocket::serde::json::Json;
// use crate::product;
use nevmes_core::*;
use log::{debug, error, info};
use rocket::serde::json::Json;
// enum StatusType {
// Delivered,
// MultisigMissing,
// MulitsigComplete,
// Shipped,
// }
enum StatusType {
_Delivered,
MultisigMissing,
_MulitsigComplete,
_Shipped,
}
// impl StatusType {
// pub fn value(&self) -> String {
// match *self {
// StatusType::Delivered => String::from("Delivered"),
// StatusType::MultisigMissing => String::from("MultisigMissing"),
// StatusType::MulitsigComplete => String::from("MulitsigComplete"),
// StatusType::Shipped => String::from("Shipped"),
// }
// }
// }
impl StatusType {
pub fn value(&self) -> String {
match *self {
StatusType::_Delivered => String::from("Delivered"),
StatusType::MultisigMissing => String::from("MultisigMissing"),
StatusType::_MulitsigComplete => String::from("MulitsigComplete"),
StatusType::_Shipped => String::from("Shipped"),
}
}
}
// /// Create a skeleton for order
// pub fn create(cid: String, pid: String) -> models::Order {
// let ts = chrono::offset::Utc::now().timestamp();
// let orid: String = format!("O{}", utils::generate_rnd());
// let m_product: models::Product = product::find(&pid);
// let new_order = models::Order {
// orid,
// cid: String::from(&cid),
// pid: String::from(&pid),
// cust_kex_1: utils::empty_string(),
// cust_kex_2: utils::empty_string(),
// cust_kex_3: utils::empty_string(),
// cust_msig_make: utils::empty_string(),
// cust_msig_prepare: utils::empty_string(),
// cust_msig_txset: utils::empty_string(),
// date: 0,
// deliver_date: 0,
// hash: utils::empty_string(),
// mediator_kex_1: utils::empty_string(),
// mediator_kex_2: utils::empty_string(),
// mediator_kex_3: utils::empty_string(),
// mediator_msig_make: utils::empty_string(),
// mediator_msig_prepare: utils::empty_string(),
// ship_date: 0,
// subaddress: utils::empty_string(),
// status: utils::empty_string(),
// quantity: 0,
// vend_kex_1: utils::empty_string(),
// vend_kex_2: utils::empty_string(),
// vend_kex_3: utils::empty_string(),
// vend_msig_make: utils::empty_string(),
// vend_msig_prepare: utils::empty_string(),
// vend_msig_txset: utils::empty_string(),
// xmr_address: utils::empty_string(),
// };
// debug!("insert order: {:?}", new_order);
// let m_wallet = monero::create_wallet(String::from(&orid), &utils::empty_string()).await;
// if !m_wallet {
// error!("error creating msig wallet for order {}", &orid);
// }
// debug!("insert order: {:?}", &new_order);
// let s = db::Interface::open();
// let k = &new_order.orid;
// db::Interface::write(&s.env, &s.handle, k, &models::Order::to_db(&new_order));
// // in order to retrieve all orders, write keys to with ol
// let list_key = format!("ol");
// let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key));
// if r == utils::empty_string() {
// debug!("creating order index");
// }
// let order_list = [r, String::from(&orid)].join(",");
// debug!("writing order index {} for id: {}", order_list, list_key);
// db::Interface::write(&s.env, &s.handle, &String::from(list_key), &order_list);
// new_order
// }
/// Create a intial order
pub async fn create(j_order: Json<reqres::OrderRequest>) -> models::Order {
info!("creating order");
let ts = chrono::offset::Utc::now().timestamp();
let orid: String = format!("O{}", utils::generate_rnd());
let r_subaddress = monero::create_address().await;
let subaddress = r_subaddress.result.address;
let new_order = models::Order {
orid: String::from(&orid),
cid: String::from(&j_order.cid),
pid: String::from(&j_order.pid),
date: ts,
ship_address: j_order.ship_address.iter().cloned().collect(),
subaddress,
status: StatusType::MultisigMissing.value(),
quantity: j_order.quantity,
..Default::default()
};
debug!("insert order: {:?}", new_order);
let m_wallet = monero::create_wallet(String::from(&orid), &utils::empty_string()).await;
if !m_wallet {
error!("error creating msig wallet for order {}", &orid);
return Default::default();
}
debug!("insert order: {:?}", &new_order);
let s = db::Interface::open();
let k = &new_order.orid;
db::Interface::write(&s.env, &s.handle, k, &models::Order::to_db(&new_order));
// in order to retrieve all orders, write keys to with ol
let list_key = format!("ol");
let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key));
if r == utils::empty_string() {
debug!("creating order index");
}
let order_list = [r, String::from(&orid)].join(",");
debug!("writing order index {} for id: {}", order_list, list_key);
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &order_list);
new_order
}
// /// Lookup order
// pub fn find(oid: String) -> models::Order {
// let s = db::Interface::open();
// let r = db::Interface::read(&s.env, &s.handle, &String::from(&oid));
// if r == utils::empty_string() {
// error!("order not found");
// return Default::default();
// }
// models::Order::from_db(String::from(&oid), r)
// }
/// Lookup order
pub fn find(oid: String) -> models::Order {
info!("find order: {}", &oid);
let s = db::Interface::open();
let r = db::Interface::read(&s.env, &s.handle, &String::from(&oid));
if r == utils::empty_string() {
error!("order not found");
return Default::default();
}
models::Order::from_db(String::from(&oid), r)
}
// /// Lookup all orders for customer
// pub async fn find_all_customer_orders(cid: String) -> Vec<models::Order> {
// let i_s = db::Interface::open();
// let i_list_key = format!("ol");
// let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
// if i_r == utils::empty_string() {
// error!("order index not found");
// }
// let i_v_oid = i_r.split(",");
// let i_v: Vec<String> = i_v_oid.map(|s| String::from(s)).collect();
// let mut orders: Vec<models::Order> = Vec::new();
// for o in i_v {
// let order: models::Order = find(o);
// if order.orid != utils::empty_string() && order.cid == cid {
// orders.push(order);
// }
// }
// orders
// }
/// Lookup all orders for customer
pub async fn find_all_customer_orders(cid: String) -> Vec<models::Order> {
info!("lookup orders for customer: {}", &cid);
let i_s = db::Interface::open();
let i_list_key = format!("ol");
let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
if i_r == utils::empty_string() {
error!("order index not found");
}
let i_v_oid = i_r.split(",");
let i_v: Vec<String> = i_v_oid.map(|s| String::from(s)).collect();
let mut orders: Vec<models::Order> = Vec::new();
for o in i_v {
let order: models::Order = find(o);
if order.orid != utils::empty_string() && order.cid == cid {
orders.push(order);
}
}
orders
}

View file

@ -15,12 +15,18 @@ use nevmes_core::{
};
/// Send message
#[post("/", data = "<m_req>")]
#[post("/<r_type>", data = "<m_req>")]
pub async fn send_message(
m_req: Json<Message>,
r_type: String,
token: proof::PaymentProof,
) -> Custom<Json<Message>> {
let res: Message = message::create(m_req, token.get_jwp()).await;
let m_type: message::MessageType = if r_type == "multisig" {
message::MessageType::Multisig
} else {
message::MessageType::Normal
};
let res: Message = message::create(m_req, token.get_jwp(), m_type).await;
Custom(Status::Ok, Json(res))
}

View file

@ -37,7 +37,7 @@ pub async fn get_i2p_status() -> Custom<Json<i2p::HttpProxyStatus>> {
}
/// Share your contact information
/// TODO(c2m): configurable option to only allow adding after JWP creation
///
/// Protected: false
#[get("/")]
pub async fn share_contact_info() -> Custom<Json<models::Contact>> {
@ -78,11 +78,66 @@ pub async fn gen_jwp(proof: Json<proof::TxProof>) -> Custom<Json<reqres::Jwp>> {
// NEVMES Market APIs
//-----------------------------------------------
/// Get all products by passing vendor address
/// Get all products
///
/// Protected: true
/// Protected: false
#[get("/products")]
pub async fn get_products(_jwp: proof::PaymentProof) -> Custom<Json<Vec<models::Product>>> {
let m_products: Vec<models::Product> = product::find_all();
Custom(Status::Ok, Json(m_products))
}
/// Create order
///
/// Protected: true
#[post("/order/create", data = "<r_order>")]
pub async fn create_order(
r_order: Json<reqres::OrderRequest>,
_jwp: proof::PaymentProof) -> Custom<Json<models::Order>> {
let m_order: models::Order = order::create(r_order).await;
Custom(Status::Created, Json(m_order))
}
/// Customer order retreival. Must send `signature`
///
/// which is the order id signed by the wallet.
///
/// Protected: true
#[get("/order/retrieve/<orid>/<_signature>")]
pub async fn retrieve_order(
orid: String,
_signature: String,
_jwp: proof::PaymentProof) -> Custom<Json<models::Order>> {
// get customer address
// send address, orid and signature to verify()
let m_order: models::Order = order::find(orid);
Custom(Status::Created, Json(m_order))
}
/// Create order
///
/// Protected: true
#[get("/multisig/prepare/<orid>/<contact>")]
pub async fn get_prepare_multisig_info(
orid: String,
contact: String,
_jwp: proof::PaymentProof) -> Custom<Json<models::Order>> {
// TODO(c2m): create a multisig message
message::send_prepare_info(orid, contact).await;
Custom(Status::Ok, Json(Default::default()))
}
/// Recieve multisig messages here
///
/// Protected: true
#[post("/", data = "<message>")]
pub async fn rx_multisig_message(
_jwp: proof::PaymentProof,
message: Json<models::Message>,
) -> Custom<Json<models::Message>> {
message::rx_multisig(message).await;
Custom(Status::Ok, Json(Default::default()))
}

View file

@ -54,6 +54,7 @@ async fn rocket() -> _ {
.register("/", catchers![internal_error, not_found, payment_required])
.mount("/invoice", routes![controller::gen_invoice])
.mount("/message/rx", routes![controller::rx_message])
.mount("/message/rx/multisig", routes![controller::rx_multisig_message])
.mount("/prove", routes![controller::gen_jwp])
.mount("/share", routes![controller::share_contact_info])
.mount("/i2p", routes![controller::get_i2p_status])