diff --git a/Cargo.lock b/Cargo.lock index bde0c02..01ac7ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1572,7 +1572,7 @@ dependencies = [ [[package]] name = "grin_wallet_api" version = "5.1.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" +source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213" dependencies = [ "base64 0.12.3", "chrono", @@ -1596,7 +1596,7 @@ dependencies = [ [[package]] name = "grin_wallet_config" version = "5.1.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" +source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213" dependencies = [ "dirs", "grin_wallet_util", @@ -1609,7 +1609,7 @@ dependencies = [ [[package]] name = "grin_wallet_impls" version = "5.1.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" +source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213" dependencies = [ "base64 0.12.3", "blake2-rfc", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "grin_wallet_libwallet" version = "5.1.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" +source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213" dependencies = [ "age", "base64 0.9.3", @@ -1676,7 +1676,7 @@ dependencies = [ [[package]] name = "grin_wallet_util" version = "5.1.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" +source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213" dependencies = [ "data-encoding", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 2f9040f..761c92c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,6 @@ grin_core = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alp grin_chain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_keychain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_servers = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } -grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } -grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } -grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } \ No newline at end of file +grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } +grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } +grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" } \ No newline at end of file diff --git a/README.md b/README.md index 361d3c3..8db7fd4 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,13 @@ The first CoinSwap server (n1) provides the `swap` API, publicly avai **params:** ``` [{ - "comsig": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", - "msg": "00010203", + "comsig": "0835f4b8b9cd286c9e35475f575c3e4ae71ceb4ff36598504662627afd628a17d6ba7dedb1aa4c47f0fabad026b76fc86d06f3bef8d0621b8ac4601d4b1b98401586ca3374a401508f32049212478ae91cfa474dfaa5ef2c3dd559d5a292e02334", "onion": { - "commit": "0967593792bc958cd73848c0b948ecab2c6e996ab3c550d462fe41359e447b651f", - "data": ["3719e5fba260c71a5a4bcf9d9caa58cd5dc49531388782fae7699c6fa6b30b09fe42"], - "pubkey": "020dd38a220280f14515f6901a3a366cb7b87630814e4b68b3189a32df964961e5" + "commit": "099a8922343f242dd3da29935ba5bbc7e38bf68eccfb8c96aec87aec0535199139", + "data":[ + "758bc7339edfe8a1f117ef2ef02de58b33d99e820ffd2c21a3d108f4cbdadfbcbc061b705fc351ae2fb34a855bb64180fe8b4913ea13b3bf826773e4b293166cdad1fdf59cadd7d33f73a8f3fc0f339a849f9c505c573d8cc2f006082d8f5bf2c2c84c18d5873a821d9f60bcdd44cf0566d04d761a1eda005cd19ab0b1c593da4656dd2a09322fe1d725f61e8855c927844ec9e80b9260a58012f7c519c5ca3d9b192ab1104d30ac09fb844bd7f692214e5cf0f6cdf1477c20708c2f3ecb098dce6be661907593918840b0fe8eb5043996dace45f2fb66e8f1e2a732035b4e6447b8904f313badebcba99f75c003e297d8dd815915f534dfa7ed416af0c415b60d2a0186752af6af33b781f31fdd3016aeee3bd2e47743fe2ce391b3354b9036b56ec38ed7539adafbc96bef1dbaf354a805b03ac0df7a0d32cff91716926bce68c8ccebb607340f2ffe09c08a9c9fd282ea19b33c69107ed5c54d4872eb0ed83c38d7e07606722069d7709fb914e1e02ea23323f3ae9252902dbfa6f15bd83a3f64587c9ae23aaf96b2a95e1341da12a6e423cf95375184752e10c1dd1a599db74ac0c3d74ec270c589f6a3bdd0877eb986d9a58a8548b917e22bfb93a4a06c36d7cad8d4a8791a8d1e1dc683429b440b136c43ad2f664dafc5156b808050a3c4d28771877d3f1d3a9daa2585eae259aaa64745c6cd260f577e538e27be3c985db41b7c456b63c5b18d7d17420a277d4abc04ae892ceb26940b09fb322445846c14898f5f59305490b1338c56384cd0c7bf5950a0a403aec4d2c2f5e2378b5eb7b1e7fcdbd8d6cc547f3b5a372b22e50e37d858bb197392a10fb9e6e292d6ed6bd8eab1fef7f2d069b6250a0e3e597ccf9a062e04b68821f5c57328ddab775d141147b71c1764c911bad03d8b88e2e62034bc899395514ecab4dec8ab341ba114f0a4e5d1dcfa182396c0e4826ddee187b07bb524dfeaa5297f7a5465f99eaaaa37f082c787b94811feb15b57d68369e6a7e3761d" + ], + "pubkey": "033946e6a495e7278027b38be3d500cfc23d3e0836f1b7e24513841437f316ccb0" } }] ``` diff --git a/src/config.rs b/src/config.rs index 8da88bd..2369c88 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,7 +15,8 @@ use std::path::PathBuf; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ServerConfig { pub key: SecretKey, - pub addr: SocketAddr, + pub interval_s: u32, + pub addr: SocketAddr, pub grin_node_url: SocketAddr, pub wallet_owner_url: SocketAddr, } @@ -34,7 +35,6 @@ impl EncryptedServerKey { password: &ZeroingString, ) -> Result { let salt: [u8; 8] = thread_rng().gen(); - let nonce: [u8; 12] = thread_rng().gen(); let password = password.as_bytes(); let mut key = [0; 32]; ring::pbkdf2::derive( @@ -49,6 +49,7 @@ impl EncryptedServerKey { let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); let sealing_key: aead::LessSafeKey = aead::LessSafeKey::new(unbound_key); + let nonce: [u8; 12] = thread_rng().gen(); let aad = aead::Aad::from(&[]); let _ = sealing_key.seal_in_place_append_tag( aead::Nonce::assume_unique_for_key(nonce), @@ -106,7 +107,8 @@ struct RawConfig { pub encrypted_key: String, pub salt: String, pub nonce: String, - pub addr: SocketAddr, + pub interval_s: u32, + pub addr: SocketAddr, pub grin_node_url: SocketAddr, pub wallet_owner_url: SocketAddr, } @@ -119,6 +121,7 @@ pub fn write_config(config_path: &PathBuf, server_config: &ServerConfig, passwo encrypted_key: encrypted.encrypted_key, salt: encrypted.salt, nonce: encrypted.nonce, + interval_s: server_config.interval_s, addr: server_config.addr, grin_node_url: server_config.grin_node_url, wallet_owner_url: server_config.wallet_owner_url, @@ -146,6 +149,7 @@ pub fn load_config(config_path: &PathBuf, password: &ZeroingString) -> Result = std::result::Result; +pub type StdResult = std::result::Result; #[derive(Clone, Debug, Eq, Fail, PartialEq)] /// MWixnet error types pub enum ErrorKind { - /// Unsupported payload version - #[fail(display = "Unsupported Payload Version")] - UnsupportedPayload, /// Error from secp256k1-zkp library #[fail(display = "Secp Error")] SecpError, @@ -29,23 +27,9 @@ pub enum ErrorKind { String, io::ErrorKind, ), - /// Expected a given value that wasn't found - #[fail(display = "UnexpectedData")] - UnexpectedData { - /// What we wanted - expected: Vec, - /// What we got - received: Vec, - }, /// Data wasn't in a consumable format #[fail(display = "CorruptedData")] CorruptedData, - /// Incorrect number of elements (when deserializing a vec via read_multi say). - #[fail(display = "CountError")] - CountError, - /// When asked to read too much data - #[fail(display = "TooLargeReadErr")] - TooLargeReadErr, /// Error from grin's api crate #[fail(display = "GRIN API Error")] GrinApiError, @@ -85,6 +69,14 @@ impl From for Error { } } +impl From for Error { + fn from(_e: grin_core::ser::Error) -> Error { + Error { + inner: Context::new(ErrorKind::GrinCoreError), + } + } +} + impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.inner, f) @@ -98,10 +90,6 @@ impl Error { } } - pub fn kind(&self) -> ErrorKind { - self.inner.get_context().clone() - } - pub fn message(&self) -> String { format!("{}", self).into() } diff --git a/src/main.rs b/src/main.rs index 796c5b6..4704a92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ use config::ServerConfig; use error::{Error, ErrorKind}; -use wallet::Wallet; +use node::HttpGrinNode; +use wallet::HttpWallet; use clap::App; -use grin_util::ZeroingString; +use grin_util::{StopState, ZeroingString}; use std::env; use std::path::PathBuf; +use std::sync::Arc; #[macro_use] extern crate lazy_static; @@ -18,11 +20,12 @@ mod error; mod node; mod onion; mod secp; -mod ser; mod server; mod types; mod wallet; +const DEFAULT_INTERVAL: u32 = 12 * 60 * 60; + fn main() -> std::result::Result<(), Box> { let yml = load_yaml!("../mwixnet.yml"); let args = App::from_yaml(yml).get_matches(); @@ -38,6 +41,7 @@ fn main() -> std::result::Result<(), Box> { let password = args.value_of("pass").ok_or(Error::new(ErrorKind::LoadConfigError))?; let password = ZeroingString::from(password); + let round_time = args.value_of("round_time").map(|t| t.parse::().unwrap() ); let bind_addr = args.value_of("bind_addr"); let grin_node_url = args.value_of("grin_node_url"); let wallet_owner_url = args.value_of("wallet_owner_url"); @@ -50,6 +54,7 @@ fn main() -> std::result::Result<(), Box> { let server_config = ServerConfig { key: secp::random_secret(), + interval_s: round_time.unwrap_or(DEFAULT_INTERVAL), addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?, grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?, wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?, @@ -79,13 +84,24 @@ fn main() -> std::result::Result<(), Box> { // Open wallet let wallet_pass = args.value_of("wallet_pass").ok_or(error::Error::new(error::ErrorKind::LoadConfigError))?; let wallet_pass = grin_util::ZeroingString::from(wallet_pass); - let wallet = Wallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?; + let wallet = HttpWallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?; - let shutdown_signal = async move { - // Wait for the CTRL+C signal - tokio::signal::ctrl_c() - .await - .expect("failed to install CTRL+C signal handler"); - }; - server::listen(&server_config, &wallet, shutdown_signal) + // Create GrinNode + let node = HttpGrinNode::new(&server_config.grin_node_url, &None); + + let stop_state = Arc::new(StopState::new()); + + let stop_state_clone = stop_state.clone(); + std::thread::spawn(move || { + let shutdown_signal = async move { + // Wait for the CTRL+C signal + tokio::signal::ctrl_c() + .await + .expect("failed to install CTRL+C signal handler"); + }; + futures::executor::block_on(shutdown_signal); + stop_state_clone.stop(); + }); + + server::listen(&server_config, Arc::new(wallet), Arc::new(node), &stop_state) } \ No newline at end of file diff --git a/src/node.rs b/src/node.rs index 5849263..fa02cc4 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,31 +1,85 @@ -use crate::config::ServerConfig; use crate::error::{ErrorKind, Result}; use crate::secp::Commitment; use grin_api::client; use grin_api::json_rpc::{build_request, Request, Response}; -use grin_api::{BlockPrintable, LocatedTxKernel, OutputPrintable, OutputType, Tip}; +use grin_api::{OutputPrintable, OutputType, Tip}; use grin_core::consensus::COINBASE_MATURITY; use grin_core::core::{Input, OutputFeatures, Transaction}; use grin_util::ToHex; use serde_json::json; +use std::collections::HashMap; use std::net::SocketAddr; +use std::sync::{Arc, RwLock}; -const ENDPOINT: &str = "/v2/foreign"; +pub trait GrinNode : Send + Sync { + /// Retrieves the unspent output with a matching commitment + fn get_utxo(&self, output_commit: &Commitment) -> Result>; + /// Gets the height of the chain tip + fn get_chain_height(&self) -> Result; + + /// Posts a transaction to the grin node + fn post_tx(&self, tx: &Transaction) -> Result<()>; +} + +/// Checks whether a commitment is spendable at the block height provided +pub fn is_spendable(node: &Arc, output_commit: &Commitment, next_block_height: u64) -> Result { + let output = node.get_utxo(&output_commit)?; + if let Some(out) = output { + let is_coinbase = match out.output_type { + OutputType::Coinbase => true, + OutputType::Transaction => false, + }; + + if is_coinbase { + if let Some(block_height) = out.block_height { + if block_height + COINBASE_MATURITY < next_block_height { + return Ok(false); + } + } else { + return Ok(false); + } + } + + return Ok(true); + } + + Ok(false) +} + +/// Builds an input for an unspent output commitment +pub fn build_input(node: &Arc, output_commit: &Commitment) -> Result> { + let output = node.get_utxo(&output_commit)?; + + if let Some(out) = output { + let features = match out.output_type { + OutputType::Coinbase => OutputFeatures::Coinbase, + OutputType::Transaction => OutputFeatures::Plain, + }; + + let input = Input::new(features, out.commit); + return Ok(Some(input)); + } + + Ok(None) +} + +/// HTTP (JSONRPC) implementation of the 'GrinNode' trait #[derive(Clone)] -pub struct HTTPNodeClient { +pub struct HttpGrinNode { node_url: SocketAddr, node_api_secret: Option, } -impl HTTPNodeClient { - /// Create a new client that will communicate with the given grin node - pub fn new(node_url: &SocketAddr, node_api_secret: Option) -> HTTPNodeClient { - HTTPNodeClient { +const ENDPOINT: &str = "/v2/foreign"; + +impl HttpGrinNode { + pub fn new(node_url: &SocketAddr, node_api_secret: &Option) -> HttpGrinNode { + HttpGrinNode { node_url: node_url.to_owned(), - node_api_secret: node_api_secret, + node_api_secret: node_api_secret.to_owned(), } } @@ -43,61 +97,8 @@ impl HTTPNodeClient { } } -#[derive(Clone)] -pub struct GrinNode { - client: HTTPNodeClient, -} - -impl GrinNode { - pub fn new(server_config: &ServerConfig) -> GrinNode { - GrinNode { - client: HTTPNodeClient::new(&server_config.grin_node_url, None), - } - } - - // Checks whether a commitment is spendable at the block height provided - pub fn is_spendable(&self, output_commit: &Commitment, next_block_height: u64) -> Result { - let output = self.get_output(&output_commit)?; - if let Some(out) = output { - let is_coinbase = match out.output_type { - OutputType::Coinbase => true, - OutputType::Transaction => false, - }; - - if is_coinbase { - if let Some(block_height) = out.block_height { - if block_height + COINBASE_MATURITY < next_block_height { - return Ok(false); - } - } else { - return Ok(false); - } - } - - return Ok(true); - } - - Ok(false) - } - - /// Builds an input for an unspent output commitment - pub fn build_input(&self, output_commit: &Commitment) -> Result> { - let output = self.get_output(&output_commit)?; - - if let Some(out) = output { - let features = match out.output_type { - OutputType::Coinbase => OutputFeatures::Coinbase, - OutputType::Transaction => OutputFeatures::Plain, - }; - - let input = Input::new(features, out.commit); - return Ok(Some(input)); - } - - Ok(None) - } - - fn get_output(&self, output_commit: &Commitment) -> Result> { +impl GrinNode for HttpGrinNode { + fn get_utxo(&self, output_commit: &Commitment) -> Result> { let commits : Vec = vec![output_commit.to_hex()]; let start_height : Option = None; let end_height : Option = None; @@ -105,7 +106,7 @@ impl GrinNode { let include_merkle_proof : Option = Some(false); let params = json!([Some(commits), start_height, end_height, include_proof, include_merkle_proof]); - let outputs = self.client.send_json_request::>("get_outputs", ¶ms)?; + let outputs = self.send_json_request::>("get_outputs", ¶ms)?; if outputs.is_empty() { return Ok(None); } @@ -113,10 +114,9 @@ impl GrinNode { Ok(Some(outputs[0].clone())) } - /// Gets the height of the chain tip - pub fn get_chain_height(&self) -> Result { + fn get_chain_height(&self) -> Result { let params = json!([]); - let tip_json = self.client.send_json_request::("get_tip", ¶ms)?; + let tip_json = self.send_json_request::("get_tip", ¶ms)?; let tip: Result = serde_json::from_value(tip_json["Ok"].clone()) .map_err(|_| ErrorKind::SerdeJsonError.into()); @@ -124,46 +124,67 @@ impl GrinNode { Ok(tip?.height) } - /// Posts a transaction to the grin node - pub fn post_tx(&self, tx: &Transaction) -> Result<()> { + fn post_tx(&self, tx: &Transaction) -> Result<()> { let params = json!([tx, true]); - self.client.send_json_request::("push_transaction", ¶ms)?; + self.send_json_request::("push_transaction", ¶ms)?; Ok(()) } +} - // milestone 3: needed to handle chain reorgs - pub fn chain_has_kernel( - &self, - kernel_excess: &Commitment, - start_height: Option, - end_height: Option, - ) -> Result { - let params = json!([kernel_excess, start_height, end_height]); - let located_kernel_json = self.client.send_json_request::("get_kernel", ¶ms)?; +pub struct MockGrinNode { + utxos: HashMap, + txns_posted: RwLock>, +} - let located_kernel : Result = serde_json::from_value(located_kernel_json["Ok"].clone()) - .map_err(|_| ErrorKind::SerdeJsonError.into()); - - Ok(located_kernel.is_ok()) +impl MockGrinNode { + pub fn new() -> MockGrinNode { + MockGrinNode { + utxos: HashMap::new(), + txns_posted: RwLock::new(Vec::new()), + } } - // milestone 3: needed to handle chain reorgs - pub fn block_has_kernel(&self, block_hash: &String, kernel_excess: &Commitment) -> Result { - let block = self.get_block(None, Some(block_hash.clone()), None)?; - - let found = block.kernels.into_iter() - .any(|kern| kern.excess == kernel_excess.to_hex()); - Ok(found) + pub fn add_utxo(&mut self, output_commit: &Commitment, utxo: &OutputPrintable) { + self.utxos.insert(output_commit.clone(), utxo.clone()); } - // milestone 3: needed to handle chain reorgs - fn get_block(&self, - height: Option, - hash: Option, - commit: Option, - ) -> Result { - let params = json!([height, hash, commit]); - let block_printable = self.client.send_json_request::("get_block", ¶ms)?; - Ok(block_printable) + pub fn add_default_utxo(&mut self, output_commit: &Commitment) { + let utxo = OutputPrintable{ + output_type: OutputType::Transaction, + commit: output_commit.to_owned(), + spent: false, + proof: None, + proof_hash: String::from(""), + block_height: None, + merkle_proof: None, + mmr_index: 0 + }; + + self.add_utxo(&output_commit, &utxo); + } + + pub fn get_posted_txns(&self) -> Vec { + let read = self.txns_posted.read().unwrap(); + read.clone() + } +} + +impl GrinNode for MockGrinNode { + fn get_utxo(&self, output_commit: &Commitment) -> Result> { + if let Some(utxo) = self.utxos.get(&output_commit) { + return Ok(Some(utxo.clone())); + } + + Ok(None) + } + + fn get_chain_height(&self) -> Result { + Ok(100) + } + + fn post_tx(&self, tx: &Transaction) -> Result<()> { + let mut write = self.txns_posted.write().unwrap(); + write.push(tx.clone()); + Ok(()) } } \ No newline at end of file diff --git a/src/onion.rs b/src/onion.rs index 9b17fd9..11541aa 100644 --- a/src/onion.rs +++ b/src/onion.rs @@ -1,89 +1,75 @@ use crate::error::Result; use crate::secp::{self, Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret}; -use crate::types::{Hop, Onion, RawBytes, Payload, deserialize_payload, serialize_payload}; -use crate::ser; +use crate::types::Payload; use chacha20::{ChaCha20, Key, Nonce}; use chacha20::cipher::{NewCipher, StreamCipher}; +use grin_core::ser::{self, ProtocolVersion, Writeable, Writer}; +use grin_util::{self, ToHex}; use hmac::{Hmac, Mac}; +use serde::{Deserialize}; +use serde::ser::SerializeStruct; use sha2::{Digest, Sha256}; +use std::fmt; type HmacSha256 = Hmac; +type RawBytes = Vec; -/// Create an Onion for the Commitment, encrypting the payload for each hop -#[allow(dead_code)] // used by component tests -pub fn create_onion(commitment: &Commitment, session_key: &SecretKey, hops: &Vec) -> Result { - let secp = Secp256k1::new(); - let mut ephemeral_key = session_key.clone(); - - let mut shared_secrets: Vec = Vec::new(); - let mut enc_payloads: Vec = Vec::new(); - for hop in hops { - let shared_secret = SharedSecret::new(&secp, &hop.pubkey, &ephemeral_key); - - let ephemeral_pubkey = PublicKey::from_secret_key(&secp, &ephemeral_key)?; - let blinding_factor = calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?; - - shared_secrets.push(shared_secret); - enc_payloads.push(serialize_payload(&hop.payload)?); - ephemeral_key.mul_assign(&secp, &blinding_factor)?; - } - - for i in (0..shared_secrets.len()).rev() { - let mut cipher = new_stream_cipher(&shared_secrets[i])?; - for j in i..shared_secrets.len() { - cipher.apply_keystream(&mut enc_payloads[j]); - } - } - - let onion = Onion{ - ephemeral_pubkey: secp::to_public_key(&session_key)?, - commit: commitment.clone(), - enc_payloads: enc_payloads, - }; - Ok(onion) +#[derive(Clone, Debug, PartialEq)] +pub struct Onion { + pub ephemeral_pubkey: PublicKey, + pub commit: Commitment, + pub enc_payloads: Vec, } -/// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload -pub fn peel_layer(onion: &Onion, secret_key: &SecretKey) -> Result<(Payload, Onion)> { - let secp = Secp256k1::new(); +impl Onion { + pub fn serialize(&self) -> Result> { + let mut vec = vec![]; + ser::serialize_default(&mut vec, &self)?; + Ok(vec) + } + + /// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload + pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<(Payload, Onion)> { + let secp = Secp256k1::new(); + + let shared_secret = SharedSecret::new(&secp, &self.ephemeral_pubkey, &secret_key); + let mut cipher = new_stream_cipher(&shared_secret)?; - let shared_secret = SharedSecret::new(&secp, &onion.ephemeral_pubkey, &secret_key); - let mut cipher = new_stream_cipher(&shared_secret)?; - - let mut decrypted_bytes = onion.enc_payloads[0].clone(); - cipher.apply_keystream(&mut decrypted_bytes); - let decrypted_payload = deserialize_payload(&decrypted_bytes)?; - - let enc_payloads : Vec = onion.enc_payloads.iter() - .enumerate() - .filter(|&(i, _)| i != 0) - .map(|(_, enc_payload)| { - let mut p = enc_payload.clone(); - cipher.apply_keystream(&mut p); - p - }) - .collect(); - - let blinding_factor = calc_blinding_factor(&shared_secret, &onion.ephemeral_pubkey)?; + let mut decrypted_bytes = self.enc_payloads[0].clone(); + cipher.apply_keystream(&mut decrypted_bytes); + let decrypted_payload = Payload::deserialize(&decrypted_bytes)?; - let mut ephemeral_pubkey = onion.ephemeral_pubkey.clone(); - ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?; - - let mut commitment = onion.commit.clone(); - commitment = secp::add_excess(&commitment, &decrypted_payload.excess)?; - commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())?; - - let peeled_onion = Onion{ - ephemeral_pubkey: ephemeral_pubkey, - commit: commitment.clone(), - enc_payloads: enc_payloads, - }; - Ok((decrypted_payload, peeled_onion)) + let enc_payloads : Vec = self.enc_payloads.iter() + .enumerate() + .filter(|&(i, _)| i != 0) + .map(|(_, enc_payload)| { + let mut p = enc_payload.clone(); + cipher.apply_keystream(&mut p); + p + }) + .collect(); + + let blinding_factor = calc_blinding_factor(&shared_secret, &self.ephemeral_pubkey)?; + + let mut ephemeral_pubkey = self.ephemeral_pubkey.clone(); + ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?; + + let mut commitment = self.commit.clone(); + commitment = secp::add_excess(&commitment, &decrypted_payload.excess)?; + commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())?; + + let peeled_onion = Onion{ + ephemeral_pubkey: ephemeral_pubkey, + commit: commitment.clone(), + enc_payloads: enc_payloads, + }; + Ok((decrypted_payload, peeled_onion)) + } } fn calc_blinding_factor(shared_secret: &SharedSecret, ephemeral_pubkey: &PublicKey) -> Result { - let serialized_pubkey = ser::ser_vec(&ephemeral_pubkey)?; + let serialized_pubkey = ser::ser_vec(&ephemeral_pubkey, ProtocolVersion::local())?; let mut hasher = Sha256::default(); hasher.update(&serialized_pubkey); @@ -105,11 +91,160 @@ fn new_stream_cipher(shared_secret: &SharedSecret) -> Result { Ok(ChaCha20::new(&key, &nonce)) } +impl Writeable for Onion { + fn write(&self, writer: &mut W) -> std::result::Result<(), ser::Error> { + self.ephemeral_pubkey.write(writer)?; + writer.write_fixed_bytes(&self.commit)?; + writer.write_u64(self.enc_payloads.len() as u64)?; + for p in &self.enc_payloads { + writer.write_u64(p.len() as u64)?; + p.write(writer)?; + } + Ok(()) + } +} + +impl serde::ser::Serialize for Onion { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("Onion", 3)?; + + let secp = Secp256k1::new(); + state.serialize_field("pubkey", &self.ephemeral_pubkey.serialize_vec(&secp, true).to_hex())?; + state.serialize_field("commit", &self.commit.to_hex())?; + + let hex_payloads: Vec = self.enc_payloads.iter().map(|v| v.to_hex()).collect(); + state.serialize_field("data", &hex_payloads)?; + state.end() + } +} + +impl<'de> serde::de::Deserialize<'de> for Onion { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::de::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Pubkey, + Commit, + Data + } + + struct OnionVisitor; + + impl<'de> serde::de::Visitor<'de> for OnionVisitor { + type Value = Onion; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an Onion") + } + + fn visit_map(self, mut map: A) -> std::result::Result + where + A: serde::de::MapAccess<'de>, + { + let mut pubkey = None; + let mut commit = None; + let mut data = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Pubkey => { + let val: String = map.next_value()?; + let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; + let secp = Secp256k1::new(); + pubkey = Some(PublicKey::from_slice(&secp, &vec[..]).map_err(serde::de::Error::custom)?); + } + Field::Commit => { + let val: String = map.next_value()?; + let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; + commit = Some(Commitment::from_vec(vec)); + } + Field::Data => { + let val: Vec = map.next_value()?; + let mut vec: Vec> = Vec::new(); + for hex in val { + vec.push(grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?); + } + data = Some(vec); + } + } + } + + Ok(Onion { + ephemeral_pubkey: pubkey.unwrap(), + commit: commit.unwrap(), + enc_payloads: data.unwrap(), + }) + } + } + + const FIELDS: &[&str] = &[ + "pubkey", + "commit", + "data" + ]; + deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor) + } +} + #[cfg(test)] -mod tests { - use super::super::secp; - use super::super::types; - use super::super::onion; +pub mod test_util { + use super::{Onion, RawBytes}; + use crate::error::Result; + use crate::types::{Payload}; + use crate::secp::{Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret}; + + use chacha20::cipher::{StreamCipher}; + + pub struct Hop { + pub pubkey: PublicKey, + pub payload: Payload, + } + + /// Create an Onion for the Commitment, encrypting the payload for each hop + pub fn create_onion(commitment: &Commitment, session_key: &SecretKey, hops: &Vec) -> Result { + let secp = Secp256k1::new(); + let mut ephemeral_key = session_key.clone(); + + let mut shared_secrets: Vec = Vec::new(); + let mut enc_payloads: Vec = Vec::new(); + for hop in hops { + let shared_secret = SharedSecret::new(&secp, &hop.pubkey, &ephemeral_key); + + let ephemeral_pubkey = PublicKey::from_secret_key(&secp, &ephemeral_key)?; + let blinding_factor = super::calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?; + + shared_secrets.push(shared_secret); + enc_payloads.push(hop.payload.serialize()?); + ephemeral_key.mul_assign(&secp, &blinding_factor)?; + } + + for i in (0..shared_secrets.len()).rev() { + let mut cipher = super::new_stream_cipher(&shared_secrets[i])?; + for j in i..shared_secrets.len() { + cipher.apply_keystream(&mut enc_payloads[j]); + } + } + + let onion = Onion{ + ephemeral_pubkey: PublicKey::from_secret_key(&secp, session_key)?, + commit: commitment.clone(), + enc_payloads: enc_payloads, + }; + Ok(onion) + } +} + +#[cfg(test)] +pub mod tests { + use super::test_util::{self, Hop}; + use crate::types::{Payload}; + use crate::secp; use grin_core::core::FeeFields; @@ -124,7 +259,7 @@ mod tests { let commitment = secp::commit(in_value, &blind).unwrap(); let session_key = secp::random_secret(); - let mut hops : Vec = Vec::new(); + let mut hops : Vec = Vec::new(); let mut keys : Vec = Vec::new(); let mut final_commit = secp::commit(out_value, &blind).unwrap(); @@ -146,9 +281,9 @@ mod tests { None }; - hops.push(types::Hop{ + hops.push(Hop{ pubkey: secp::PublicKey::from_secret_key(&secp, &keys[i]).unwrap(), - payload: types::Payload{ + payload: Payload{ excess: excess, fee: FeeFields::from(fee_per_hop as u32), rangeproof: proof, @@ -156,15 +291,15 @@ mod tests { }); } - let mut onion_packet = onion::create_onion(&commitment, &session_key, &hops).unwrap(); + let mut onion_packet = test_util::create_onion(&commitment, &session_key, &hops).unwrap(); - let mut payload = types::Payload{ + let mut payload = Payload{ excess: secp::random_secret(), fee: FeeFields::from(fee_per_hop as u32), rangeproof: None }; for i in 0..5 { - let peeled = onion::peel_layer(&onion_packet, &keys[i]).unwrap(); + let peeled = onion_packet.peel_layer(&keys[i]).unwrap(); payload = peeled.0; onion_packet = peeled.1; } diff --git a/src/secp.rs b/src/secp.rs index 7844e73..4e45827 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -5,27 +5,26 @@ pub use secp256k1zkp::pedersen::{Commitment, RangeProof}; pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY}; pub use secp256k1zkp::constants::{AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE, SECRET_KEY_SIZE}; -use crate::ser::{self, Readable, Reader, Writeable, Writer}; use crate::error::{Error, ErrorKind, Result}; use blake2::blake2b::Blake2b; use byteorder::{BigEndian, ByteOrder}; +use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; use secp256k1zkp::rand::thread_rng; -use std::cmp; /// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys pub struct ComSignature { - pub pub_nonce: Commitment, - pub s: SecretKey, - pub t: SecretKey, + pub_nonce: Commitment, + s: SecretKey, + t: SecretKey, } impl ComSignature { pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature { ComSignature { - pub_nonce: pub_nonce.clone(), - s: s.clone(), - t: t.clone(), + pub_nonce: pub_nonce.to_owned(), + s: s.to_owned(), + t: t.to_owned(), } } @@ -34,7 +33,7 @@ impl ComSignature { let secp = Secp256k1::with_caps(ContextFlag::Commit); let mut amt_bytes = [0; 32]; - BigEndian::write_u64(&mut amt_bytes[24..31], amount); + BigEndian::write_u64(&mut amt_bytes[24..32], amount); let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?; let k_1 = SecretKey::new(&secp, &mut thread_rng()); @@ -71,7 +70,7 @@ impl ComSignature { let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()]; let S2 = secp.commit_sum(commits, Vec::new())?; - if S1 == S2 { + if S1 != S2 { return Err(Error::new(ErrorKind::InvalidSigError)); } @@ -94,9 +93,8 @@ impl ComSignature { /// Serializes a ComSignature to and from hex pub mod comsig_serde { use super::ComSignature; - use super::ser::{self, BinReader, Readable}; + use grin_core::ser::{self, ProtocolVersion}; use serde::{Deserialize, Serializer}; - use std::io::Cursor; use grin_util::ToHex; /// Serializes a ComSignature as a hex string @@ -105,7 +103,7 @@ pub mod comsig_serde { S: Serializer, { use serde::ser::Error; - let bytes = ser::ser_vec(&comsig).map_err(Error::custom)?; + let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?; serializer.serialize_str(&bytes.to_hex()) } @@ -117,18 +115,29 @@ pub mod comsig_serde { use serde::de::Error; let bytes = String::deserialize(deserializer) .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; - - let mut cursor = Cursor::new(&bytes); - let mut reader = BinReader::new(&mut cursor); - ComSignature::read(&mut reader).map_err(Error::custom) + let sig: ComSignature = grin_core::ser::deserialize_default(&mut &bytes[..]) + .map_err(Error::custom)?; + Ok(sig) } } -/// Compute a PublicKey from a SecretKey -pub fn to_public_key(secret_key: &SecretKey) -> Result { - let secp = Secp256k1::new(); - let pubkey = PublicKey::from_secret_key(&secp, secret_key)?; - Ok(pubkey) +#[allow(non_snake_case)] +impl Readable for ComSignature { + fn read(reader: &mut R) -> std::result::Result { + let R = Commitment::read(reader)?; + let s = read_secret_key(reader)?; + let t = read_secret_key(reader)?; + Ok(ComSignature::new(&R, &s, &t)) + } +} + +impl Writeable for ComSignature { + fn write(&self, writer: &mut W) -> std::result::Result<(), ser::Error> { + writer.write_fixed_bytes(self.pub_nonce.0)?; + writer.write_fixed_bytes(self.s.0)?; + writer.write_fixed_bytes(self.t.0)?; + Ok(()) + } } /// Generate a random SecretKey. @@ -137,6 +146,14 @@ pub fn random_secret() -> SecretKey { SecretKey::new(&secp, &mut thread_rng()) } +/// Deserialize a SecretKey from a Reader +pub fn read_secret_key(reader: &mut R) -> std::result::Result { + let buf = reader.read_fixed_bytes(SECRET_KEY_SIZE)?; + let secp = Secp256k1::with_caps(ContextFlag::None); + let pk = SecretKey::from_slice(&secp, &buf).map_err(|_| ser::Error::CorruptedData)?; + Ok(pk) +} + /// Build a Pedersen Commitment using the provided value and blinding factor pub fn commit(value: u64, blind: &SecretKey) -> Result { let secp = Secp256k1::with_caps(ContextFlag::Commit); @@ -154,6 +171,7 @@ pub fn add_excess(commitment: &Commitment, excess: &SecretKey) -> Result Result { let secp = Secp256k1::with_caps(ContextFlag::Commit); let neg_commit : Commitment = secp.commit(value, ZERO_KEY)?; @@ -161,18 +179,7 @@ pub fn sub_value(commitment: &Commitment, value: u64) -> Result { Ok(sum) } -pub fn add_blinds(excesses: &Vec) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - let sum = secp.blind_sum(excesses.clone(), Vec::new())?; - Ok(sum) -} - -pub fn sub_blinds(minuend: &SecretKey, subtrahend: &SecretKey) -> Result { - let secp = Secp256k1::with_caps(ContextFlag::Commit); - let result = secp.blind_sum(vec![minuend.clone()], vec![subtrahend.clone()])?; - Ok(result) -} - +/// Signs the message with the provided SecretKey pub fn sign(sk: &SecretKey, msg: &Message) -> Result { let secp = Secp256k1::with_caps(ContextFlag::Full); let pubkey = PublicKey::from_secret_key(&secp, &sk)?; @@ -180,107 +187,33 @@ pub fn sign(sk: &SecretKey, msg: &Message) -> Result { Ok(sig) } -/// secp256k1-zkp object serialization +#[cfg(test)] +mod tests { + use super::{ComSignature, ContextFlag, Secp256k1, SecretKey}; + use crate::error::Result; -impl Readable for Commitment { - fn read(reader: &mut R) -> Result { - let a = reader.read_fixed_bytes(PEDERSEN_COMMITMENT_SIZE)?; - let mut c = [0; PEDERSEN_COMMITMENT_SIZE]; - c[..PEDERSEN_COMMITMENT_SIZE].clone_from_slice(&a[..PEDERSEN_COMMITMENT_SIZE]); - Ok(Commitment(c)) - } -} + use secp256k1zkp::rand::{RngCore, thread_rng}; + use rand::Rng; + + /// Test signing and verification of ComSignatures + #[test] + fn verify_comsig() -> Result<()> { + let secp = Secp256k1::with_caps(ContextFlag::Commit); -impl Writeable for Commitment { - fn write(&self, writer: &mut W) -> Result<()> { - writer.write_fixed_bytes(self) - } -} + let amount = thread_rng().next_u64(); + let blind = SecretKey::new(&secp, &mut thread_rng()); + let msg: [u8; 16] = rand::thread_rng().gen(); + let comsig = ComSignature::sign(amount, &blind, &msg.to_vec())?; + + let commit = secp.commit(amount, blind.clone())?; + assert!(comsig.verify(&commit, &msg.to_vec()).is_ok()); -impl Readable for RangeProof { - fn read(reader: &mut R) -> Result { - let len = reader.read_u64()?; - let max_len = cmp::min(len as usize, MAX_PROOF_SIZE); - let p = reader.read_fixed_bytes(max_len)?; - let mut proof = [0; MAX_PROOF_SIZE]; - proof[..p.len()].clone_from_slice(&p[..]); - Ok(RangeProof { - plen: proof.len(), - proof, - }) - } -} + let wrong_msg: [u8; 16] = rand::thread_rng().gen(); + assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err()); -impl Writeable for RangeProof { - fn write(&self, writer: &mut W) -> Result<()> { - writer.write_bytes(self) - } -} + let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?; + assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err()); -impl Readable for Signature { - fn read(reader: &mut R) -> Result { - let a = reader.read_fixed_bytes(AGG_SIGNATURE_SIZE)?; - let mut c = [0; AGG_SIGNATURE_SIZE]; - c[..AGG_SIGNATURE_SIZE].clone_from_slice(&a[..AGG_SIGNATURE_SIZE]); - Ok(Signature::from_raw_data(&c).unwrap()) - } -} - -impl Writeable for Signature { - fn write(&self, writer: &mut W) -> Result<()> { - writer.write_fixed_bytes(self) - } -} - -impl Readable for PublicKey { - // Read the public key in compressed form - fn read(reader: &mut R) -> Result { - let buf = reader.read_fixed_bytes(COMPRESSED_PUBLIC_KEY_SIZE)?; - let secp = Secp256k1::with_caps(ContextFlag::None); - let pk = PublicKey::from_slice(&secp, &buf).map_err(|_| ErrorKind::CorruptedData)?; - Ok(pk) - } -} - -impl Writeable for PublicKey { - // Write the public key in compressed form - fn write(&self, writer: &mut W) -> Result<()> { - let secp = Secp256k1::with_caps(ContextFlag::None); - writer.write_fixed_bytes(self.serialize_vec(&secp, true))?; Ok(()) } -} - -impl Readable for SecretKey { - fn read(reader: &mut R) -> Result { - let buf = reader.read_fixed_bytes(SECRET_KEY_SIZE)?; - let secp = Secp256k1::with_caps(ContextFlag::None); - let pk = SecretKey::from_slice(&secp, &buf).map_err(|_| ErrorKind::CorruptedData)?; - Ok(pk) - } -} - -impl Writeable for SecretKey { - fn write(&self, writer: &mut W) -> Result<()> { - writer.write_fixed_bytes(self.0)?; - Ok(()) - } -} - -#[allow(non_snake_case)] -impl Readable for ComSignature { - fn read(reader: &mut R) -> Result { - let R = Commitment::read(reader)?; - let s = SecretKey::read(reader)?; - let t = SecretKey::read(reader)?; - Ok(ComSignature::new(&R, &s, &t)) - } -} - -impl Writeable for ComSignature { - fn write(&self, writer: &mut W) -> Result<()> { - writer.write_fixed_bytes(self.pub_nonce.0)?; - writer.write_fixed_bytes(self.s.0)?; - writer.write_fixed_bytes(self.t.0) - } } \ No newline at end of file diff --git a/src/ser.rs b/src/ser.rs deleted file mode 100644 index a86ef2e..0000000 --- a/src/ser.rs +++ /dev/null @@ -1,624 +0,0 @@ -// Copyright 2021 The Grin Developers -// -// 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. - -//! Serialization and deserialization layer specialized for binary encoding. -//! Ensures consistency and safety. Basically a minimal subset or -//! rustc_serialize customized for our need. -//! -//! To use it simply implement `Writeable` or `Readable` and then use the -//! `serialize` or `deserialize` functions on them as appropriate. - -use crate::error::{Error, ErrorKind, Result}; -use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; -use bytes::Buf; -use std::io::{self, Read, Write}; -use std::marker; - -/// Implementations defined how different numbers and binary structures are -/// written to an underlying stream or container (depending on implementation). -pub trait Writer { - /// Writes a u8 as bytes - fn write_u8(&mut self, n: u8) -> Result<()> { - self.write_fixed_bytes(&[n]) - } - - /// Writes a u16 as bytes - fn write_u16(&mut self, n: u16) -> Result<()> { - let mut bytes = [0; 2]; - BigEndian::write_u16(&mut bytes, n); - self.write_fixed_bytes(&bytes) - } - - /// Writes a u32 as bytes - fn write_u32(&mut self, n: u32) -> Result<()> { - let mut bytes = [0; 4]; - BigEndian::write_u32(&mut bytes, n); - self.write_fixed_bytes(&bytes) - } - - /// Writes a u32 as bytes - fn write_i32(&mut self, n: i32) -> Result<()> { - let mut bytes = [0; 4]; - BigEndian::write_i32(&mut bytes, n); - self.write_fixed_bytes(&bytes) - } - - /// Writes a u64 as bytes - fn write_u64(&mut self, n: u64) -> Result<()> { - let mut bytes = [0; 8]; - BigEndian::write_u64(&mut bytes, n); - self.write_fixed_bytes(&bytes) - } - - /// Writes a i64 as bytes - fn write_i64(&mut self, n: i64) -> Result<()> { - let mut bytes = [0; 8]; - BigEndian::write_i64(&mut bytes, n); - self.write_fixed_bytes(&bytes) - } - - /// Writes a variable number of bytes. The length is encoded as a 64-bit - /// prefix. - fn write_bytes>(&mut self, bytes: T) -> Result<()> { - self.write_u64(bytes.as_ref().len() as u64)?; - self.write_fixed_bytes(bytes) - } - - /// Writes a fixed number of bytes. The reader is expected to know the actual length on read. - fn write_fixed_bytes>(&mut self, bytes: T) -> Result<()>; - - /// Writes a fixed length of "empty" bytes. - fn write_empty_bytes(&mut self, length: usize) -> Result<()> { - self.write_fixed_bytes(vec![0u8; length]) - } -} - -/// Implementations defined how different numbers and binary structures are -/// read from an underlying stream or container (depending on implementation). -pub trait Reader { - /// Read a u8 from the underlying Read - fn read_u8(&mut self) -> Result; - /// Read a u16 from the underlying Read - fn read_u16(&mut self) -> Result; - /// Read a u32 from the underlying Read - fn read_u32(&mut self) -> Result; - /// Read a u64 from the underlying Read - fn read_u64(&mut self) -> Result; - /// Read a i32 from the underlying Read - fn read_i32(&mut self) -> Result; - /// Read a i64 from the underlying Read - fn read_i64(&mut self) -> Result; - /// Read a u64 len prefix followed by that number of exact bytes. - fn read_bytes_len_prefix(&mut self) -> Result>; - /// Read a fixed number of bytes from the underlying reader. - fn read_fixed_bytes(&mut self, length: usize) -> Result>; - /// Consumes a byte from the reader, producing an error if it doesn't have - /// the expected value - fn expect_u8(&mut self, val: u8) -> Result; - - /// Read a fixed number of "empty" bytes from the underlying reader. - /// It is an error if any non-empty bytes encountered. - fn read_empty_bytes(&mut self, length: usize) -> Result<()> { - for _ in 0..length { - if self.read_u8()? != 0u8 { - return Err(ErrorKind::CorruptedData.into()); - } - } - Ok(()) - } -} - -/// Trait that every type that can be serialized as binary must implement. -/// Writes directly to a Writer, a utility type thinly wrapping an -/// underlying Write implementation. -pub trait Writeable { - /// Write the data held by this Writeable to the provided writer - fn write(&self, writer: &mut W) -> Result<()>; -} - -/// Reader that exposes an Iterator interface. -pub struct IteratingReader<'a, T, R: Reader> { - count: u64, - curr: u64, - reader: &'a mut R, - _marker: marker::PhantomData, -} - -impl<'a, T, R: Reader> IteratingReader<'a, T, R> { - /// Constructor to create a new iterating reader for the provided underlying reader. - /// Takes a count so we know how many to iterate over. - pub fn new(reader: &'a mut R, count: u64) -> Self { - let curr = 0; - IteratingReader { - count, - curr, - reader, - _marker: marker::PhantomData, - } - } -} - -impl<'a, T, R> Iterator for IteratingReader<'a, T, R> -where - T: Readable, - R: Reader, -{ - type Item = T; - - fn next(&mut self) -> Option { - if self.curr >= self.count { - return None; - } - self.curr += 1; - T::read(self.reader).ok() - } -} - -/// Reads multiple serialized items into a Vec. -pub fn read_multi(reader: &mut R, count: u64) -> Result> -where - T: Readable, - R: Reader, -{ - // Very rudimentary check to ensure we do not overflow anything - // attempting to read huge amounts of data. - // Probably better than checking if count * size overflows a u64 though. - if count > 1_000_000 { - return Err(ErrorKind::TooLargeReadErr.into()); - } - - let res: Vec = IteratingReader::new(reader, count).collect(); - if res.len() as u64 != count { - return Err(ErrorKind::CountError.into()); - } - Ok(res) -} - -/// Trait that every type that can be deserialized from binary must implement. -/// Reads directly to a Reader, a utility type thinly wrapping an -/// underlying Read implementation. -pub trait Readable -where - Self: Sized, -{ - /// Reads the data necessary to this Readable from the provided reader - fn read(reader: &mut R) -> Result; -} - -/// Deserializes a Readable from any std::io::Read implementation. -pub fn deserialize(source: &mut R) -> Result { - let mut reader = BinReader::new(source); - T::read(&mut reader) -} - -/// Serializes a Writeable into any std::io::Write implementation. -pub fn serialize(sink: &mut dyn Write, thing: &W) -> Result<()> { - let mut writer = BinWriter::new(sink); - thing.write(&mut writer) -} - -/// Utility function to serialize a writeable directly in memory using a -/// Vec. -pub fn ser_vec(thing: &W) -> Result> { - let mut vec = vec![]; - serialize(&mut vec, thing)?; - Ok(vec) -} - -/// Utility to read from a binary source -pub struct BinReader<'a, R: Read> { - source: &'a mut R, -} - -impl<'a, R: Read> BinReader<'a, R> { - /// Constructor for a new BinReader for the provided source. - pub fn new(source: &'a mut R) -> Self { - BinReader { source } - } -} - -fn map_io_err(err: io::Error) -> Error { - ErrorKind::IOErr(format!("{}", err), err.kind()).into() -} - -/// Utility wrapper for an underlying byte Reader. Defines higher level methods -/// to read numbers, byte vectors, hashes, etc. -impl<'a, R: Read> Reader for BinReader<'a, R> { - fn read_u8(&mut self) -> Result { - self.source.read_u8().map_err(map_io_err) - } - fn read_u16(&mut self) -> Result { - self.source.read_u16::().map_err(map_io_err) - } - fn read_u32(&mut self) -> Result { - self.source.read_u32::().map_err(map_io_err) - } - fn read_i32(&mut self) -> Result { - self.source.read_i32::().map_err(map_io_err) - } - fn read_u64(&mut self) -> Result { - self.source.read_u64::().map_err(map_io_err) - } - fn read_i64(&mut self) -> Result { - self.source.read_i64::().map_err(map_io_err) - } - /// Read a variable size vector from the underlying Read. Expects a usize - fn read_bytes_len_prefix(&mut self) -> Result> { - let len = self.read_u64()?; - self.read_fixed_bytes(len as usize) - } - - /// Read a fixed number of bytes. - fn read_fixed_bytes(&mut self, len: usize) -> Result> { - // not reading more than 100k bytes in a single read - if len > 100_000 { - return Err(ErrorKind::TooLargeReadErr.into()); - } - let mut buf = vec![0; len]; - self.source - .read_exact(&mut buf) - .map(move |_| buf) - .map_err(map_io_err) - } - - fn expect_u8(&mut self, val: u8) -> Result { - let b = self.read_u8()?; - if b == val { - Ok(b) - } else { - Err(ErrorKind::UnexpectedData { - expected: vec![val], - received: vec![b], - }.into()) - } - } -} - -/// A reader that reads straight off a stream. -/// Tracks total bytes read so we can verify we read the right number afterwards. -pub struct StreamingReader<'a> { - total_bytes_read: u64, - stream: &'a mut dyn Read, -} - -impl<'a> StreamingReader<'a> { - /// Create a new streaming reader with the provided underlying stream. - /// Also takes a duration to be used for each individual read_exact call. - pub fn new(stream: &'a mut dyn Read) -> StreamingReader<'a> { - StreamingReader { - total_bytes_read: 0, - stream, - } - } - - /// Returns the total bytes read via this streaming reader. - pub fn total_bytes_read(&self) -> u64 { - self.total_bytes_read - } -} - -/// Note: We use read_fixed_bytes() here to ensure our "async" I/O behaves as expected. -impl<'a> Reader for StreamingReader<'a> { - fn read_u8(&mut self) -> Result { - let buf = self.read_fixed_bytes(1)?; - Ok(buf[0]) - } - fn read_u16(&mut self) -> Result { - let buf = self.read_fixed_bytes(2)?; - Ok(BigEndian::read_u16(&buf[..])) - } - fn read_u32(&mut self) -> Result { - let buf = self.read_fixed_bytes(4)?; - Ok(BigEndian::read_u32(&buf[..])) - } - fn read_i32(&mut self) -> Result { - let buf = self.read_fixed_bytes(4)?; - Ok(BigEndian::read_i32(&buf[..])) - } - fn read_u64(&mut self) -> Result { - let buf = self.read_fixed_bytes(8)?; - Ok(BigEndian::read_u64(&buf[..])) - } - fn read_i64(&mut self) -> Result { - let buf = self.read_fixed_bytes(8)?; - Ok(BigEndian::read_i64(&buf[..])) - } - - /// Read a variable size vector from the underlying stream. Expects a usize - fn read_bytes_len_prefix(&mut self) -> Result> { - let len = self.read_u64()?; - self.total_bytes_read += 8; - self.read_fixed_bytes(len as usize) - } - - /// Read a fixed number of bytes. - fn read_fixed_bytes(&mut self, len: usize) -> Result> { - let mut buf = vec![0u8; len]; - self.stream.read_exact(&mut buf)?; - self.total_bytes_read += len as u64; - Ok(buf) - } - - fn expect_u8(&mut self, val: u8) -> Result { - let b = self.read_u8()?; - if b == val { - Ok(b) - } else { - Err(ErrorKind::UnexpectedData { - expected: vec![val], - received: vec![b], - }.into()) - } - } -} - -/// Protocol version-aware wrapper around a `Buf` impl -pub struct BufReader<'a, B: Buf> { - inner: &'a mut B, - bytes_read: usize, -} - -impl<'a, B: Buf> BufReader<'a, B> { - /// Construct a new BufReader - pub fn new(buf: &'a mut B) -> Self { - Self { - inner: buf, - bytes_read: 0, - } - } - - /// Check whether the buffer has enough bytes remaining to perform a read - fn has_remaining(&mut self, len: usize) -> Result<()> { - if self.inner.remaining() >= len { - self.bytes_read += len; - Ok(()) - } else { - Err(io::ErrorKind::UnexpectedEof.into()) - } - } - - /// The total bytes read - pub fn bytes_read(&self) -> u64 { - self.bytes_read as u64 - } - - /// Convenience function to read from the buffer and deserialize - pub fn body(&mut self) -> Result { - T::read(self) - } -} - -impl<'a, B: Buf> Reader for BufReader<'a, B> { - fn read_u8(&mut self) -> Result { - self.has_remaining(1)?; - Ok(self.inner.get_u8()) - } - - fn read_u16(&mut self) -> Result { - self.has_remaining(2)?; - Ok(self.inner.get_u16()) - } - - fn read_u32(&mut self) -> Result { - self.has_remaining(4)?; - Ok(self.inner.get_u32()) - } - - fn read_u64(&mut self) -> Result { - self.has_remaining(8)?; - Ok(self.inner.get_u64()) - } - - fn read_i32(&mut self) -> Result { - self.has_remaining(4)?; - Ok(self.inner.get_i32()) - } - - fn read_i64(&mut self) -> Result { - self.has_remaining(8)?; - Ok(self.inner.get_i64()) - } - - fn read_bytes_len_prefix(&mut self) -> Result> { - let len = self.read_u64()?; - self.read_fixed_bytes(len as usize) - } - - fn read_fixed_bytes(&mut self, len: usize) -> Result> { - // not reading more than 100k bytes in a single read - if len > 100_000 { - return Err(ErrorKind::TooLargeReadErr.into()); - } - self.has_remaining(len)?; - - let mut buf = vec![0; len]; - self.inner.copy_to_slice(&mut buf[..]); - Ok(buf) - } - - fn expect_u8(&mut self, val: u8) -> Result { - let b = self.read_u8()?; - if b == val { - Ok(b) - } else { - Err(ErrorKind::UnexpectedData { - expected: vec![val], - received: vec![b], - }.into()) - } - } -} - -/// Utility wrapper for an underlying byte Writer. Defines higher level methods -/// to write numbers, byte vectors, hashes, etc. -pub struct BinWriter<'a> { - sink: &'a mut dyn Write, -} - -impl<'a> BinWriter<'a> { - /// Wraps a standard Write in a new BinWriter - pub fn new(sink: &'a mut dyn Write) -> BinWriter<'a> { - BinWriter { sink } - } -} - -impl<'a> Writer for BinWriter<'a> { - fn write_fixed_bytes>(&mut self, bytes: T) -> Result<()> { - self.sink.write_all(bytes.as_ref())?; - Ok(()) - } -} - -macro_rules! impl_int { - ($int:ty, $w_fn:ident, $r_fn:ident) => { - impl Writeable for $int { - fn write(&self, writer: &mut W) -> Result<()> { - writer.$w_fn(*self) - } - } - - impl Readable for $int { - fn read(reader: &mut R) -> Result<$int> { - reader.$r_fn() - } - } - }; -} - -impl_int!(u8, write_u8, read_u8); -impl_int!(u16, write_u16, read_u16); -impl_int!(u32, write_u32, read_u32); -impl_int!(i32, write_i32, read_i32); -impl_int!(u64, write_u64, read_u64); -impl_int!(i64, write_i64, read_i64); - -impl Readable for Vec -where - T: Readable, -{ - fn read(reader: &mut R) -> Result> { - let mut buf = Vec::new(); - loop { - let elem = T::read(reader); - match elem { - Ok(e) => buf.push(e), - // Err(ErrorKind::IOErr(ref _d, ref kind)) if *kind == io::ErrorKind::UnexpectedEof => { - // break; - // } - Err(e) => { - match e.kind() { - ErrorKind::IOErr(ref _d, ref kind) if *kind == io::ErrorKind::UnexpectedEof => { - break; - }, - _ => return Err(e), - } - }, - } - } - Ok(buf) - } -} - -impl Writeable for Vec -where - T: Writeable, -{ - fn write(&self, writer: &mut W) -> Result<()> { - for elmt in self { - elmt.write(writer)?; - } - Ok(()) - } -} - -impl<'a, A: Writeable> Writeable for &'a A { - fn write(&self, writer: &mut W) -> Result<()> { - Writeable::write(*self, writer) - } -} - -impl Writeable for (A, B) { - fn write(&self, writer: &mut W) -> Result<()> { - Writeable::write(&self.0, writer)?; - Writeable::write(&self.1, writer) - } -} - -impl Readable for (A, B) { - fn read(reader: &mut R) -> Result<(A, B)> { - Ok((Readable::read(reader)?, Readable::read(reader)?)) - } -} - -impl Writeable for (A, B, C) { - fn write(&self, writer: &mut W) -> Result<()> { - Writeable::write(&self.0, writer)?; - Writeable::write(&self.1, writer)?; - Writeable::write(&self.2, writer) - } -} - -impl Writeable for (A, B, C, D) { - fn write(&self, writer: &mut W) -> Result<()> { - Writeable::write(&self.0, writer)?; - Writeable::write(&self.1, writer)?; - Writeable::write(&self.2, writer)?; - Writeable::write(&self.3, writer) - } -} - -impl Readable for (A, B, C) { - fn read(reader: &mut R) -> Result<(A, B, C)> { - Ok(( - Readable::read(reader)?, - Readable::read(reader)?, - Readable::read(reader)?, - )) - } -} - -impl Readable for (A, B, C, D) { - fn read(reader: &mut R) -> Result<(A, B, C, D)> { - Ok(( - Readable::read(reader)?, - Readable::read(reader)?, - Readable::read(reader)?, - Readable::read(reader)?, - )) - } -} - -/// Serializes a Vec to and from hex -pub mod vec_serde { - use serde::{Deserialize, Serializer}; - use grin_util::ToHex; - - /// Serializes a Vec as a hex string - pub fn serialize(bytes: &Vec, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&bytes.to_hex()) - } - - /// Creates a Vec from a hex string - pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result, D::Error> - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - String::deserialize(deserializer) - .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom)) - } -} \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index ccb0b29..3290818 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,13 +1,12 @@ use crate::config::ServerConfig; -use crate::node::GrinNode; -use crate::onion; -use crate::secp::{self, ComSignature, SecretKey}; -use crate::ser; -use crate::types::Onion; -use crate::wallet::Wallet; +use crate::node::{self, GrinNode}; +use crate::onion::Onion; +use crate::secp::{self, ComSignature, Secp256k1, SecretKey}; +use crate::wallet::{self, Wallet}; use grin_core::core::{Input, Output, OutputFeatures, TransactionBody}; use grin_core::global::DEFAULT_ACCEPT_FEE_BASE; +use grin_util::StopState; use jsonrpc_derive::rpc; use jsonrpc_http_server::*; use jsonrpc_http_server::jsonrpc_core::*; @@ -27,8 +26,6 @@ pub struct Submission { #[derive(Serialize, Deserialize)] pub struct SwapReq { pub onion: Onion, - #[serde(with = "ser::vec_serde")] - pub msg: Vec, #[serde(with = "secp::comsig_serde")] pub comsig: ComSignature, } @@ -50,15 +47,14 @@ pub trait Server { #[derive(Clone)] pub struct ServerImpl { server_config: ServerConfig, - - wallet: Wallet, - - node: GrinNode, + stop_state: Arc, + wallet: Arc, + node: Arc, } impl ServerImpl { - pub fn new(server_config: ServerConfig, wallet: Wallet, node: GrinNode) -> Self { - ServerImpl { server_config, wallet, node } + pub fn new(server_config: ServerConfig, stop_state: Arc, wallet: Arc, node: Arc) -> Self { + ServerImpl { server_config, stop_state, wallet, node } } /// The fee base to use. For now, just using the default. @@ -68,7 +64,7 @@ impl ServerImpl { /// Minimum fee to perform a swap. /// Requires enough fee for the mwixnet server's kernel, 1 input and its output to swap. - pub fn get_minimum_swap_fee(&self) -> u64 { + fn get_minimum_swap_fee(&self) -> u64 { TransactionBody::weight_by_iok(1, 1, 1) * self.get_fee_base() } @@ -77,15 +73,19 @@ impl ServerImpl { /// /// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes. pub fn execute_round(&self) -> crate::error::Result<()> { - let locked_state = SERVER_STATE.lock().unwrap(); + let mut locked_state = SERVER_STATE.lock().unwrap(); let next_block_height = self.node.get_chain_height()? + 1; let spendable : Vec = locked_state .iter() - .filter(|s| self.node.is_spendable(&s.input.commit, next_block_height).unwrap_or(false)) + .filter(|s| node::is_spendable(&self.node, &s.input.commit, next_block_height).unwrap_or(false)) .cloned() .collect(); + if spendable.len() == 0 { + return Ok(()) + } + let total_fee : u64 = spendable .iter() .enumerate() @@ -110,11 +110,10 @@ impl ServerImpl { .map(|(_, s)| s.excess.clone()) .collect(); - let excess_sum = secp::add_blinds(&excesses)?; - - let tx = self.wallet.assemble_tx(&inputs, &outputs, total_fee, &excess_sum)?; + let tx = wallet::assemble_tx(&self.wallet, &inputs, &outputs, self.get_fee_base(), total_fee, &excesses)?; self.node.post_tx(&tx)?; + locked_state.clear(); Ok(()) } @@ -124,26 +123,39 @@ impl Server for ServerImpl { /// Implements the 'swap' API fn swap(&self, swap: SwapReq) -> Result { // Verify commitment signature to ensure caller owns the output - let _ = swap.comsig.verify(&swap.onion.commit, &swap.msg) + let serialized_onion = swap.onion.serialize() + .map_err(|_| jsonrpc_core::Error::internal_error())?; + let _ = swap.comsig.verify(&swap.onion.commit, &serialized_onion) .map_err(|_| jsonrpc_core::Error::invalid_params("ComSignature invalid"))?; // Verify that commitment is unspent - let input = self.node.build_input(&swap.onion.commit) + let input = node::build_input(&self.node, &swap.onion.commit) .map_err(|_| jsonrpc_core::Error::internal_error())?; let input = input.ok_or(jsonrpc_core::Error::invalid_params("Commitment not found"))?; - let peeled = onion::peel_layer(&swap.onion, &self.server_config.key) + let peeled = swap.onion.peel_layer(&self.server_config.key) .map_err(|e| jsonrpc_core::Error::invalid_params(e.message()))?; + // Verify the fee meets the minimum let fee: u64 = peeled.0.fee.into(); if fee < self.get_minimum_swap_fee() { return Err(jsonrpc_core::Error::invalid_params("Fee does not meet minimum")); } + // Calculate final output commitment let output_commit = secp::add_excess(&swap.onion.commit, &peeled.0.excess) .map_err(|_| jsonrpc_core::Error::internal_error())?; + let output_commit = secp::sub_value(&output_commit, fee) + .map_err(|_| jsonrpc_core::Error::internal_error())?; + + // Verify the bullet proof and build the final output let output = match peeled.0.rangeproof { - Some(r) => Some(Output::new(OutputFeatures::Plain, output_commit, r)), + Some(r) => { + let secp = Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit); + secp.verify_bullet_proof(output_commit, r, None) + .map_err(|_| jsonrpc_core::Error::invalid_params("RangeProof invalid"))?; + Some(Output::new(OutputFeatures::Plain, output_commit, r)) + }, None => None }; SERVER_STATE.lock().unwrap().push(Submission{ @@ -158,11 +170,9 @@ impl Server for ServerImpl { } /// Spin up the JSON-RPC web server -pub fn listen(server_config: &ServerConfig, wallet: &Wallet, shutdown_signal: F) -> std::result::Result<(), Box> -where - F: futures::future::Future + Send + 'static, +pub fn listen(server_config: &ServerConfig, wallet: Arc, node: Arc, stop_state: &Arc) -> std::result::Result<(), Box> { - let server_impl = Arc::new(ServerImpl::new(server_config.clone(), wallet.clone(), GrinNode::new(&server_config))); + let server_impl = Arc::new(ServerImpl::new(server_config.clone(), stop_state.clone(), wallet.clone(), node.clone())); let mut io = IoHandler::new(); io.extend_with(ServerImpl::to_delegate(server_impl.as_ref().clone())); @@ -179,28 +189,47 @@ where .start_http(&server_config.addr) .expect("Unable to start RPC server"); - std::thread::spawn(move || { + let close_handle = server.close_handle(); + + let config = server_config.clone(); + let round_handle = std::thread::spawn(move || { + let mut secs = 0; loop { - std::thread::sleep(std::time::Duration::from_secs(30)); // todo: Graceful shutdown - let _ = server_impl.as_ref().execute_round(); + if server_impl.as_ref().stop_state.is_stopped() { + close_handle.close(); + break; + } + + std::thread::sleep(std::time::Duration::from_secs(1)); + secs = (secs + 1) % config.interval_s; + + if secs == 0 { + let _ = server_impl.as_ref().execute_round(); + secs = 0; + } } }); - let close_handle = server.close_handle(); - std::thread::spawn(move || { - futures::executor::block_on(shutdown_signal); - close_handle.close(); - }); server.wait(); + round_handle.join().unwrap(); Ok(()) } #[cfg(test)] mod tests { - use crate::{config, onion, secp, server, types, wallet}; - use grin_core::core::FeeFields; + use crate::config::ServerConfig; + use crate::node::{GrinNode, MockGrinNode}; + use crate::onion::test_util::{self, Hop}; + use crate::secp::{self, ComSignature, PublicKey, Secp256k1}; + use crate::server::{self, SwapReq}; + use crate::types::Payload; + use crate::wallet::{MockWallet, Wallet}; + + use grin_core::core::{Committed, FeeFields, Transaction}; + use grin_util::StopState; use std::net::TcpListener; + use std::sync::Arc; use std::time::Duration; use std::thread; @@ -213,9 +242,15 @@ mod tests { } /// Spin up a temporary web service, query the API, then cleanup and return response - fn make_request(server_key: secp::SecretKey, req: String) -> Result> { - let server_config = config::ServerConfig { + fn make_request( + server_key: secp::SecretKey, + wallet: Arc, + node: Arc, + req: String + ) -> Result> { + let server_config = ServerConfig { key: server_key, + interval_s: 1, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, grin_node_url: "127.0.0.1:3413".parse()?, wallet_owner_url: "127.0.0.1:3420".parse()? @@ -225,11 +260,17 @@ mod tests { let (shutdown_sender, shutdown_receiver) = futures::channel::oneshot::channel(); let uri = format!("http://{}/v1", server_config.addr); - let wallet = wallet::Wallet::open_wallet(&server_config.wallet_owner_url, &grin_util::ZeroingString::from("wallet_pass"))?; + let stop_state = Arc::new(StopState::new()); + let stop_state_clone = stop_state.clone(); // Spawn the server task threaded_rt.spawn(async move { - server::listen(&server_config, &wallet, async { shutdown_receiver.await.ok(); }).unwrap() + server::listen(&server_config, wallet, node, &stop_state).unwrap() + }); + + threaded_rt.spawn(async move { + futures::executor::block_on(shutdown_receiver).unwrap(); + stop_state_clone.stop(); }); // Wait for listener @@ -246,6 +287,9 @@ mod tests { let response = threaded_rt.block_on(do_request)?; let response_str: String = threaded_rt.block_on(body_to_string(response)); + + // Wait for at least one round to execute + thread::sleep(Duration::from_millis(1500)); shutdown_sender.send(()).ok(); @@ -257,52 +301,110 @@ mod tests { } /// Single hop to demonstrate request validation and onion unwrapping. - /// UTXO creation and bulletproof generation reserved for milestones 2 & 3. #[test] fn swap_lifecycle() -> Result<(), Box> { + let secp = Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit); let server_key = secp::random_secret(); - let secp = secp::Secp256k1::new(); - let value: u64 = 100; - let fee: u64= 10; + let value: u64 = 200_000_000; + let fee: u64= 50_000_000; let blind = secp::random_secret(); let commitment = secp::commit(value, &blind)?; - let session_key = secp::random_secret(); + let hop_excess = secp::random_secret(); + let nonce = secp::random_secret(); + + let mut final_blind = blind.clone(); + final_blind.add_assign(&secp, &hop_excess).unwrap(); + let proof = secp.bullet_proof(value - fee, final_blind.clone(), nonce.clone(), nonce.clone(), None, None); - let hop = types::Hop { - pubkey: secp::PublicKey::from_secret_key(&secp, &server_key)?, - payload: types::Payload{ + let hop = Hop { + pubkey: PublicKey::from_secret_key(&secp, &server_key)?, + payload: Payload{ + excess: hop_excess, + fee: FeeFields::from(fee as u32), + rangeproof: Some(proof), + } + }; + let hops: Vec = vec![hop]; + let session_key = secp::random_secret(); + let onion_packet = test_util::create_onion(&commitment, &session_key, &hops)?; + let comsig = ComSignature::sign(value, &blind, &onion_packet.serialize()?)?; + let swap = SwapReq{ + onion: onion_packet, + comsig: comsig, + }; + + let wallet = MockWallet{}; + let mut mut_node = MockGrinNode::new(); + mut_node.add_default_utxo(&commitment); + let node = Arc::new(mut_node); + + let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", serde_json::json!(swap)); + println!("Request: {}", req); + let response = make_request(server_key, Arc::new(wallet), node.clone(), req)?; + let expected = "{\"jsonrpc\":\"2.0\",\"result\":\"success\",\"id\":\"1\"}\n"; + assert_eq!(response, expected); + + // check that the transaction was posted + let posted_txns = node.get_posted_txns(); + assert_eq!(posted_txns.len(), 1); + let posted_txn : Transaction = posted_txns.into_iter().next().unwrap(); + let input_commit = posted_txn.inputs_committed().into_iter().next().unwrap(); + assert_eq!(input_commit, commitment); + + Ok(()) + } + + /// Returns "Commitment not found" when there's no matching output in the UTXO set. + #[test] + fn swap_utxo_missing() -> Result<(), Box> { + let secp = Secp256k1::new(); + let server_key = secp::random_secret(); + + let value: u64 = 200_000_000; + let fee: u64= 50_000_000; + let blind = secp::random_secret(); + let commitment = secp::commit(value, &blind)?; + + let hop = Hop { + pubkey: PublicKey::from_secret_key(&secp, &server_key)?, + payload: Payload{ excess: secp::random_secret(), fee: FeeFields::from(fee as u32), rangeproof: None, } }; - let hops: Vec = vec![hop]; - let onion_packet = onion::create_onion(&commitment, &session_key, &hops)?; - let msg : Vec = vec![0u8, 1u8, 2u8, 3u8]; - let comsig = secp::ComSignature::sign(value, &blind, &msg)?; - let swap = server::SwapReq{ + let hops: Vec = vec![hop]; + let session_key = secp::random_secret(); + let onion_packet = test_util::create_onion(&commitment, &session_key, &hops)?; + let comsig = ComSignature::sign(value, &blind, &onion_packet.serialize()?)?; + let swap = SwapReq{ onion: onion_packet, - msg: msg, comsig: comsig, }; + let wallet = MockWallet{}; + let node = MockGrinNode::new(); + let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", serde_json::json!(swap)); - let response = make_request(server_key, req)?; - let expected = "{\"jsonrpc\":\"2.0\",\"result\":\"success\",\"id\":\"1\"}\n"; + let response = make_request(server_key, Arc::new(wallet), Arc::new(node), req)?; + let expected = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Commitment not found\"},\"id\":\"1\"}\n"; assert_eq!(response, expected); Ok(()) } + // TODO: Test bulletproof verification and test minimum fee + #[test] fn swap_bad_request() -> Result<(), Box> { + let wallet = MockWallet{}; + let node = MockGrinNode::new(); + let params = "{ \"param\": \"Not a valid Swap request\" }"; let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", params); - let response = make_request(secp::random_secret(), req)?; + let response = make_request(secp::random_secret(), Arc::new(wallet), Arc::new(node), req)?; let expected = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `onion`.\"},\"id\":\"1\"}\n"; assert_eq!(response, expected); Ok(()) } - - // milestone 2 - add tests to cover invalid comsig's & inputs not in utxo set } \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 086f91c..abb7782 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,15 +1,9 @@ -use crate::error::{ErrorKind, Result}; -use crate::secp::{Commitment, PublicKey, RangeProof, SecretKey, Secp256k1}; -use crate::ser::{self, BinReader, Readable, Reader, Writeable, Writer}; +use crate::error::{Result, StdResult}; +use crate::secp::{self, RangeProof, SecretKey}; use grin_core::core::FeeFields; -use grin_util::{self, ToHex}; +use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; use serde::{Deserialize, Serialize}; -use serde::ser::SerializeStruct; -use std::fmt; -use std::io::Cursor; - -pub type RawBytes = Vec; const CURRENT_VERSION : u8 = 0; @@ -20,15 +14,30 @@ pub struct Payload { pub rangeproof: Option, } +impl Payload { + pub fn deserialize(bytes: &Vec) -> Result { + let payload: Payload = grin_core::ser::deserialize_default(&mut &bytes[..])?; + Ok(payload) + } + + #[cfg(test)] + pub fn serialize(&self) -> Result> { + let mut vec = vec![]; + ser::serialize_default(&mut vec, &self)?; + Ok(vec) + } +} + impl Readable for Payload { - fn read(reader: &mut R) -> Result { + fn read(reader: &mut R) -> StdResult { let version = reader.read_u8()?; if version != CURRENT_VERSION { - return Err(ErrorKind::UnsupportedPayload.into()); + return Err(ser::Error::UnsupportedProtocolVersion); } - let excess = SecretKey::read(reader)?; - let fee = FeeFields::try_from(reader.read_u64()?)?; + let excess = secp::read_secret_key(reader)?; + let fee = FeeFields::try_from(reader.read_u64()?) + .map_err(|_| ser::Error::CorruptedData)?; let rangeproof = if reader.read_u8()? == 0 { None } else { @@ -45,7 +54,7 @@ impl Readable for Payload { } impl Writeable for Payload { - fn write(&self, writer: &mut W) -> Result<()> { + fn write(&self, writer: &mut W) -> StdResult<(), ser::Error> { writer.write_u8(CURRENT_VERSION)?; writer.write_fixed_bytes(&self.excess)?; writer.write_u64(self.fee.into())?; @@ -60,114 +69,4 @@ impl Writeable for Payload { Ok(()) } -} - -pub fn serialize_payload(payload: &Payload) -> Result> { - ser::ser_vec(&payload) -} - -pub fn deserialize_payload(bytes: &Vec) -> Result { - let mut cursor = Cursor::new(&bytes); - let mut reader = BinReader::new(&mut cursor); - Payload::read(&mut reader) -} - -pub struct Hop { - pub pubkey: PublicKey, - pub payload: Payload, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Onion { - pub ephemeral_pubkey: PublicKey, - pub commit: Commitment, - pub enc_payloads: Vec, -} - -impl serde::ser::Serialize for Onion { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - let mut state = serializer.serialize_struct("Onion", 3)?; - - let secp = Secp256k1::new(); - state.serialize_field("pubkey", &self.ephemeral_pubkey.serialize_vec(&secp, true).to_hex())?; - state.serialize_field("commit", &self.commit.to_hex())?; - - let hex_payloads: Vec = self.enc_payloads.iter().map(|v| v.to_hex()).collect(); - state.serialize_field("data", &hex_payloads)?; - state.end() - } -} - -impl<'de> serde::de::Deserialize<'de> for Onion { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Pubkey, - Commit, - Data - } - - struct OnionVisitor; - - impl<'de> serde::de::Visitor<'de> for OnionVisitor { - type Value = Onion; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an Onion") - } - - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - let mut pubkey = None; - let mut commit = None; - let mut data = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Pubkey => { - let val: String = map.next_value()?; - let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; - let secp = Secp256k1::new(); - pubkey = Some(PublicKey::from_slice(&secp, &vec[..]).map_err(serde::de::Error::custom)?); - } - Field::Commit => { - let val: String = map.next_value()?; - let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?; - commit = Some(Commitment::from_vec(vec)); - } - Field::Data => { - let val: Vec = map.next_value()?; - let mut vec: Vec> = Vec::new(); - for hex in val { - vec.push(grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?); - } - data = Some(vec); - } - } - } - - Ok(Onion { - ephemeral_pubkey: pubkey.unwrap(), - commit: commit.unwrap(), - enc_payloads: data.unwrap(), - }) - } - } - - const FIELDS: &[&str] = &[ - "pubkey", - "commit", - "data" - ]; - deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor) - } } \ No newline at end of file diff --git a/src/wallet.rs b/src/wallet.rs index e54e60d..e48429f 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,36 +1,102 @@ -use crate::error::Result; +use crate::error::{ErrorKind, Result}; use crate::secp::{self, SecretKey}; use grin_api::client; use grin_api::json_rpc::{build_request, Request, Response}; -use grin_core::core::{FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TxKernel}; +use grin_core::core::{FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TransactionBody, TxKernel}; +use grin_core::libtx::secp_ser; use grin_keychain::BlindingFactor; use grin_util::ZeroingString; use grin_wallet_api::Token; +use secp256k1zkp::{ContextFlag, Secp256k1}; +use serde::{Deserialize, Serialize}; use serde_json::json; use std::net::SocketAddr; +use std::sync::Arc; + +pub trait Wallet : Send + Sync { + /// Builds an output for the wallet with the provided amount. + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)>; +} + +/// Builds and verifies a 'Transaction' using the provided components. +pub fn assemble_tx(wallet: &Arc, inputs: &Vec, outputs: &Vec, fee_base: u64, total_fee: u64, excesses: &Vec) -> Result { + let secp = Secp256k1::with_caps(ContextFlag::Commit); + let txn_inputs = Inputs::from(inputs.as_slice()); + let mut txn_outputs = outputs.clone(); + let mut txn_excesses = excesses.clone(); + let mut kernel_fee = total_fee; + + // calculate fee required if we add our own output + let fee_required = TransactionBody::weight_by_iok(inputs.len() as u64, (outputs.len() + 1) as u64, 1) * fee_base; + + // calculate fee to spend the output to ensure there's enough leftover to cover the fees for spending it + let fee_to_spend = TransactionBody::weight_by_iok(1, 0, 0) * fee_base; + + // collect any leftover fees + if total_fee > fee_required + fee_to_spend { + let amount = total_fee - fee_required; + kernel_fee -= amount; + + let wallet_output = wallet.build_output(amount)?; + txn_outputs.push(wallet_output.1); + + let output_excess = wallet_output.0.secret_key(&secp) + .map_err(|_| ErrorKind::CorruptedData)?; + txn_excesses.push(output_excess); + } + + // generate random transaction offset + let offset = secp::random_secret(); + + // calculate kernel excess + let kern_excess = secp.blind_sum(txn_excesses, vec![offset.clone()])?; + + // build and verify kernel + let mut kernel = TxKernel::with_features(KernelFeatures::Plain { + fee: FeeFields::new(0, kernel_fee).unwrap(), + }); + let msg = kernel.msg_to_sign()?; + kernel.excess = secp::commit(0, &kern_excess)?; + kernel.excess_sig = secp::sign(&kern_excess, &msg)?; + kernel.verify()?; + + // assemble the transaction + let tx = Transaction::new(txn_inputs, &txn_outputs, &[kernel]) + .with_offset(BlindingFactor::from_secret_key(offset)); + Ok(tx) +} + +/// HTTP (JSONRPC) implementation of the 'Wallet' trait. +#[derive(Clone)] +pub struct HttpWallet { + wallet_owner_url: SocketAddr, + token: Token, +} const ENDPOINT: &str = "/v3/owner"; -#[derive(Clone)] -pub struct HTTPWalletClient { - wallet_owner_url: SocketAddr, -} +impl HttpWallet { + /// Calls the 'open_wallet' using the RPC API. + pub fn open_wallet(wallet_owner_url: &SocketAddr, wallet_pass: &ZeroingString) -> Result { + let open_wallet_params = json!({ + "name": null, + "password": wallet_pass.to_string() + }); + let token: Token = HttpWallet::send_json_request(&wallet_owner_url, "open_wallet", &open_wallet_params)?; -impl HTTPWalletClient { - /// Create a new client that will communicate with the given grin node - pub fn new(wallet_owner_url: &SocketAddr) -> HTTPWalletClient { - HTTPWalletClient { - wallet_owner_url: wallet_owner_url.to_owned(), - } + Ok(HttpWallet { + wallet_owner_url: wallet_owner_url.clone(), + token: token, + }) } fn send_json_request( - &self, + wallet_owner_url: &SocketAddr, method: &str, params: &serde_json::Value, ) -> Result { - let url = format!("http://{}{}", self.wallet_owner_url, ENDPOINT); + let url = format!("http://{}{}", wallet_owner_url, ENDPOINT); let req = build_request(method, params); let res = client::post::(url.as_str(), None, &req)?; let parsed = res.clone().into_result()?; @@ -38,54 +104,51 @@ impl HTTPWalletClient { } } -#[derive(Clone)] -pub struct Wallet { - pub client: HTTPWalletClient, - - pub token: Token, +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OutputWithBlind { + #[serde( + serialize_with = "secp_ser::as_hex", + deserialize_with = "secp_ser::blind_from_hex" + )] + blind: BlindingFactor, + output: Output, } -impl Wallet { - pub fn open_wallet(wallet_owner_url: &SocketAddr, wallet_pass: &ZeroingString) -> Result { - let client = HTTPWalletClient::new(&wallet_owner_url); - - let open_wallet_params = json!({ - "name": null, - "password": wallet_pass.to_string() +impl Wallet for HttpWallet { + /// Builds an 'Output' for the wallet using the 'build_output' RPC API. + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)> { + let req_json = json!({ + "token": self.token.keychain_mask.clone().unwrap().0, + "features": "Plain", + "amount": amount }); - let token: Token = client.send_json_request("open_wallet", &open_wallet_params)?; - - Ok(Wallet { - client: client, - token: token, - }) + let output: OutputWithBlind = HttpWallet::send_json_request(&self.wallet_owner_url, "build_output", &req_json)?; + Ok((output.blind, output.output)) } +} - /// Builds and verifies a Transaction using the provided components. - pub fn assemble_tx(&self, inputs: &Vec, outputs: &Vec, total_fee: u64, total_excess: &SecretKey) -> Result { - // generate random transaction offset - let offset = secp::random_secret(); +use grin_core::core::OutputFeatures; - // build and verify kernel - let kern_excess = secp::sub_blinds(&total_excess, &offset)?; - let kern = Wallet::build_kernel(total_fee, &kern_excess)?; +/// HTTP (JSONRPC) implementation of the 'Wallet' trait. +#[derive(Clone)] +pub struct MockWallet { +} - // assemble the transaction - let tx = Transaction::new(Inputs::from(inputs.as_slice()), &outputs, &[kern]) - .with_offset(BlindingFactor::from_secret_key(offset)); - Ok(tx) - } - - /// Builds and verifies a TxKernel from the provided fee and excess. - fn build_kernel(total_fee: u64, kern_excess: &SecretKey) -> Result { - let mut kernel = TxKernel::with_features(KernelFeatures::Plain { - fee: FeeFields::new(0, total_fee).unwrap(), - }); - let msg = kernel.msg_to_sign()?; - kernel.excess = secp::commit(0, &kern_excess)?; - kernel.excess_sig = secp::sign(&kern_excess, &msg)?; - kernel.verify()?; - - Ok(kernel) +impl Wallet for MockWallet { + /// Builds an 'Output' for the wallet using the 'build_output' RPC API. + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)> { + let secp = Secp256k1::new(); + let blind = secp::random_secret(); + let commit = secp::commit(amount, &blind)?; + let proof = secp.bullet_proof( + amount, + blind.clone(), + secp::random_secret(), + secp::random_secret(), + None, + None, + ); + let output = Output::new(OutputFeatures::Plain, commit.clone(), proof); + Ok((BlindingFactor::from_secret_key(blind), output)) } } \ No newline at end of file