From bff59b08ac14ba4ea3617bc2add71ae9e3cccd96 Mon Sep 17 00:00:00 2001 From: scilio Date: Wed, 27 Apr 2022 16:30:15 -0400 Subject: [PATCH] filter duplicate commitments --- Cargo.lock | 10 ++++++++ Cargo.toml | 1 + src/node.rs | 6 +++++ src/server.rs | 69 ++++++++++++++++++++++++++++++++------------------- 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01ac7ed..457a17d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2089,6 +2089,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -2480,6 +2489,7 @@ dependencies = [ "grin_wallet_libwallet", "hmac 0.12.0", "hyper 0.14.14", + "itertools", "jsonrpc-core 18.0.0", "jsonrpc-derive", "jsonrpc-http-server", diff --git a/Cargo.toml b/Cargo.toml index 761c92c..79bcd6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ failure = "0.1.8" futures = "0.3" hmac = { version = "0.12.0", features = ["std"]} hyper = { version = "0.14", features = ["full"] } +itertools = { version = "0.10.3"} jsonrpc-core = "18.0" jsonrpc-derive = "18.0" jsonrpc-http-server = "18.0" diff --git a/src/node.rs b/src/node.rs index fa02cc4..d00ede1 100644 --- a/src/node.rs +++ b/src/node.rs @@ -24,6 +24,12 @@ pub trait GrinNode : Send + Sync { fn post_tx(&self, tx: &Transaction) -> Result<()>; } +/// Checks if a commitment is in the UTXO set +pub fn is_unspent(node: &Arc, commit: &Commitment) -> Result { + let utxo = node.get_utxo(&commit)?; + Ok(utxo.is_some()) +} + /// 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)?; diff --git a/src/server.rs b/src/server.rs index 3290818..7794f53 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,37 +1,40 @@ use crate::config::ServerConfig; use crate::node::{self, GrinNode}; use crate::onion::Onion; -use crate::secp::{self, ComSignature, Secp256k1, SecretKey}; +use crate::secp::{self, Commitment, ComSignature, RangeProof, 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 itertools::Itertools; use jsonrpc_derive::rpc; use jsonrpc_http_server::*; use jsonrpc_http_server::jsonrpc_core::*; use jsonrpc_core::{Result, Value}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::sync::{Arc, Mutex}; #[derive(Clone, Debug, PartialEq)] -pub struct Submission { - pub excess: SecretKey, - pub output: Option, - pub input: Input, - pub fee: u64, - pub onion: Onion, +struct Submission { + excess: SecretKey, + output_commit: Commitment, + rangeproof: Option, + input: Input, + fee: u64, + onion: Onion, } #[derive(Serialize, Deserialize)] pub struct SwapReq { - pub onion: Onion, + onion: Onion, #[serde(with = "secp::comsig_serde")] - pub comsig: ComSignature, + comsig: ComSignature, } lazy_static! { - static ref SERVER_STATE: Mutex> = Mutex::new(Vec::new()); + static ref SERVER_STATE: Mutex> = Mutex::new(HashMap::new()); } #[rpc(server)] @@ -45,7 +48,7 @@ pub trait Server { } #[derive(Clone)] -pub struct ServerImpl { +struct ServerImpl { server_config: ServerConfig, stop_state: Arc, wallet: Arc, @@ -53,7 +56,7 @@ pub struct ServerImpl { } impl ServerImpl { - pub fn new(server_config: ServerConfig, stop_state: Arc, wallet: Arc, node: Arc) -> Self { + fn new(server_config: ServerConfig, stop_state: Arc, wallet: Arc, node: Arc) -> Self { ServerImpl { server_config, stop_state, wallet, node } } @@ -72,13 +75,16 @@ impl ServerImpl { /// and assemble the coinswap transaction, posting the transaction to the configured node. /// /// 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<()> { + fn execute_round(&self) -> crate::error::Result<()> { let mut locked_state = SERVER_STATE.lock().unwrap(); let next_block_height = self.node.get_chain_height()? + 1; let spendable : Vec = locked_state - .iter() + .values() + .into_iter() + .unique_by(|s| s.output_commit) .filter(|s| node::is_spendable(&self.node, &s.input.commit, next_block_height).unwrap_or(false)) + .filter(|s| !node::is_unspent(&self.node, &s.output_commit).unwrap_or(true)) .cloned() .collect(); @@ -101,7 +107,7 @@ impl ServerImpl { let outputs : Vec = spendable .iter() .enumerate() - .filter_map(|(_, s)| s.output) + .map(|(_, s)| Output::new(OutputFeatures::Plain, s.output_commit, s.rangeproof.unwrap())) .collect(); let excesses : Vec = spendable @@ -122,6 +128,11 @@ impl ServerImpl { impl Server for ServerImpl { /// Implements the 'swap' API fn swap(&self, swap: SwapReq) -> Result { + // milestone 3: check that enc_payloads length matches number of configured servers + if swap.onion.enc_payloads.len() != 1 { + return Err(jsonrpc_core::Error::invalid_params("Multi server not supported until milestone 3")); + } + // Verify commitment signature to ensure caller owns the output let serialized_onion = swap.onion.serialize() .map_err(|_| jsonrpc_core::Error::internal_error())?; @@ -149,18 +160,24 @@ impl Server for ServerImpl { .map_err(|_| jsonrpc_core::Error::internal_error())?; // Verify the bullet proof and build the final output - let output = match peeled.0.rangeproof { - 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{ + if let Some(r) = peeled.0.rangeproof { + 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"))?; + } else { + // milestone 3: only the last hop will have a rangeproof + return Err(jsonrpc_core::Error::invalid_params("Rangeproof expected")); + } + + let mut locked = SERVER_STATE.lock().unwrap(); + if locked.contains_key(&swap.onion.commit) { + return Err(jsonrpc_core::Error::invalid_params("swap already called for coin")); + } + + locked.insert(swap.onion.commit, Submission{ excess: peeled.0.excess, - output: output, + output_commit: output_commit, + rangeproof: peeled.0.rangeproof, input: input, fee: fee, onion: peeled.1