Skip to content

Commit

Permalink
feat: add order signature verification (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmountaintop authored Jun 27, 2021
1 parent 88a3e32 commit f0b13a6
Show file tree
Hide file tree
Showing 9 changed files with 578 additions and 17 deletions.
350 changes: 339 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ actix-rt = "2.1.0"
actix-web = "4.0.0-beta.4"
anyhow = "1.0.38"
arrayref = "0.3.6"
babyjubjub-rs = { git = "https://github.com/lispc/babyjubjub-rs", features = [ "aarch64" ], default-features = false }
bytes = "1.0.1"
chrono = { version = "0.4.19", features = [ "serde" ] }
config_rs = { package = "config", version = "0.10.1" }
crossbeam-channel = "0.5.0"
dotenv = "0.15.0"
ff = { package = "ff_ce", version = "0.11", features = [ "derive" ] }
futures = "0.3.13"
futures-channel = "0.3.13"
futures-core = { version = "0.3.13", default-features = false }
Expand All @@ -25,7 +27,9 @@ itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
nix = "0.20.0"
num-bigint = { version = "0.2.2", features = [ "rand" ] }
num_enum = "0.5.1"
poseidon-rs = { version = "0.0.8" }
prost = "0.7.0"
prost-types = "0.7.0"
qstring = "0.7.2"
Expand Down Expand Up @@ -61,6 +65,6 @@ path = "src/bin/matchengine.rs"
[features]
windows_build = [ "rdkafka/dynamic_linking" ]
emit_state_diff = [ ]
default = ["emit_state_diff" ]
default = [ "emit_state_diff" ]
#default = ["windows_build"]
#default = ["windows_build", "emit_state_diff"]
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod storage;
pub use storage::{database, models, sqlxextend};
pub mod config;
pub mod message;
pub mod primitives;
pub mod restapi;
pub mod types;
pub mod utils;
48 changes: 47 additions & 1 deletion src/matchengine/asset/asset_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::config;
use anyhow::Result;
use crate::market::OrderCommitment;
use crate::matchengine::rpc::*;
use crate::primitives::*;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Eq, Hash)]
pub struct AssetInfo {
pub prec_save: u32,
pub prec_show: u32,
pub inner_id: u32,
}

#[derive(Clone)]
Expand All @@ -24,6 +29,7 @@ impl AssetManager {
AssetInfo {
prec_save: item.prec_save,
prec_show: item.prec_show,
inner_id: item.rollup_token_id as u32,
},
);
}
Expand All @@ -38,6 +44,7 @@ impl AssetManager {
AssetInfo {
prec_save: item.prec_save,
prec_show: item.prec_show,
inner_id: item.rollup_token_id as u32,
},
);
if ret.is_some() {
Expand All @@ -60,4 +67,43 @@ impl AssetManager {
pub fn asset_prec_show(&self, id: &str) -> u32 {
self.asset_get(id).unwrap().prec_show
}

pub fn commit_order(&self, o: &OrderPutRequest) -> Result<OrderCommitment> {
let assets: Vec<&str> = o.market.split('_').collect();
if assets.len() != 2 {
bail!("market error");
}
let base_token = match self.asset_get(assets[0]) {
Some(token) => token,
None => bail!("market base_token error"),
};
let quote_token = match self.asset_get(assets[1]) {
Some(token) => token,
None => bail!("market quote_token error"),
};
let amount = match rust_decimal::Decimal::from_str(&o.amount) {
Ok(d) => d,
_ => bail!("amount error"),
};
let price = match rust_decimal::Decimal::from_str(&o.price) {
Ok(d) => d,
_ => bail!("price error"),
};

match OrderSide::from_i32(o.order_side) {
Some(OrderSide::Ask) => Ok(OrderCommitment {
token_buy: u32_to_fr(quote_token.inner_id),
token_sell: u32_to_fr(base_token.inner_id),
total_buy: decimal_to_fr(&(amount * price), quote_token.prec_save),
total_sell: decimal_to_fr(&amount, base_token.prec_save),
}),
Some(OrderSide::Bid) => Ok(OrderCommitment {
token_buy: u32_to_fr(base_token.inner_id),
token_sell: u32_to_fr(quote_token.inner_id),
total_buy: decimal_to_fr(&amount, base_token.prec_save),
total_sell: decimal_to_fr(&(amount * price), quote_token.prec_save),
}),
None => bail!("market error"),
}
}
}
4 changes: 2 additions & 2 deletions src/matchengine/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::types::{ConnectionType, DbType, SimpleResult};
use crate::user_manager::{self, UserManager};
use crate::utils::{self, FTimestamp};

use anyhow::anyhow;
use anyhow::{anyhow, bail};
use rust_decimal::prelude::Zero;
use rust_decimal::Decimal;
use serde::Serialize;
Expand Down Expand Up @@ -725,7 +725,7 @@ impl Controller {
OPERATION_REGISTER_USER => {
self.register_user(false, serde_json::from_str(params)?)?;
}
_ => return Err(anyhow!("invalid operation {}", method)),
_ => bail!("invalid operation {}", method),
}
Ok(())
}
Expand Down
34 changes: 34 additions & 0 deletions src/matchengine/market/order.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::primitives::*;
use crate::types::{OrderSide, OrderType};
use crate::utils::InternedString;
use rust_decimal::Decimal;
Expand Down Expand Up @@ -175,3 +176,36 @@ pub struct OrderInput {
pub post_only: bool,
pub signature: [u8; 64],
}

pub struct OrderCommitment {
// order_id
// account_id
// nonce
pub token_sell: Fr,
pub token_buy: Fr,
pub total_sell: Fr,
pub total_buy: Fr,
}

impl OrderCommitment {
pub fn hash(&self) -> BigInt {
// consistent with https://github.com/Fluidex/circuits/blob/d6e06e964b9d492f1fa5513bcc2295e7081c540d/helper.ts/state-utils.ts#L38
// TxType::PlaceOrder
let magic_head = u32_to_fr(4);
let data = hash(&[
magic_head,
// TODO: sign nonce or order_id
//u32_to_fr(self.order_id),
self.token_sell,
self.token_buy,
self.total_sell,
self.total_buy,
]);
//data = hash([data, accountID, nonce]);
// nonce and orderID seems redundant?

// account_id is not needed if the hash is signed later?
//data = hash(&[data, u32_to_fr(self.account_id)]);
fr_to_bigint(&data)
}
}
11 changes: 10 additions & 1 deletion src/matchengine/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,19 @@ impl super::rpc::matchengine_server::Matchengine for GrpcHandler {
if self.settings.check_eddsa_signatue == OrderSignatrueCheck::Needed
|| self.settings.check_eddsa_signatue == OrderSignatrueCheck::Auto && !req.signature.is_empty()
{
// TODO:
// check order signature here
// order signature checking is not 'write' op, so it need not to be moved into the main thread
// it is better to finish it here
let stub = self.stub.read().await;
let order = stub
.balance_manager
.asset_manager
.commit_order(&req)
.map_err(|_| Status::invalid_argument("invalid order params"))?;
let msg = order.hash();
if !stub.user_manager.verify_signature(req.user_id, msg, &req.signature) {
return Err(Status::invalid_argument("invalid signature"));
}
}
let ControllerDispatch(act, rt) =
ControllerDispatch::new(move |ctrl: &mut Controller| Box::pin(async move { ctrl.order_put(true, req) }));
Expand Down
14 changes: 13 additions & 1 deletion src/matchengine/user_manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use crate::models::AccountDesc;
use crate::models::AccountDesc;
use crate::primitives::*;
use crate::types::ConnectionType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -36,6 +37,17 @@ impl UserManager {
}
Ok(())
}

pub fn verify_signature(&self, user_id: u32, msg: BigInt, signature: &str) -> bool {
match self.users.get(&user_id) {
None => false,
Some(user) => {
let pubkey = str_to_pubkey(&user.l2_pubkey).map_err(|_| false).unwrap();
let signature = str_to_signature(signature).map_err(|_| false).unwrap();
babyjubjub_rs::verify(pubkey, signature, msg)
}
}
}
}

impl Default for UserManager {
Expand Down
127 changes: 127 additions & 0 deletions src/primitives.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use rust_decimal::prelude::ToPrimitive;
use std::convert::TryInto;
use std::str::FromStr;

pub use babyjubjub_rs::{Point, Signature};
pub use ff::{from_hex, to_hex};
pub use ff::{Field, PrimeField, PrimeFieldRepr};
pub use num_bigint::BigInt;
pub use poseidon_rs::Fr;
pub use rust_decimal::Decimal;

lazy_static! {
//pub static ref POSEIDON_PARAMS: poseidon_rs::Constants = poseidon_rs::load_constants();
pub static ref POSEIDON_HASHER: poseidon_rs::Poseidon = poseidon_rs::Poseidon::new();
}

pub fn hash(inputs: &[Fr]) -> Fr {
(&POSEIDON_HASHER).hash(inputs.to_vec()).unwrap()
}

// TODO: these functions needed to be rewrite...

pub fn u32_to_fr(x: u32) -> Fr {
Fr::from_str(&format!("{}", x)).unwrap()
}

pub fn u64_to_fr(x: u64) -> Fr {
Fr::from_repr(poseidon_rs::FrRepr::from(x)).unwrap()
}

pub fn bigint_to_fr(x: BigInt) -> Fr {
let mut s = x.to_str_radix(16);
if s.len() % 2 != 0 {
// convert "f" to "0f"
s.insert(0, '0');
}
from_hex(&s).unwrap()
}

pub fn str_to_fr(x: &str) -> Fr {
if x.starts_with("0x") {
vec_to_fr(&hex::decode(x.trim_start_matches("0x")).unwrap()).unwrap()
} else {
let i = BigInt::from_str(x).unwrap();
bigint_to_fr(i)
}
}

pub fn vec_to_fr(arr: &[u8]) -> Result<Fr> {
if arr.len() > 32 {
anyhow::bail!("invalid vec len for fr");
}
let mut repr = <Fr as PrimeField>::Repr::default();

// prepad 0
let mut buf = arr.to_vec();
let required_length = repr.as_ref().len() * 8;
buf.reverse();
buf.resize(required_length, 0);
buf.reverse();

repr.read_be(&buf[..])?;
Ok(Fr::from_repr(repr)?)
}

pub fn fr_to_u32(f: &Fr) -> u32 {
fr_to_string(f).parse::<u32>().unwrap()
}

pub fn fr_to_i64(f: &Fr) -> i64 {
fr_to_string(f).parse::<i64>().unwrap()
}

pub fn fr_to_bigint(elem: &Fr) -> BigInt {
BigInt::parse_bytes(to_hex(elem).as_bytes(), 16).unwrap()
}

pub fn fr_to_string(elem: &Fr) -> String {
fr_to_bigint(&elem).to_str_radix(10)
}

pub fn fr_to_decimal(f: &Fr, scale: u32) -> Decimal {
Decimal::new(fr_to_i64(f), scale)
}

// big endian
pub fn fr_to_vec(f: &Fr) -> Vec<u8> {
let repr = f.into_repr();
let required_length = repr.as_ref().len() * 8;
let mut buf: Vec<u8> = Vec::with_capacity(required_length);
repr.write_be(&mut buf).unwrap();
buf
}

pub fn fr_to_bool(f: &Fr) -> Result<bool> {
if f.is_zero() {
Ok(false)
} else if f == &Fr::one() {
Ok(true)
} else {
Err(anyhow!("invalid fr"))
}
}

pub fn str_to_pubkey(pubkey: &str) -> Result<Point> {
let pubkey_packed = hex::decode(pubkey)?;
babyjubjub_rs::decompress_point(pubkey_packed.try_into().unwrap()).map_err(|e| anyhow!(e))
}

pub fn str_to_signature(signature: &str) -> Result<Signature> {
let sig_packed_vec = hex::decode(signature)?;
babyjubjub_rs::decompress_signature(&sig_packed_vec.try_into().unwrap()).map_err(|e| anyhow!(e))
}

pub fn decimal_to_u64(num: &Decimal, prec: u32) -> u64 {
let prec_mul = Decimal::new(10, 0).powi(prec as u64);
let adjusted = num * prec_mul;
adjusted.floor().to_u64().unwrap()
}

pub fn decimal_to_fr(num: &Decimal, prec: u32) -> Fr {
// TODO: is u64 enough?
u64_to_fr(decimal_to_u64(num, prec))
// Float864::from_decimal(num, prec).unwrap().to_fr()
}

0 comments on commit f0b13a6

Please sign in to comment.