inter-server communication

This commit is contained in:
scilio
2023-01-07 16:09:04 -05:00
parent 9ec200f548
commit 76532c899e
23 changed files with 2756 additions and 1169 deletions
+171
View File
@@ -0,0 +1,171 @@
use crate::config::ServerConfig;
use crate::crypto::dalek;
use crate::onion::Onion;
use crate::servers::mix_rpc::MixReq;
use crate::tx::TxComponents;
use crate::{tor, DalekPublicKey};
use grin_api::client;
use grin_api::json_rpc::build_request;
use grin_core::ser;
use grin_core::ser::ProtocolVersion;
use grin_wallet_util::OnionV3Address;
use hyper::client::HttpConnector;
use hyper_proxy::{Intercept, Proxy, ProxyConnector};
use serde_json;
use thiserror::Error;
/// Error types for interacting with nodes
#[derive(Error, Debug)]
pub enum ClientError {
#[error("Tor Error: {0:?}")]
Tor(tor::TorError),
#[error("API Error: {0:?}")]
API(grin_api::Error),
#[error("Dalek Error: {0:?}")]
Dalek(dalek::DalekError),
#[error("Error parsing response: {0:?}")]
ResponseParse(serde_json::Error),
#[error("Custom client error: {0:?}")]
Custom(String),
}
/// A client for consuming a mix API
pub trait MixClient: Send + Sync {
/// Swaps the outputs provided and returns the final swapped outputs and kernels.
fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<(Vec<usize>, TxComponents), ClientError>;
}
pub struct MixClientImpl {
config: ServerConfig,
addr: OnionV3Address,
}
impl MixClientImpl {
pub fn new(config: ServerConfig, next_pubkey: DalekPublicKey) -> Self {
let addr = OnionV3Address::from_bytes(next_pubkey.as_ref().to_bytes());
MixClientImpl { config, addr }
}
fn send_json_request<D: serde::de::DeserializeOwned>(
&self,
addr: &OnionV3Address,
method: &str,
params: &serde_json::Value,
) -> Result<D, ClientError> {
let _tor = tor::init_tor_sender(&self.config).map_err(ClientError::Tor)?;
let proxy = {
let proxy_uri = format!("http://{:?}", self.config.socks_proxy_addr)
.parse()
.unwrap();
let proxy = Proxy::new(Intercept::All, proxy_uri);
//proxy.set_authorization(Authorization::basic("John Doe", "Agent1234"));
let connector = HttpConnector::new();
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
proxy_connector
};
let url = format!("{}/v1", addr.to_http_str());
let mut req = client::create_post_request(&url, None, &build_request(method, params))
.map_err(ClientError::API)?;
let uri = url.parse().unwrap();
if let Some(headers) = proxy.http_headers(&uri) {
req.headers_mut().extend(headers.clone().into_iter());
}
let res = client::send_request(req).map_err(ClientError::API)?;
serde_json::from_str(&res).map_err(ClientError::ResponseParse)
}
}
impl MixClient for MixClientImpl {
fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<(Vec<usize>, TxComponents), ClientError> {
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
let sig =
dalek::sign(&self.config.key, serialized.as_slice()).map_err(ClientError::Dalek)?;
let mix = MixReq::new(onions.clone(), sig);
let params = serde_json::json!(mix);
self.send_json_request::<(Vec<usize>, TxComponents)>(&self.addr, "mix", &params)
}
}
#[cfg(test)]
pub mod mock {
use super::{ClientError, MixClient};
use crate::onion::Onion;
use crate::tx::TxComponents;
use std::collections::HashMap;
pub struct MockMixClient {
results: HashMap<Vec<Onion>, (Vec<usize>, TxComponents)>,
}
impl MockMixClient {
pub fn new() -> MockMixClient {
MockMixClient {
results: HashMap::new(),
}
}
pub fn set_response(&mut self, onions: &Vec<Onion>, r: (Vec<usize>, TxComponents)) {
self.results.insert(onions.clone(), r);
}
}
impl MixClient for MockMixClient {
fn mix_outputs(
&self,
onions: &Vec<Onion>,
) -> Result<(Vec<usize>, TxComponents), ClientError> {
self.results
.get(onions)
.map(|r| Ok(r.clone()))
.unwrap_or(Err(ClientError::Custom("No response set for input".into())))
}
}
}
#[cfg(test)]
pub mod test_util {
use super::{ClientError, MixClient};
use crate::crypto::dalek;
use crate::crypto::secp::SecretKey;
use crate::onion::Onion;
use crate::servers::mix::MixServer;
use crate::tx::TxComponents;
use crate::DalekPublicKey;
use grin_core::ser;
use grin_core::ser::ProtocolVersion;
use std::sync::Arc;
/// Implementation of the 'MixClient' trait that calls a mix server implementation directly.
/// No JSON-RPC serialization or socket communication occurs.
#[derive(Clone)]
pub struct DirectMixClient {
pub key: SecretKey,
pub mix_server: Arc<dyn MixServer>,
}
impl MixClient for DirectMixClient {
fn mix_outputs(
&self,
onions: &Vec<Onion>,
) -> Result<(Vec<usize>, TxComponents), ClientError> {
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
let sig = dalek::sign(&self.key, serialized.as_slice()).map_err(ClientError::Dalek)?;
sig.verify(
&DalekPublicKey::from_secret(&self.key),
serialized.as_slice(),
)
.unwrap();
Ok(self.mix_server.mix_outputs(&onions, &sig).unwrap())
}
}
}
+60 -6
View File
@@ -1,8 +1,10 @@
use crate::secp::SecretKey;
use crate::crypto::dalek::DalekPublicKey;
use crate::crypto::secp::SecretKey;
use core::num::NonZeroU32;
use grin_core::global::ChainTypes;
use grin_util::{file, ToHex, ZeroingString};
use grin_wallet_util::OnionV3Address;
use rand::{thread_rng, Rng};
use ring::{aead, pbkdf2};
use serde_derive::{Deserialize, Serialize};
@@ -26,6 +28,8 @@ pub struct ServerConfig {
pub interval_s: u32,
/// socket address the server listener should bind to
pub addr: SocketAddr,
/// socket address the tor sender should bind to
pub socks_proxy_addr: SocketAddr,
/// foreign api address of the grin node
pub grin_node_url: SocketAddr,
/// path to file containing api secret for the grin node
@@ -34,9 +38,23 @@ pub struct ServerConfig {
pub wallet_owner_url: SocketAddr,
/// path to file containing secret for the grin wallet's owner api
pub wallet_owner_secret_path: Option<String>,
/// public key of the previous mix/swap server (e.g. N_1 if this is N_2)
#[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
pub prev_server: Option<DalekPublicKey>,
/// public key of the next mix server
#[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
pub next_server: Option<DalekPublicKey>,
}
impl ServerConfig {
pub fn onion_address(&self) -> OnionV3Address {
OnionV3Address::from_private(&self.key.0).unwrap()
}
pub fn server_pubkey(&self) -> DalekPublicKey {
DalekPublicKey::from_secret(&self.key)
}
pub fn node_api_secret(&self) -> Option<String> {
file::get_first_line(self.grin_node_secret_path.clone())
}
@@ -163,10 +181,15 @@ struct RawConfig {
nonce: String,
interval_s: u32,
addr: SocketAddr,
socks_proxy_addr: SocketAddr,
grin_node_url: SocketAddr,
grin_node_secret_path: Option<String>,
wallet_owner_url: SocketAddr,
wallet_owner_secret_path: Option<String>,
#[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
prev_server: Option<DalekPublicKey>,
#[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
next_server: Option<DalekPublicKey>,
}
/// Writes the server config to the config_path given, encrypting the server_key first.
@@ -183,10 +206,13 @@ pub fn write_config(
nonce: encrypted.nonce,
interval_s: server_config.interval_s,
addr: server_config.addr,
socks_proxy_addr: server_config.socks_proxy_addr,
grin_node_url: server_config.grin_node_url,
grin_node_secret_path: server_config.grin_node_secret_path.clone(),
wallet_owner_url: server_config.wallet_owner_url,
wallet_owner_secret_path: server_config.wallet_owner_secret_path.clone(),
prev_server: server_config.prev_server.clone(),
next_server: server_config.next_server.clone(),
};
let encoded: String =
toml::to_string(&raw_config).map_err(|e| ConfigError::EncodingError(e))?;
@@ -203,10 +229,8 @@ pub fn load_config(
config_path: &PathBuf,
password: &ZeroingString,
) -> Result<ServerConfig, ConfigError> {
let contents =
std::fs::read_to_string(config_path).map_err(|e| ConfigError::ReadConfigError(e))?;
let raw_config: RawConfig =
toml::from_str(&contents).map_err(|e| ConfigError::DecodingError(e))?;
let contents = std::fs::read_to_string(config_path).map_err(ConfigError::ReadConfigError)?;
let raw_config: RawConfig = toml::from_str(&contents).map_err(ConfigError::DecodingError)?;
let encrypted_key = EncryptedServerKey {
encrypted_key: raw_config.encrypted_key,
@@ -219,10 +243,13 @@ pub fn load_config(
key: secret_key,
interval_s: raw_config.interval_s,
addr: raw_config.addr,
socks_proxy_addr: raw_config.socks_proxy_addr,
grin_node_url: raw_config.grin_node_url,
grin_node_secret_path: raw_config.grin_node_secret_path,
wallet_owner_url: raw_config.wallet_owner_url,
wallet_owner_secret_path: raw_config.wallet_owner_secret_path,
prev_server: raw_config.prev_server,
next_server: raw_config.next_server,
})
}
@@ -260,10 +287,37 @@ pub fn wallet_owner_url(_chain_type: &ChainTypes) -> SocketAddr {
"127.0.0.1:3420".parse().unwrap()
}
#[cfg(test)]
pub mod test_util {
use crate::{DalekPublicKey, ServerConfig};
use secp256k1zkp::SecretKey;
use std::net::TcpListener;
pub fn local_config(
server_key: &SecretKey,
prev_server: &Option<DalekPublicKey>,
next_server: &Option<DalekPublicKey>,
) -> Result<ServerConfig, Box<dyn std::error::Error>> {
let config = ServerConfig {
key: server_key.clone(),
interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?,
grin_node_secret_path: None,
wallet_owner_url: "127.0.0.1:3420".parse()?,
wallet_owner_secret_path: None,
prev_server: prev_server.clone(),
next_server: next_server.clone(),
};
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::secp;
use crate::crypto::secp;
#[test]
fn server_key_encrypt() {
+189 -275
View File
@@ -1,275 +1,189 @@
pub use secp256k1zkp::aggsig;
pub use secp256k1zkp::constants::{
AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE,
SECRET_KEY_SIZE,
};
pub use secp256k1zkp::ecdh::SharedSecret;
pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY};
pub use secp256k1zkp::pedersen::{Commitment, RangeProof};
pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature};
use blake2::blake2b::Blake2b;
use byteorder::{BigEndian, ByteOrder};
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use secp256k1zkp::rand::thread_rng;
use thiserror::Error;
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys
#[derive(Clone)]
pub struct ComSignature {
pub_nonce: Commitment,
s: SecretKey,
t: SecretKey,
}
/// Error types for Commitment Signatures
#[derive(Error, Debug)]
pub enum ComSigError {
#[error("Commitment signature is invalid")]
InvalidSig,
#[error("Secp256k1zkp error: {0:?}")]
Secp256k1zkp(secp256k1zkp::Error),
}
impl From<secp256k1zkp::Error> for ComSigError {
fn from(err: secp256k1zkp::Error) -> ComSigError {
ComSigError::Secp256k1zkp(err)
}
}
impl ComSignature {
pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature {
ComSignature {
pub_nonce: pub_nonce.to_owned(),
s: s.to_owned(),
t: t.to_owned(),
}
}
#[allow(dead_code)]
pub fn sign(
amount: u64,
blind: &SecretKey,
msg: &Vec<u8>,
) -> Result<ComSignature, ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let mut amt_bytes = [0; 32];
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());
let k_2 = SecretKey::new(&secp, &mut thread_rng());
let commitment = secp.commit(amount, blind.clone())?;
let nonce_commitment = secp.commit_blind(k_1.clone(), k_2.clone())?;
let e = ComSignature::calc_challenge(&secp, &commitment, &nonce_commitment, &msg)?;
// s = k_1 + (e * amount)
let mut s = k_amt.clone();
s.mul_assign(&secp, &e)?;
s.add_assign(&secp, &k_1)?;
// t = k_2 + (e * blind)
let mut t = blind.clone();
t.mul_assign(&secp, &e)?;
t.add_assign(&secp, &k_2)?;
Ok(ComSignature::new(&nonce_commitment, &s, &t))
}
#[allow(non_snake_case)]
pub fn verify(&self, commit: &Commitment, msg: &Vec<u8>) -> Result<(), ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let S1 = secp.commit_blind(self.s.clone(), self.t.clone())?;
let mut Ce = commit.to_pubkey(&secp)?;
let e = ComSignature::calc_challenge(&secp, &commit, &self.pub_nonce, &msg)?;
Ce.mul_assign(&secp, &e)?;
let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()];
let S2 = secp.commit_sum(commits, Vec::new())?;
if S1 != S2 {
return Err(ComSigError::InvalidSig);
}
Ok(())
}
fn calc_challenge(
secp: &Secp256k1,
commit: &Commitment,
nonce_commit: &Commitment,
msg: &Vec<u8>,
) -> Result<SecretKey, ComSigError> {
let mut challenge_hasher = Blake2b::new(32);
challenge_hasher.update(&commit.0);
challenge_hasher.update(&nonce_commit.0);
challenge_hasher.update(msg);
let mut challenge = [0; 32];
challenge.copy_from_slice(challenge_hasher.finalize().as_bytes());
Ok(SecretKey::from_slice(&secp, &challenge)?)
}
}
/// Serializes a ComSignature to and from hex
pub mod comsig_serde {
use super::ComSignature;
use grin_core::ser::{self, ProtocolVersion};
use grin_util::ToHex;
use serde::{Deserialize, Serializer};
/// Serializes a ComSignature as a hex string
pub fn serialize<S>(comsig: &ComSignature, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::Error;
let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?;
serializer.serialize_str(&bytes.to_hex())
}
/// Creates a ComSignature from a hex string
pub fn deserialize<'de, D>(deserializer: D) -> Result<ComSignature, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let bytes = String::deserialize(deserializer)
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?;
let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?;
Ok(sig)
}
}
#[allow(non_snake_case)]
impl Readable for ComSignature {
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
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<W: Writer>(&self, writer: &mut W) -> 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.
pub fn random_secret() -> SecretKey {
let secp = Secp256k1::new();
SecretKey::new(&secp, &mut thread_rng())
}
/// Deserialize a SecretKey from a Reader
pub fn read_secret_key<R: Reader>(reader: &mut R) -> Result<SecretKey, ser::Error> {
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<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let commit = secp.commit(value, blind.clone())?;
Ok(commit)
}
/// Add a blinding factor to an existing Commitment
pub fn add_excess(
commitment: &Commitment,
excess: &SecretKey,
) -> Result<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let excess_commit: Commitment = secp.commit(0, excess.clone())?;
let commits = vec![commitment.clone(), excess_commit.clone()];
let sum = secp.commit_sum(commits, Vec::new())?;
Ok(sum)
}
/// Subtracts a value (v*H) from an existing commitment
pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let neg_commit: Commitment = secp.commit(value, ZERO_KEY)?;
let sum = secp.commit_sum(vec![commitment.clone()], vec![neg_commit.clone()])?;
Ok(sum)
}
/// Signs the message with the provided SecretKey
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Full);
let pubkey = PublicKey::from_secret_key(&secp, &sk)?;
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?;
Ok(sig)
}
#[cfg(test)]
pub mod test_util {
use crate::secp::{self, Commitment, RangeProof, Secp256k1};
use grin_core::core::hash::Hash;
use grin_util::ToHex;
use rand::RngCore;
pub fn rand_commit() -> Commitment {
secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap()
}
pub fn rand_hash() -> Hash {
Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap()
}
pub fn rand_proof() -> RangeProof {
let secp = Secp256k1::new();
secp.bullet_proof(
rand::thread_rng().next_u64(),
secp::random_secret(),
secp::random_secret(),
secp::random_secret(),
None,
None,
)
}
}
#[cfg(test)]
mod tests {
use super::{ComSigError, ComSignature, ContextFlag, Secp256k1, SecretKey};
use rand::Rng;
use secp256k1zkp::rand::{thread_rng, RngCore};
/// Test signing and verification of ComSignatures
#[test]
fn verify_comsig() -> Result<(), ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
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());
let wrong_msg: [u8; 16] = rand::thread_rng().gen();
assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err());
let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?;
assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err());
Ok(())
}
}
use crate::crypto::secp::{self, Commitment, ContextFlag, Secp256k1, SecretKey};
use blake2::blake2b::Blake2b;
use byteorder::{BigEndian, ByteOrder};
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use secp256k1zkp::rand::thread_rng;
use thiserror::Error;
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys
#[derive(Clone)]
pub struct ComSignature {
pub_nonce: Commitment,
s: SecretKey,
t: SecretKey,
}
/// Error types for Commitment Signatures
#[derive(Error, Debug)]
pub enum ComSigError {
#[error("Commitment signature is invalid")]
InvalidSig,
#[error("Secp256k1zkp error: {0:?}")]
Secp256k1zkp(secp256k1zkp::Error),
}
impl From<secp256k1zkp::Error> for ComSigError {
fn from(err: secp256k1zkp::Error) -> ComSigError {
ComSigError::Secp256k1zkp(err)
}
}
impl ComSignature {
pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature {
ComSignature {
pub_nonce: pub_nonce.to_owned(),
s: s.to_owned(),
t: t.to_owned(),
}
}
#[allow(dead_code)]
pub fn sign(
amount: u64,
blind: &SecretKey,
msg: &Vec<u8>,
) -> Result<ComSignature, ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let mut amt_bytes = [0; 32];
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());
let k_2 = SecretKey::new(&secp, &mut thread_rng());
let commitment = secp.commit(amount, blind.clone())?;
let nonce_commitment = secp.commit_blind(k_1.clone(), k_2.clone())?;
let e = ComSignature::calc_challenge(&secp, &commitment, &nonce_commitment, &msg)?;
// s = k_1 + (e * amount)
let mut s = k_amt.clone();
s.mul_assign(&secp, &e)?;
s.add_assign(&secp, &k_1)?;
// t = k_2 + (e * blind)
let mut t = blind.clone();
t.mul_assign(&secp, &e)?;
t.add_assign(&secp, &k_2)?;
Ok(ComSignature::new(&nonce_commitment, &s, &t))
}
#[allow(non_snake_case)]
pub fn verify(&self, commit: &Commitment, msg: &Vec<u8>) -> Result<(), ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let S1 = secp.commit_blind(self.s.clone(), self.t.clone())?;
let mut Ce = commit.to_pubkey(&secp)?;
let e = ComSignature::calc_challenge(&secp, &commit, &self.pub_nonce, &msg)?;
Ce.mul_assign(&secp, &e)?;
let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()];
let S2 = secp.commit_sum(commits, Vec::new())?;
if S1 != S2 {
return Err(ComSigError::InvalidSig);
}
Ok(())
}
fn calc_challenge(
secp: &Secp256k1,
commit: &Commitment,
nonce_commit: &Commitment,
msg: &Vec<u8>,
) -> Result<SecretKey, ComSigError> {
let mut challenge_hasher = Blake2b::new(32);
challenge_hasher.update(&commit.0);
challenge_hasher.update(&nonce_commit.0);
challenge_hasher.update(msg);
let mut challenge = [0; 32];
challenge.copy_from_slice(challenge_hasher.finalize().as_bytes());
Ok(SecretKey::from_slice(&secp, &challenge)?)
}
}
/// Serializes a ComSignature to and from hex
pub mod comsig_serde {
use super::ComSignature;
use grin_core::ser::{self, ProtocolVersion};
use grin_util::ToHex;
use serde::{Deserialize, Serializer};
/// Serializes a ComSignature as a hex string
pub fn serialize<S>(comsig: &ComSignature, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::Error;
let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?;
serializer.serialize_str(&bytes.to_hex())
}
/// Creates a ComSignature from a hex string
pub fn deserialize<'de, D>(deserializer: D) -> Result<ComSignature, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let bytes = String::deserialize(deserializer)
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?;
let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?;
Ok(sig)
}
}
#[allow(non_snake_case)]
impl Readable for ComSignature {
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
let R = Commitment::read(reader)?;
let s = secp::read_secret_key(reader)?;
let t = secp::read_secret_key(reader)?;
Ok(ComSignature::new(&R, &s, &t))
}
}
impl Writeable for ComSignature {
fn write<W: Writer>(&self, writer: &mut W) -> 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(())
}
}
#[cfg(test)]
mod tests {
use super::{ComSigError, ComSignature, ContextFlag, Secp256k1, SecretKey};
use rand::Rng;
use secp256k1zkp::rand::{thread_rng, RngCore};
/// Test signing and verification of ComSignatures
#[test]
fn verify_comsig() -> Result<(), ComSigError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
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());
let wrong_msg: [u8; 16] = rand::thread_rng().gen();
assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err());
let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?;
assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err());
Ok(())
}
}
+285
View File
@@ -0,0 +1,285 @@
use crate::crypto::secp::SecretKey;
use ed25519_dalek::{Keypair, PublicKey, Signature, Signer, Verifier};
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use grin_util::ToHex;
use thiserror::Error;
/// Error types for Dalek structures and logic
#[derive(Clone, Error, Debug, PartialEq)]
pub enum DalekError {
#[error("Hex error {0:?}")]
HexError(String),
#[error("Failed to parse secret key")]
KeyParseError,
#[error("Failed to verify signature")]
SigVerifyFailed,
}
/// Encapsulates an ed25519_dalek::PublicKey and provides (de-)serialization
#[derive(Clone, Debug, PartialEq)]
pub struct DalekPublicKey(PublicKey);
impl DalekPublicKey {
/// Convert DalekPublicKey to hex string
pub fn to_hex(&self) -> String {
self.0.to_hex()
}
/// Convert hex string to DalekPublicKey.
pub fn from_hex(hex: &str) -> Result<Self, DalekError> {
let bytes = grin_util::from_hex(hex)
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
let pk = PublicKey::from_bytes(bytes.as_ref())
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
Ok(DalekPublicKey(pk))
}
/// Compute DalekPublicKey from a SecretKey
pub fn from_secret(key: &SecretKey) -> Self {
let secret = ed25519_dalek::SecretKey::from_bytes(&key.0).unwrap();
let pk: PublicKey = (&secret).into();
DalekPublicKey(pk)
}
}
impl AsRef<PublicKey> for DalekPublicKey {
fn as_ref(&self) -> &PublicKey {
&self.0
}
}
/// Serializes an Option<DalekPublicKey> to and from hex
pub mod option_dalek_pubkey_serde {
use super::DalekPublicKey;
use grin_util::ToHex;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
///
pub fn serialize<S>(pk: &Option<DalekPublicKey>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match pk {
Some(pk) => serializer.serialize_str(&pk.0.to_hex()),
None => serializer.serialize_none(),
}
}
///
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DalekPublicKey>, D::Error>
where
D: Deserializer<'de>,
{
Option::<String>::deserialize(deserializer).and_then(|res| match res {
Some(string) => DalekPublicKey::from_hex(&string)
.map_err(|e| Error::custom(e.to_string()))
.and_then(|pk: DalekPublicKey| Ok(Some(pk))),
None => Ok(None),
})
}
}
impl Readable for DalekPublicKey {
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
let pk = PublicKey::from_bytes(&reader.read_fixed_bytes(32)?)
.map_err(|_| ser::Error::CorruptedData)?;
Ok(DalekPublicKey(pk))
}
}
impl Writeable for DalekPublicKey {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_fixed_bytes(self.0.to_bytes())?;
Ok(())
}
}
/// Encapsulates an ed25519_dalek::Signature and provides (de-)serialization
#[derive(Clone, Debug, PartialEq)]
pub struct DalekSignature(Signature);
impl DalekSignature {
/// Convert hex string to DalekSignature.
pub fn from_hex(hex: &str) -> Result<Self, DalekError> {
let bytes = grin_util::from_hex(hex)
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
let sig = Signature::from_bytes(bytes.as_ref())
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
Ok(DalekSignature(sig))
}
/// Verifies DalekSignature
pub fn verify(&self, pk: &DalekPublicKey, msg: &[u8]) -> Result<(), DalekError> {
pk.as_ref()
.verify(&msg, &self.0)
.map_err(|_| DalekError::SigVerifyFailed)
}
}
impl AsRef<Signature> for DalekSignature {
fn as_ref(&self) -> &Signature {
&self.0
}
}
/// Serializes a DalekSignature to and from hex
pub mod dalek_sig_serde {
use super::DalekSignature;
use grin_util::ToHex;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
///
pub fn serialize<S>(sig: &DalekSignature, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&sig.0.to_hex())
}
///
pub fn deserialize<'de, D>(deserializer: D) -> Result<DalekSignature, D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
let sig = DalekSignature::from_hex(&str).map_err(|e| Error::custom(e.to_string()))?;
Ok(sig)
}
}
pub fn sign(sk: &SecretKey, message: &[u8]) -> Result<DalekSignature, DalekError> {
let secret =
ed25519_dalek::SecretKey::from_bytes(&sk.0).map_err(|_| DalekError::KeyParseError)?;
let public: PublicKey = (&secret).into();
let keypair = Keypair { secret, public };
let sig = keypair.sign(&message);
Ok(DalekSignature(sig))
}
#[cfg(test)]
pub mod test_util {
use super::*;
use crate::crypto::secp::random_secret;
pub fn rand_keypair() -> (SecretKey, DalekPublicKey) {
let sk = random_secret();
let pk = DalekPublicKey::from_secret(&sk);
(sk, pk)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::dalek::test_util;
use crate::crypto::dalek::test_util::rand_keypair;
use grin_core::ser::{self, ProtocolVersion};
use grin_util::ToHex;
use rand::Rng;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
struct TestPubKeySerde {
#[serde(with = "option_dalek_pubkey_serde", default)]
pk: Option<DalekPublicKey>,
}
#[test]
fn pubkey_test() -> Result<(), Box<dyn std::error::Error>> {
// Test from_hex
let rand_pk = test_util::rand_keypair().1;
let pk_from_hex = DalekPublicKey::from_hex(rand_pk.0.to_hex().as_str()).unwrap();
assert_eq!(rand_pk.0, pk_from_hex.0);
// Test ser (de-)serialization
let bytes = ser::ser_vec(&rand_pk, ProtocolVersion::local()).unwrap();
assert_eq!(bytes.len(), 32);
let pk_from_deser: DalekPublicKey = ser::deserialize_default(&mut &bytes[..]).unwrap();
assert_eq!(rand_pk.0, pk_from_deser.0);
// Test serde with Some(rand_pk)
let some = TestPubKeySerde {
pk: Some(rand_pk.clone()),
};
let val = serde_json::to_value(some.clone()).unwrap();
if let Value::Object(o) = &val {
if let Value::String(s) = o.get("pk").unwrap() {
assert_eq!(s, &rand_pk.0.to_hex());
} else {
panic!("Invalid type");
}
} else {
panic!("Invalid type")
}
assert_eq!(some, serde_json::from_value(val).unwrap());
// Test serde with empty pk field
let none = TestPubKeySerde { pk: None };
let val = serde_json::to_value(none.clone()).unwrap();
if let Value::Object(o) = &val {
if let Value::Null = o.get("pk").unwrap() {
// ok
} else {
panic!("Invalid type");
}
} else {
panic!("Invalid type")
}
assert_eq!(none, serde_json::from_value(val).unwrap());
// Test serde with no pk field
let none2 = serde_json::from_str::<TestPubKeySerde>("{}").unwrap();
assert_eq!(none, none2);
Ok(())
}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
struct TestSigSerde {
#[serde(with = "dalek_sig_serde")]
sig: DalekSignature,
}
#[test]
fn sig_test() -> Result<(), Box<dyn std::error::Error>> {
// Sign a message
let (sk, pk) = rand_keypair();
let msg: [u8; 16] = rand::thread_rng().gen();
let sig = sign(&sk, &msg).unwrap();
// Verify signature
assert!(sig.verify(&pk, &msg).is_ok());
// Wrong message
let wrong_msg: [u8; 16] = rand::thread_rng().gen();
assert!(sig.verify(&pk, &wrong_msg).is_err());
// Wrong pubkey
let wrong_pk = rand_keypair().1;
assert!(sig.verify(&wrong_pk, &msg).is_err());
// Test from_hex
let sig_from_hex = DalekSignature::from_hex(sig.0.to_hex().as_str()).unwrap();
assert_eq!(sig.0, sig_from_hex.0);
// Test serde (de-)serialization
let serde_test = TestSigSerde { sig: sig.clone() };
let val = serde_json::to_value(serde_test.clone()).unwrap();
if let Value::Object(o) = &val {
if let Value::String(s) = o.get("sig").unwrap() {
assert_eq!(s, &sig.0.to_hex());
} else {
panic!("Invalid type");
}
} else {
panic!("Invalid type")
}
assert_eq!(serde_test, serde_json::from_value(val).unwrap());
Ok(())
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod comsig;
pub mod dalek;
pub mod secp;
+117
View File
@@ -0,0 +1,117 @@
pub use secp256k1zkp::aggsig;
pub use secp256k1zkp::constants::{
AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE,
SECRET_KEY_SIZE,
};
pub use secp256k1zkp::ecdh::SharedSecret;
pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY};
pub use secp256k1zkp::pedersen::{Commitment, RangeProof};
pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature};
use grin_core::ser::{self, Reader};
use secp256k1zkp::rand::thread_rng;
/// Generate a random SecretKey.
pub fn random_secret() -> SecretKey {
let secp = Secp256k1::new();
SecretKey::new(&secp, &mut thread_rng())
}
/// Deserialize a SecretKey from a Reader
pub fn read_secret_key<R: Reader>(reader: &mut R) -> Result<SecretKey, ser::Error> {
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<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let commit = secp.commit(value, blind.clone())?;
Ok(commit)
}
/// Add a blinding factor to an existing Commitment
pub fn add_excess(
commitment: &Commitment,
excess: &SecretKey,
) -> Result<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let excess_commit: Commitment = secp.commit(0, excess.clone())?;
let commits = vec![commitment.clone(), excess_commit.clone()];
let sum = secp.commit_sum(commits, Vec::new())?;
Ok(sum)
}
/// Subtracts a value (v*H) from an existing commitment
pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let neg_commit: Commitment = secp.commit(value, ZERO_KEY)?;
let sum = secp.commit_sum(vec![commitment.clone()], vec![neg_commit.clone()])?;
Ok(sum)
}
/// Signs the message with the provided SecretKey
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature, secp256k1zkp::Error> {
let secp = Secp256k1::with_caps(ContextFlag::Full);
let pubkey = PublicKey::from_secret_key(&secp, &sk)?;
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?;
Ok(sig)
}
#[cfg(test)]
pub mod test_util {
use crate::crypto::secp::{self, Commitment, RangeProof, Secp256k1, SecretKey};
use grin_core::core::hash::Hash;
use grin_util::ToHex;
use rand::RngCore;
pub fn rand_commit() -> Commitment {
secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap()
}
pub fn rand_hash() -> Hash {
Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap()
}
pub fn rand_proof() -> RangeProof {
let secp = Secp256k1::new();
secp.bullet_proof(
rand::thread_rng().next_u64(),
secp::random_secret(),
secp::random_secret(),
secp::random_secret(),
None,
None,
)
}
pub fn proof(
value: u64,
fee: u64,
input_blind: &SecretKey,
hop_excesses: &Vec<&SecretKey>,
) -> (Commitment, RangeProof) {
let secp = Secp256k1::new();
let mut blind = input_blind.clone();
for hop_excess in hop_excesses {
blind.add_assign(&secp, &hop_excess).unwrap();
}
let out_value = value - fee;
let rp = secp.bullet_proof(
out_value,
blind.clone(),
secp::random_secret(),
secp::random_secret(),
None,
None,
);
(secp::commit(out_value, &blind).unwrap(), rp)
}
}
+87 -70
View File
@@ -1,18 +1,16 @@
use config::ServerConfig;
use node::HttpGrinNode;
use std::collections::HashMap;
use store::SwapStore;
use wallet::HttpWallet;
use crate::client::{MixClient, MixClientImpl};
use crate::crypto::dalek::DalekPublicKey;
use crate::node::GrinNode;
use crate::store::StoreError;
use clap::App;
use grin_core::global;
use grin_core::global::ChainTypes;
use grin_util::{StopState, ZeroingString};
use grin_wallet_impls::tor::config as tor_config;
use grin_wallet_impls::tor::process as tor_process;
use grin_wallet_util::OnionV3Address;
use rpassword;
use std::path::PathBuf;
use std::sync::Arc;
@@ -21,20 +19,22 @@ use tokio::runtime::Runtime;
#[macro_use]
extern crate clap;
mod client;
mod config;
mod crypto;
mod node;
mod onion;
mod rpc;
mod secp;
mod server;
mod servers;
mod store;
mod tor;
mod tx;
mod types;
mod wallet;
const DEFAULT_INTERVAL: u32 = 12 * 60 * 60;
fn main() {
real_main().unwrap();
fn main() -> Result<(), Box<dyn std::error::Error>> {
real_main()?;
std::process::exit(0);
}
@@ -61,10 +61,17 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
.value_of("round_time")
.map(|t| t.parse::<u32>().unwrap());
let bind_addr = args.value_of("bind_addr");
let socks_addr = args.value_of("socks_addr");
let grin_node_url = args.value_of("grin_node_url");
let grin_node_secret_path = args.value_of("grin_node_secret_path");
let wallet_owner_url = args.value_of("wallet_owner_url");
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
let prev_server = args
.value_of("prev_server")
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
let next_server = args
.value_of("next_server")
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
// Write a new config file if init-config command is supplied
if let ("init-config", Some(_)) = args.subcommand() {
@@ -76,9 +83,10 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
}
let server_config = ServerConfig {
key: secp::random_secret(),
key: crypto::secp::random_secret(),
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?,
socks_proxy_addr: socks_addr.unwrap_or("127.0.0.1:3001").parse()?,
grin_node_url: match grin_node_url {
Some(u) => u.parse()?,
None => config::grin_node_url(&chain_type),
@@ -99,6 +107,8 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
.to_str()
.map(|p| p.to_owned()),
},
prev_server,
next_server,
};
let password = prompt_password_confirm();
@@ -113,11 +123,6 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
let password = prompt_password();
let mut server_config = config::load_config(&config_path, &password)?;
// Override bind_addr, if supplied
if let Some(bind_addr) = bind_addr {
server_config.addr = bind_addr.parse()?;
}
// Override grin_node_url, if supplied
if let Some(grin_node_url) = grin_node_url {
server_config.grin_node_url = grin_node_url.parse()?;
@@ -138,6 +143,26 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
}
// Override bind_addr, if supplied
if let Some(bind_addr) = bind_addr {
server_config.addr = bind_addr.parse()?;
}
// Override socks_addr, if supplied
if let Some(socks_addr) = socks_addr {
server_config.socks_proxy_addr = socks_addr.parse()?;
}
// Override prev_server, if supplied
if let Some(prev_server) = prev_server {
server_config.prev_server = Some(prev_server);
}
// Override next_server, if supplied
if let Some(next_server) = next_server {
server_config.next_server = Some(next_server);
}
// Create GrinNode
let node = HttpGrinNode::new(
&server_config.grin_node_url,
@@ -165,17 +190,7 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
}
};
// Open SwapStore
let store = SwapStore::new(
config::get_grin_path(&chain_type) // todo: load from config
.join("db")
.to_str()
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
"db_root path error".to_string(),
)))?,
)?;
let mut tor_process = init_tor_listener(&server_config)?;
let mut tor_process = tor::init_tor_listener(&server_config)?;
let stop_state = Arc::new(StopState::new());
let stop_state_clone = stop_state.clone();
@@ -187,14 +202,52 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
stop_state_clone.stop();
});
// Start the mwixnet JSON-RPC HTTP server
rpc::listen(
server_config,
Arc::new(wallet),
Arc::new(node),
store,
stop_state,
)
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
let client: Arc<dyn MixClient> =
Arc::new(MixClientImpl::new(server_config.clone(), pk.clone()));
client
});
if server_config.prev_server.is_some() {
// Start the JSON-RPC HTTP 'mix' server
println!(
"Starting MIX server with public key {:?}",
server_config.server_pubkey().to_hex()
);
servers::mix_rpc::listen(
server_config,
next_mixer,
Arc::new(wallet),
Arc::new(node),
stop_state,
)
} else {
println!(
"Starting SWAP server with public key {:?}",
server_config.server_pubkey().to_hex()
);
// Open SwapStore
let store = SwapStore::new(
config::get_grin_path(&chain_type)
.join("db")
.to_str()
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
"db_root path error".to_string(),
)))?,
)?;
// Start the mwixnet JSON-RPC HTTP 'swap' server
servers::swap_rpc::listen(
server_config,
next_mixer,
Arc::new(wallet),
Arc::new(node),
store,
stop_state,
)
}
}
#[cfg(unix)]
@@ -245,39 +298,3 @@ fn prompt_wallet_password(wallet_pass: &Option<&str>) -> ZeroingString {
}
}
}
fn init_tor_listener(
server_config: &ServerConfig,
) -> Result<tor_process::TorProcess, Box<dyn std::error::Error>> {
let mut tor_dir = config::get_grin_path(&global::get_chain_type());
tor_dir.push("tor/listener");
let mut torrc_dir = tor_dir.clone();
torrc_dir.push("torrc");
tor_config::output_tor_listener_config(
tor_dir.to_str().unwrap(),
server_config.addr.to_string().as_str(),
&vec![server_config.key.clone()],
HashMap::new(),
HashMap::new(),
)
.unwrap();
// Start TOR process
let mut process = tor_process::TorProcess::new();
process
.torrc_path(torrc_dir.to_str().unwrap())
.working_dir(tor_dir.to_str().unwrap())
.timeout(20)
.completion_percent(100)
.launch()
.unwrap();
let onion_address = OnionV3Address::from_private(&server_config.key.0).unwrap();
println!(
"Server listening at http://{}.onion",
onion_address.to_ov3_str()
);
Ok(process)
}
+16 -5
View File
@@ -1,4 +1,4 @@
use crate::secp::Commitment;
use crate::crypto::secp::Commitment;
use grin_api::client;
use grin_api::json_rpc::{build_request, Request, Response};
@@ -43,10 +43,10 @@ pub fn is_unspent(node: &Arc<dyn GrinNode>, commit: &Commitment) -> Result<bool,
/// Checks whether a commitment is spendable at the block height provided
pub fn is_spendable(
node: &Arc<dyn GrinNode>,
output_commit: &Commitment,
commit: &Commitment,
next_block_height: u64,
) -> Result<bool, NodeError> {
let output = node.get_utxo(&output_commit)?;
let output = node.get_utxo(&commit)?;
if let Some(out) = output {
let is_coinbase = match out.output_type {
OutputType::Coinbase => true,
@@ -166,7 +166,7 @@ impl GrinNode for HttpGrinNode {
#[cfg(test)]
pub mod mock {
use super::{GrinNode, NodeError};
use crate::secp::Commitment;
use crate::crypto::secp::Commitment;
use grin_api::{OutputPrintable, OutputType};
use grin_core::core::Transaction;
@@ -181,13 +181,24 @@ pub mod mock {
}
impl MockGrinNode {
pub fn new() -> MockGrinNode {
pub fn new() -> Self {
MockGrinNode {
utxos: HashMap::new(),
txns_posted: RwLock::new(Vec::new()),
}
}
pub fn new_with_utxos(utxos: &Vec<&Commitment>) -> Self {
let mut node = MockGrinNode {
utxos: HashMap::new(),
txns_posted: RwLock::new(Vec::new()),
};
for utxo in utxos {
node.add_default_utxo(utxo);
}
node
}
pub fn add_utxo(&mut self, output_commit: &Commitment, utxo: &OutputPrintable) {
self.utxos.insert(output_commit.clone(), utxo.clone());
}
+38 -8
View File
@@ -1,4 +1,4 @@
use crate::secp::{self, Commitment, SecretKey};
use crate::crypto::secp::{self, Commitment, SecretKey};
use crate::types::Payload;
use crate::onion::OnionError::{InvalidKeyLength, SerializationError};
@@ -57,6 +57,15 @@ fn vec_to_32_byte_arr(v: Vec<u8>) -> Result<[u8; 32], OnionError> {
v.try_into().map_err(|_| InvalidKeyLength)
}
/// An onion with a layer decrypted
#[derive(Clone, Debug)]
pub struct PeeledOnion {
/// The payload from the peeled layer
pub payload: Payload,
/// The onion remaining after a layer was peeled
pub onion: Onion,
}
impl Onion {
pub fn serialize(&self) -> Result<Vec<u8>, ser::Error> {
let mut vec = vec![];
@@ -65,7 +74,7 @@ impl Onion {
}
/// 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), OnionError> {
pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<PeeledOnion, OnionError> {
let shared_secret = StaticSecret::from(secret_key.0).diffie_hellman(&self.ephemeral_pubkey);
let mut cipher = new_stream_cipher(&shared_secret)?;
@@ -106,7 +115,10 @@ impl Onion {
commit: commitment.clone(),
enc_payloads,
};
Ok((decrypted_payload, peeled_onion))
Ok(PeeledOnion {
payload: decrypted_payload,
onion: peeled_onion,
})
}
}
@@ -292,13 +304,14 @@ impl From<ser::Error> for OnionError {
#[cfg(test)]
pub mod test_util {
use super::{Onion, OnionError, RawBytes};
use crate::secp::test_util::{rand_commit, rand_proof};
use crate::secp::{random_secret, Commitment, SecretKey};
use crate::crypto::secp::test_util::{rand_commit, rand_proof};
use crate::crypto::secp::{random_secret, Commitment, SecretKey};
use crate::types::Payload;
use chacha20::cipher::StreamCipher;
use grin_core::core::FeeFields;
use rand::{thread_rng, RngCore};
use secp256k1zkp::pedersen::RangeProof;
use x25519_dalek::PublicKey as xPublicKey;
use x25519_dalek::{SharedSecret, StaticSecret};
@@ -307,6 +320,23 @@ pub mod test_util {
pub pubkey: xPublicKey,
pub payload: Payload,
}
pub fn new_hop(
server_key: &SecretKey,
hop_excess: &SecretKey,
fee: u64,
proof: Option<RangeProof>,
) -> Hop {
Hop {
pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
payload: Payload {
excess: hop_excess.clone(),
fee: FeeFields::from(fee as u32),
rangeproof: proof,
},
}
}
/*
Choose random xi for each node ni and create a Payload (Pi) for each containing xi
Build a rangeproof for Cn=Cin+(Σx1...n)*G and include it in payload Pn
@@ -391,7 +421,7 @@ pub mod test_util {
#[cfg(test)]
pub mod tests {
use super::test_util::{self, Hop};
use crate::secp;
use crate::crypto::secp;
use crate::types::Payload;
use grin_core::core::FeeFields;
@@ -454,8 +484,8 @@ pub mod tests {
};
for i in 0..5 {
let peeled = onion_packet.peel_layer(&keys[i]).unwrap();
payload = peeled.0;
onion_packet = peeled.1;
payload = peeled.payload;
onion_packet = peeled.onion;
}
assert!(payload.rangeproof.is_some());
+409
View File
@@ -0,0 +1,409 @@
use crate::config::ServerConfig;
use crate::crypto::dalek::{self, DalekSignature};
use crate::onion::{Onion, OnionError, PeeledOnion};
use crate::wallet::Wallet;
use crate::{node, tx, GrinNode};
use std::collections::{HashMap, HashSet};
use crate::client::MixClient;
use crate::tx::TxComponents;
use grin_core::core::{Output, OutputFeatures, TransactionBody};
use grin_core::global::DEFAULT_ACCEPT_FEE_BASE;
use grin_core::ser;
use grin_core::ser::ProtocolVersion;
use itertools::Itertools;
use secp256k1zkp::key::ZERO_KEY;
use secp256k1zkp::Secp256k1;
use std::sync::Arc;
use thiserror::Error;
/// Mixer error types
#[derive(Error, Debug)]
pub enum MixError {
#[error("Invalid number of payloads provided")]
InvalidPayloadLength,
#[error("Signature is invalid")]
InvalidSignature,
#[error("Rangeproof is invalid")]
InvalidRangeproof,
#[error("Rangeproof is required but was not supplied")]
MissingRangeproof,
#[error("Failed to peel onion layer: {0:?}")]
PeelOnionFailure(OnionError),
#[error("Fee too low (expected >= {minimum_fee:?}, actual {actual_fee:?})")]
FeeTooLow { minimum_fee: u64, actual_fee: u64 },
#[error("None of the outputs could be mixed")]
NoValidOutputs,
#[error("Dalek error: {0:?}")]
Dalek(dalek::DalekError),
#[error("Secp error: {0:?}")]
Secp(grin_util::secp::Error),
#[error("Error building transaction: {0:?}")]
TxError(tx::TxError),
#[error("Wallet error: {0:?}")]
WalletError(crate::wallet::WalletError),
#[error("Client comm error: {0:?}")]
Client(crate::client::ClientError),
}
/// An internal MWixnet server - a "Mixer"
pub trait MixServer: Send + Sync {
/// Swaps the outputs provided and returns the final swapped outputs and kernels.
fn mix_outputs(
&self,
onions: &Vec<Onion>,
sig: &DalekSignature,
) -> Result<(Vec<usize>, TxComponents), MixError>;
}
/// The standard MWixnet "Mixer" implementation
#[derive(Clone)]
pub struct MixServerImpl {
secp: Secp256k1,
server_config: ServerConfig,
mix_client: Option<Arc<dyn MixClient>>,
wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
}
impl MixServerImpl {
/// Create a new 'Mix' server
pub fn new(
server_config: ServerConfig,
mix_client: Option<Arc<dyn MixClient>>,
wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
) -> Self {
MixServerImpl {
secp: Secp256k1::new(),
server_config,
mix_client,
wallet,
node,
}
}
/// The fee base to use. For now, just using the default.
fn get_fee_base(&self) -> u64 {
DEFAULT_ACCEPT_FEE_BASE
}
/// Minimum fee to perform a mix.
/// Requires enough fee for the mixer's kernel.
fn get_minimum_mix_fee(&self) -> u64 {
TransactionBody::weight_by_iok(0, 0, 1) * self.get_fee_base()
}
fn peel_onion(&self, onion: &Onion) -> Result<PeeledOnion, MixError> {
// Verify that more than 1 payload exists when there's a next server,
// or that exactly 1 payload exists when this is the final server
if self.server_config.next_server.is_some() && onion.enc_payloads.len() <= 1
|| self.server_config.next_server.is_none() && onion.enc_payloads.len() != 1
{
return Err(MixError::InvalidPayloadLength);
}
// Peel the top layer
let peeled = onion
.peel_layer(&self.server_config.key)
.map_err(|e| MixError::PeelOnionFailure(e))?;
// Verify the fee meets the minimum
let fee: u64 = peeled.payload.fee.into();
if fee < self.get_minimum_mix_fee() {
return Err(MixError::FeeTooLow {
minimum_fee: self.get_minimum_mix_fee(),
actual_fee: fee,
});
}
if let Some(r) = peeled.payload.rangeproof {
// Verify the bullet proof
self.secp
.verify_bullet_proof(peeled.onion.commit, r, None)
.map_err(|_| MixError::InvalidRangeproof)?;
} else if peeled.onion.enc_payloads.is_empty() {
// A rangeproof is required in the last payload
return Err(MixError::MissingRangeproof);
}
Ok(peeled)
}
fn build_final_outputs(
&self,
peeled: &Vec<(usize, PeeledOnion)>,
) -> Result<(Vec<usize>, TxComponents), MixError> {
// Filter out commitments that already exist in the UTXO set
let filtered: Vec<&(usize, PeeledOnion)> = peeled
.iter()
.filter(|(_, p)| !node::is_unspent(&self.node, &p.onion.commit).unwrap_or(true))
.collect();
// Build plain outputs for each mix entry
let outputs: Vec<Output> = filtered
.iter()
.map(|(_, p)| {
Output::new(
OutputFeatures::Plain,
p.onion.commit,
p.payload.rangeproof.unwrap(),
)
})
.collect();
let fees_paid = filtered.iter().map(|(_, p)| p.payload.fee.fee()).sum();
let output_excesses = filtered
.iter()
.map(|(_, p)| p.payload.excess.clone())
.collect();
let components = tx::assemble_components(
&self.wallet,
&TxComponents {
offset: ZERO_KEY,
kernels: Vec::new(),
outputs,
},
&output_excesses,
self.get_fee_base(),
fees_paid,
)
.map_err(MixError::TxError)?;
let indices = filtered.iter().map(|(i, _)| *i).collect();
Ok((indices, components))
}
fn call_next_mixer(
&self,
peeled: &Vec<(usize, PeeledOnion)>,
) -> Result<(Vec<usize>, TxComponents), MixError> {
// Sort by commitment
let mut onions_with_index = peeled.clone();
onions_with_index
.sort_by(|(_, a), (_, b)| a.onion.commit.partial_cmp(&b.onion.commit).unwrap());
// Create map of prev indices to next indices
let map_indices: HashMap<usize, usize> =
HashMap::from_iter(onions_with_index.iter().enumerate().map(|(i, j)| (j.0, i)));
// Call next server
let onions = peeled.iter().map(|(_, p)| p.onion.clone()).collect();
let (mixed_indices, mixed_components) = self
.mix_client
.as_ref()
.unwrap()
.mix_outputs(&onions)
.map_err(MixError::Client)?;
// Remove filtered entries
let kept_next_indices = HashSet::<_>::from_iter(mixed_indices.clone());
let filtered_onions: Vec<&(usize, PeeledOnion)> = onions_with_index
.iter()
.filter(|(i, _)| {
map_indices.contains_key(i)
&& kept_next_indices.contains(map_indices.get(i).unwrap())
})
.collect();
// Calculate excess of entries kept
let excesses = filtered_onions
.iter()
.map(|(_, p)| p.payload.excess.clone())
.collect();
// Calculate total fee of entries kept
let fees_paid = filtered_onions
.iter()
.fold(0, |f, (_, p)| f + p.payload.fee.fee());
let indices = kept_next_indices.into_iter().sorted().collect();
let components = tx::assemble_components(
&self.wallet,
&mixed_components,
&excesses,
self.get_fee_base(),
fees_paid,
)
.map_err(MixError::TxError)?;
Ok((indices, components))
}
}
impl MixServer for MixServerImpl {
fn mix_outputs(
&self,
onions: &Vec<Onion>,
sig: &DalekSignature,
) -> Result<(Vec<usize>, TxComponents), MixError> {
// Verify Signature
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
sig.verify(
self.server_config.prev_server.as_ref().unwrap(),
serialized.as_slice(),
)
.map_err(|_| MixError::InvalidSignature)?;
// Peel onions and filter
let mut peeled: Vec<(usize, PeeledOnion)> = onions
.iter()
.enumerate()
.filter_map(|(i, o)| {
if let Some(p) = self.peel_onion(&o).ok() {
Some((i, p))
} else {
None
}
})
.collect();
// Remove duplicate commitments
peeled.sort_by_key(|(_, o)| o.onion.commit);
peeled.dedup_by_key(|(_, o)| o.onion.commit);
peeled.sort_by_key(|(i, _)| *i);
if peeled.is_empty() {
return Err(MixError::NoValidOutputs);
}
if self.server_config.next_server.is_some() {
self.call_next_mixer(&peeled)
} else {
self.build_final_outputs(&peeled)
}
}
}
#[cfg(test)]
mod test_util {
use crate::client::test_util::DirectMixClient;
use crate::node::mock::MockGrinNode;
use crate::wallet::mock::MockWallet;
use crate::{config, DalekPublicKey, MixClient};
use crate::servers::mix::MixServerImpl;
use secp256k1zkp::SecretKey;
use std::sync::Arc;
pub fn new_mixer(
server_key: &SecretKey,
prev_server: (&SecretKey, &DalekPublicKey),
next_server: &Option<(DalekPublicKey, Arc<dyn MixClient>)>,
node: &Arc<MockGrinNode>,
) -> (Arc<DirectMixClient>, Arc<MockWallet>) {
let config = config::test_util::local_config(
&server_key,
&Some(prev_server.1.clone()),
&next_server.as_ref().map(|(k, _)| k.clone()),
)
.unwrap();
let wallet = Arc::new(MockWallet::new());
let mix_server = Arc::new(MixServerImpl::new(
config,
next_server.as_ref().map(|(_, c)| c.clone()),
wallet.clone(),
node.clone(),
));
let client = Arc::new(DirectMixClient {
key: prev_server.0.clone(),
mix_server: mix_server.clone(),
});
(client, wallet)
}
}
#[cfg(test)]
mod tests {
use crate::crypto::dalek;
use crate::crypto::secp::{self, Commitment};
use crate::node::mock::MockGrinNode;
use crate::onion::test_util;
use crate::MixClient;
use ::function_name::named;
use std::collections::HashSet;
use std::sync::Arc;
macro_rules! init_test {
() => {{
grin_core::global::set_local_chain_type(
grin_core::global::ChainTypes::AutomatedTesting,
);
let db_root = concat!("./target/tmp/.", function_name!());
let _ = std::fs::remove_dir_all(db_root);
()
}};
}
#[test]
#[named]
fn mix_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
let node = Arc::new(MockGrinNode::new_with_utxos(&vec![&input_commit]));
let (swap_sk, swap_pk) = dalek::test_util::rand_keypair();
let (mix1_sk, mix1_pk) = dalek::test_util::rand_keypair();
let (mix2_sk, mix2_pk) = dalek::test_util::rand_keypair();
let swap_hop_excess = secp::random_secret();
let swap_hop = test_util::new_hop(&swap_sk, &swap_hop_excess, fee, None);
let mix1_hop_excess = secp::random_secret();
let mix1_hop = test_util::new_hop(&mix1_sk, &mix1_hop_excess, fee, None);
let mix2_hop_excess = secp::random_secret();
let (out_commit, proof) = secp::test_util::proof(
value,
fee * 3,
&blind,
&vec![&swap_hop_excess, &mix1_hop_excess, &mix2_hop_excess],
);
let mix2_hop = test_util::new_hop(&mix2_sk, &mix2_hop_excess, fee, Some(proof));
let onion = test_util::create_onion(&input_commit, &vec![swap_hop, mix1_hop, mix2_hop])?;
let (mixer2_client, mixer2_wallet) =
super::test_util::new_mixer(&mix2_sk, (&mix1_sk, &mix1_pk), &None, &node);
let (mixer1_client, mixer1_wallet) = super::test_util::new_mixer(
&mix1_sk,
(&swap_sk, &swap_pk),
&Some((mix2_pk.clone(), mixer2_client.clone())),
&node,
);
// Emulate the swap server peeling the onion and then calling mix1
let mix1_onion = onion.peel_layer(&swap_sk)?;
let (mixed_indices, mixed_components) =
mixer1_client.mix_outputs(&vec![mix1_onion.onion.clone()])?;
// Verify 3 outputs are returned: mixed output, mixer1's output, and mixer2's output
assert_eq!(mixed_indices, vec![0 as usize]);
assert_eq!(mixed_components.outputs.len(), 3);
let output_commits: HashSet<Commitment> = mixed_components
.outputs
.iter()
.map(|o| o.identifier.commit.clone())
.collect();
assert!(output_commits.contains(&out_commit));
assert_eq!(mixer1_wallet.built_outputs().len(), 1);
assert!(output_commits.contains(mixer1_wallet.built_outputs().get(0).unwrap()));
assert_eq!(mixer2_wallet.built_outputs().len(), 1);
assert!(output_commits.contains(mixer2_wallet.built_outputs().get(0).unwrap()));
Ok(())
}
}
+116
View File
@@ -0,0 +1,116 @@
use crate::config::ServerConfig;
use crate::crypto::dalek::{self, DalekSignature};
use crate::node::GrinNode;
use crate::onion::Onion;
use crate::servers::mix::{MixError, MixServer, MixServerImpl};
use crate::wallet::Wallet;
use crate::client::MixClient;
use grin_util::StopState;
use jsonrpc_derive::rpc;
use jsonrpc_http_server::jsonrpc_core::{self as jsonrpc, IoHandler};
use jsonrpc_http_server::{DomainsValidation, ServerBuilder};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn};
use std::time::Duration;
#[derive(Serialize, Deserialize)]
pub struct MixReq {
onions: Vec<Onion>,
#[serde(with = "dalek::dalek_sig_serde")]
sig: DalekSignature,
}
impl MixReq {
pub fn new(onions: Vec<Onion>, sig: DalekSignature) -> Self {
MixReq { onions, sig }
}
}
#[rpc(server)]
pub trait MixAPI {
#[rpc(name = "mix")]
fn mix(&self, mix: MixReq) -> jsonrpc::Result<jsonrpc::Value>;
}
#[derive(Clone)]
struct RPCMixServer {
server_config: ServerConfig,
server: Arc<Mutex<dyn MixServer>>,
}
impl RPCMixServer {
/// Spin up an instance of the JSON-RPC HTTP server.
fn start_http(&self) -> jsonrpc_http_server::Server {
let mut io = IoHandler::new();
io.extend_with(RPCMixServer::to_delegate(self.clone()));
ServerBuilder::new(io)
.cors(DomainsValidation::Disabled)
.request_middleware(|request: hyper::Request<hyper::Body>| {
if request.uri() == "/v1" {
request.into()
} else {
jsonrpc_http_server::Response::bad_request("Only v1 supported").into()
}
})
.start_http(&self.server_config.addr)
.expect("Unable to start RPC server")
}
}
impl From<MixError> for jsonrpc::Error {
fn from(e: MixError) -> Self {
jsonrpc::Error::invalid_params(e.to_string())
}
}
impl MixAPI for RPCMixServer {
fn mix(&self, mix: MixReq) -> jsonrpc::Result<jsonrpc::Value> {
self.server
.lock()
.unwrap()
.mix_outputs(&mix.onions, &mix.sig)?;
Ok(jsonrpc::Value::String("success".into()))
}
}
/// Spin up the JSON-RPC web server
pub fn listen(
server_config: ServerConfig,
next_server: Option<Arc<dyn MixClient>>,
wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
stop_state: Arc<StopState>,
) -> Result<(), Box<dyn std::error::Error>> {
let server = MixServerImpl::new(
server_config.clone(),
next_server,
wallet.clone(),
node.clone(),
);
let server = Arc::new(Mutex::new(server));
let rpc_server = RPCMixServer {
server_config: server_config.clone(),
server: server.clone(),
};
let http_server = rpc_server.start_http();
let close_handle = http_server.close_handle();
let round_handle = spawn(move || loop {
if stop_state.is_stopped() {
close_handle.close();
break;
}
sleep(Duration::from_millis(100));
});
http_server.wait();
round_handle.join().unwrap();
Ok(())
}
+4
View File
@@ -0,0 +1,4 @@
pub mod mix;
pub mod mix_rpc;
pub mod swap;
pub mod swap_rpc;
File diff suppressed because it is too large Load Diff
+36 -29
View File
@@ -1,11 +1,12 @@
use crate::config::ServerConfig;
use crate::crypto::comsig::{self, ComSignature};
use crate::node::GrinNode;
use crate::onion::Onion;
use crate::secp::{self, ComSignature};
use crate::server::{Server, ServerImpl, SwapError};
use crate::servers::swap::{SwapError, SwapServer, SwapServerImpl};
use crate::store::SwapStore;
use crate::wallet::Wallet;
use crate::client::MixClient;
use grin_util::StopState;
use jsonrpc_core::Value;
use jsonrpc_derive::rpc;
@@ -19,31 +20,27 @@ use std::time::Duration;
#[derive(Serialize, Deserialize)]
pub struct SwapReq {
onion: Onion,
#[serde(with = "secp::comsig_serde")]
#[serde(with = "comsig::comsig_serde")]
comsig: ComSignature,
}
#[rpc(server)]
pub trait API {
pub trait SwapAPI {
#[rpc(name = "swap")]
fn swap(&self, swap: SwapReq) -> jsonrpc_core::Result<Value>;
// milestone 3: Used by mwixnet coinswap servers to communicate with each other
// fn derive_outputs(&self, entries: Vec<Onion>) -> jsonrpc_core::Result<Value>;
// fn derive_kernel(&self, tx: Tx) -> jsonrpc_core::Result<Value>;
}
#[derive(Clone)]
struct RPCServer {
struct RPCSwapServer {
server_config: ServerConfig,
server: Arc<Mutex<dyn Server>>,
server: Arc<Mutex<dyn SwapServer>>,
}
impl RPCServer {
impl RPCSwapServer {
/// Spin up an instance of the JSON-RPC HTTP server.
fn start_http(&self) -> jsonrpc_http_server::Server {
let mut io = IoHandler::new();
io.extend_with(RPCServer::to_delegate(self.clone()));
io.extend_with(RPCSwapServer::to_delegate(self.clone()));
ServerBuilder::new(io)
.cors(DomainsValidation::Disabled)
@@ -72,8 +69,7 @@ impl From<SwapError> for Error {
}
}
impl API for RPCServer {
/// Implements the 'swap' API
impl SwapAPI for RPCSwapServer {
fn swap(&self, swap: SwapReq) -> jsonrpc_core::Result<Value> {
self.server
.lock()
@@ -86,15 +82,22 @@ impl API for RPCServer {
/// Spin up the JSON-RPC web server
pub fn listen(
server_config: ServerConfig,
next_server: Option<Arc<dyn MixClient>>,
wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
store: SwapStore,
stop_state: Arc<StopState>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let server = ServerImpl::new(server_config.clone(), wallet.clone(), node.clone(), store);
let server = SwapServerImpl::new(
server_config.clone(),
next_server,
wallet.clone(),
node.clone(),
store,
);
let server = Arc::new(Mutex::new(server));
let rpc_server = RPCServer {
let rpc_server = RPCSwapServer {
server_config: server_config.clone(),
server: server.clone(),
};
@@ -128,11 +131,12 @@ pub fn listen(
#[cfg(test)]
mod tests {
use crate::config::ServerConfig;
use crate::crypto::comsig::ComSignature;
use crate::crypto::secp;
use crate::onion::test_util;
use crate::rpc::{RPCServer, SwapReq};
use crate::secp::{self, ComSignature};
use crate::server::mock::MockServer;
use crate::server::{Server, SwapError};
use crate::servers::swap::mock::MockSwapServer;
use crate::servers::swap::{SwapError, SwapServer};
use crate::servers::swap_rpc::{RPCSwapServer, SwapReq};
use std::net::TcpListener;
use std::sync::{Arc, Mutex};
@@ -147,20 +151,23 @@ mod tests {
/// Spin up a temporary web service, query the API, then cleanup and return response
fn make_request(
server: Arc<Mutex<dyn Server>>,
server: Arc<Mutex<dyn SwapServer>>,
req: String,
) -> Result<String, Box<dyn std::error::Error>> {
let server_config = ServerConfig {
key: secp::random_secret(),
interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
socks_proxy_addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?,
grin_node_secret_path: None,
wallet_owner_url: "127.0.0.1:3420".parse()?,
wallet_owner_secret_path: None,
prev_server: None,
next_server: None,
};
let rpc_server = RPCServer {
let rpc_server = RPCSwapServer {
server_config: server_config.clone(),
server: server.clone(),
};
@@ -208,7 +215,7 @@ mod tests {
comsig,
};
let server: Arc<Mutex<dyn Server>> = Arc::new(Mutex::new(MockServer::new()));
let server: Arc<Mutex<dyn SwapServer>> = Arc::new(Mutex::new(MockSwapServer::new()));
let req = format!(
"{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}",
@@ -224,7 +231,7 @@ mod tests {
#[test]
fn swap_bad_request() -> Result<(), Box<dyn std::error::Error>> {
let server: Arc<Mutex<dyn Server>> = Arc::new(Mutex::new(MockServer::new()));
let server: Arc<Mutex<dyn SwapServer>> = Arc::new(Mutex::new(MockSwapServer::new()));
let params = "{ \"param\": \"Not a valid Swap request\" }";
let req = format!(
@@ -248,14 +255,14 @@ mod tests {
comsig,
};
let mut server = MockServer::new();
let mut server = MockSwapServer::new();
server.set_response(
&onion,
SwapError::CoinNotFound {
commit: commitment.clone(),
},
);
let server: Arc<Mutex<dyn Server>> = Arc::new(Mutex::new(server));
let server: Arc<Mutex<dyn SwapServer>> = Arc::new(Mutex::new(server));
let req = format!(
"{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}",
@@ -263,9 +270,9 @@ mod tests {
);
let response = make_request(server, req)?;
let expected = format!(
"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32602,\"message\":\"Output {:?} does not exist, or is already spent.\"}},\"id\":\"1\"}}\n",
commitment
);
"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32602,\"message\":\"Output {:?} does not exist, or is already spent.\"}},\"id\":\"1\"}}\n",
commitment
);
assert_eq!(response, expected);
Ok(())
}
+9 -3
View File
@@ -1,5 +1,5 @@
use crate::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use crate::onion::Onion;
use crate::secp::{self, Commitment, RangeProof, SecretKey};
use crate::types::{read_optional, write_optional};
use grin_core::core::hash::Hash;
@@ -23,6 +23,7 @@ pub enum SwapStatus {
Unprocessed,
InProcess { kernel_hash: Hash },
Completed { kernel_hash: Hash, block_hash: Hash },
Failed,
}
impl Writeable for SwapStatus {
@@ -43,6 +44,9 @@ impl Writeable for SwapStatus {
kernel_hash.write(writer)?;
block_hash.write(writer)?;
}
SwapStatus::Failed => {
writer.write_u8(3)?;
}
};
Ok(())
@@ -65,6 +69,7 @@ impl Readable for SwapStatus {
block_hash,
}
}
3 => SwapStatus::Failed,
_ => {
return Err(ser::Error::CorruptedData);
}
@@ -239,10 +244,11 @@ impl SwapStore {
#[cfg(test)]
mod tests {
use crate::crypto::secp;
use crate::crypto::secp::test_util::{rand_commit, rand_hash, rand_proof};
use crate::onion::test_util::rand_onion;
use crate::secp::test_util::{rand_commit, rand_hash, rand_proof};
use crate::store::{SwapData, SwapStatus, SwapStore};
use crate::{secp, StoreError};
use crate::StoreError;
use grin_core::core::{Input, OutputFeatures};
use grin_core::global::{self, ChainTypes};
use rand::RngCore;
+83
View File
@@ -0,0 +1,83 @@
use crate::config::{self, ServerConfig};
use grin_core::global;
use grin_wallet_impls::tor::config as tor_config;
use grin_wallet_impls::tor::process::TorProcess;
use std::collections::HashMap;
use thiserror::Error;
/// Tor error types
#[derive(Error, Debug)]
pub enum TorError {
#[error("Error generating config: {0:?}")]
ConfigError(String),
#[error("Error starting process: {0:?}")]
ProcessError(grin_wallet_impls::tor::process::Error),
}
pub fn init_tor_listener(server_config: &ServerConfig) -> Result<TorProcess, TorError> {
println!("Initializing tor listener");
let mut tor_dir = config::get_grin_path(&global::get_chain_type());
tor_dir.push("tor/listener");
let mut torrc_dir = tor_dir.clone();
torrc_dir.push("torrc");
tor_config::output_tor_listener_config(
tor_dir.to_str().unwrap(),
server_config.addr.to_string().as_str(),
&vec![server_config.key.clone()],
HashMap::new(),
HashMap::new(),
)
.map_err(|e| TorError::ConfigError(e.to_string()))?;
// Start TOR process
let mut process = TorProcess::new();
process
.torrc_path(torrc_dir.to_str().unwrap())
.working_dir(tor_dir.to_str().unwrap())
.timeout(20)
.completion_percent(100)
.launch()
.map_err(TorError::ProcessError)?;
println!(
"Server listening at http://{}.onion",
server_config.onion_address().to_ov3_str()
);
Ok(process)
}
pub fn init_tor_sender(server_config: &ServerConfig) -> Result<TorProcess, TorError> {
println!(
"Starting TOR Process for send at {:?}",
server_config.socks_proxy_addr
);
let mut tor_dir = config::get_grin_path(&global::get_chain_type());
tor_dir.push("tor/sender");
let mut torrc_dir = tor_dir.clone();
torrc_dir.push("torrc");
tor_config::output_tor_sender_config(
tor_dir.to_str().unwrap(),
&server_config.socks_proxy_addr.to_string(),
HashMap::new(),
HashMap::new(),
)
.map_err(|e| TorError::ConfigError(e.to_string()))?;
// Start TOR process
let mut tor_process = TorProcess::new();
tor_process
.torrc_path(torrc_dir.to_str().unwrap())
.working_dir(tor_dir.to_str().unwrap())
.timeout(20)
.completion_percent(100)
.launch()
.map_err(TorError::ProcessError)?;
Ok(tor_process)
}
+182
View File
@@ -0,0 +1,182 @@
use crate::crypto::secp;
use crate::wallet::Wallet;
use grin_core::core::{
FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TransactionBody, TxKernel,
};
use grin_keychain::BlindingFactor;
use secp256k1zkp::{ContextFlag, Secp256k1, SecretKey};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;
/// Error types for interacting with wallets
#[derive(Error, Debug)]
pub enum TxError {
#[error("Error computing transactions's offset: {0:?}")]
OffsetError(secp256k1zkp::Error),
#[error("Error building kernel's fee fields: {0:?}")]
KernelFeeError(grin_core::core::transaction::Error),
#[error("Error computing kernel's excess: {0:?}")]
KernelExcessError(secp256k1zkp::Error),
#[error("Error computing kernel's signature message: {0:?}")]
KernelSigMessageError(grin_core::core::transaction::Error),
#[error("Error signing kernel: {0:?}")]
KernelSigError(secp256k1zkp::Error),
#[error("Built kernel failed to verify: {0:?}")]
KernelVerifyError(grin_core::core::transaction::Error),
#[error("Output blinding factor is invalid: {0:?}")]
OutputBlindError(secp256k1zkp::Error),
#[error("Wallet error: {0:?}")]
WalletError(crate::wallet::WalletError),
}
/// A collection of transaction components
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TxComponents {
/// Transaction offset
pub offset: SecretKey,
/// Transaction kernels
pub kernels: Vec<TxKernel>,
/// Transaction outputs
pub outputs: Vec<Output>,
}
/// Builds and verifies the finalized swap 'Transaction' using the provided components.
pub fn assemble_tx(
wallet: &Arc<dyn Wallet>,
inputs: &Vec<Input>,
outputs: &Vec<Output>,
kernels: &Vec<TxKernel>,
fee_base: u64,
fees_paid: u64,
prev_offset: &SecretKey,
output_excesses: &Vec<SecretKey>,
) -> Result<Transaction, TxError> {
// calculate minimum fee required for the kernel
let min_kernel_fee =
TransactionBody::weight_by_iok(inputs.len() as u64, outputs.len() as u64, 1) * fee_base;
let components = add_kernel_and_collect_fees(
&wallet,
&outputs,
&kernels,
fee_base,
min_kernel_fee,
fees_paid,
&prev_offset,
&output_excesses,
)?;
// assemble the transaction
let tx = Transaction::new(
Inputs::from(inputs.as_slice()),
&components.outputs,
&components.kernels,
)
.with_offset(BlindingFactor::from_secret_key(components.offset));
Ok(tx)
}
/// Adds a kernel and output to a collection of transaction components to consume fees and offset excesses.
pub fn assemble_components(
wallet: &Arc<dyn Wallet>,
components: &TxComponents,
output_excesses: &Vec<SecretKey>,
fee_base: u64,
fees_paid: u64,
) -> Result<TxComponents, TxError> {
// calculate minimum fee required for the kernel
let min_kernel_fee = TransactionBody::weight_by_iok(0, 0, 1) * fee_base;
add_kernel_and_collect_fees(
&wallet,
&components.outputs,
&components.kernels,
fee_base,
min_kernel_fee,
fees_paid,
&components.offset,
&output_excesses,
)
}
fn add_kernel_and_collect_fees(
wallet: &Arc<dyn Wallet>,
outputs: &Vec<Output>,
kernels: &Vec<TxKernel>,
fee_base: u64,
min_kernel_fee: u64,
fees_paid: u64,
prev_offset: &SecretKey,
output_excesses: &Vec<SecretKey>,
) -> Result<TxComponents, TxError> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let mut txn_outputs = outputs.clone();
let mut txn_excesses = output_excesses.clone();
let mut txn_kernels = kernels.clone();
let mut kernel_fee = fees_paid;
// calculate fee required if we add our own output
let fee_to_collect = TransactionBody::weight_by_iok(0, 1, 0) * 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 fees_paid > min_kernel_fee + fee_to_collect + fee_to_spend {
let amount = fees_paid - (min_kernel_fee + fee_to_collect);
kernel_fee -= amount;
let wallet_output = wallet.build_output(amount).map_err(TxError::WalletError)?;
txn_outputs.push(wallet_output.1);
let output_excess = SecretKey::from_slice(&secp, &wallet_output.0.as_ref())
.map_err(TxError::OutputBlindError)?;
txn_excesses.push(output_excess);
}
// generate random transaction offset
let our_offset = secp::random_secret();
let txn_offset = secp
.blind_sum(vec![prev_offset.clone(), our_offset.clone()], Vec::new())
.map_err(TxError::OffsetError)?;
// calculate kernel excess
let kern_excess = secp
.blind_sum(txn_excesses, vec![our_offset.clone()])
.map_err(TxError::KernelExcessError)?;
// build and verify kernel
let kernel = build_kernel(&kern_excess, kernel_fee)?;
txn_kernels.push(kernel);
// Sort outputs & kernels by commitment
txn_kernels.sort_by(|a, b| a.excess.partial_cmp(&b.excess).unwrap());
txn_outputs.sort_by(|a, b| {
a.identifier
.commit
.partial_cmp(&b.identifier.commit)
.unwrap()
});
Ok(TxComponents {
offset: txn_offset,
kernels: txn_kernels,
outputs: txn_outputs,
})
}
pub fn build_kernel(excess: &SecretKey, fee: u64) -> Result<TxKernel, TxError> {
let mut kernel = TxKernel::with_features(KernelFeatures::Plain {
fee: FeeFields::new(0, fee).map_err(TxError::KernelFeeError)?,
});
let msg = kernel
.msg_to_sign()
.map_err(TxError::KernelSigMessageError)?;
kernel.excess = secp::commit(0, &excess).map_err(TxError::KernelExcessError)?;
kernel.excess_sig = secp::sign(&excess, &msg).map_err(TxError::KernelSigError)?;
kernel.verify().map_err(TxError::KernelVerifyError)?;
Ok(kernel)
}
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::secp::{self, RangeProof, SecretKey};
use crate::crypto::secp::{self, RangeProof, SecretKey};
use grin_core::core::FeeFields;
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
+29 -82
View File
@@ -1,19 +1,16 @@
use crate::secp;
use crate::crypto::secp;
use grin_api::client;
use grin_api::json_rpc::{build_request, Request, Response};
use grin_core::core::{
FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TransactionBody, TxKernel,
};
use grin_core::core::Output;
use grin_core::libtx::secp_ser;
use grin_keychain::BlindingFactor;
use grin_util::{ToHex, ZeroingString};
use grin_wallet_api::{EncryptedRequest, EncryptedResponse, JsonId, Token};
use secp256k1zkp::{ContextFlag, PublicKey, Secp256k1, SecretKey};
use secp256k1zkp::{PublicKey, Secp256k1, SecretKey};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::net::SocketAddr;
use std::sync::Arc;
use thiserror::Error;
pub trait Wallet: Send + Sync {
@@ -24,18 +21,6 @@ pub trait Wallet: Send + Sync {
/// Error types for interacting with wallets
#[derive(Error, Debug)]
pub enum WalletError {
#[error("Error building kernel's fee fields: {0:?}")]
KernelFeeError(grin_core::core::transaction::Error),
#[error("Error computing kernel's excess: {0:?}")]
KernelExcessError(secp256k1zkp::Error),
#[error("Error computing kernel's signature message: {0:?}")]
KernelSigMessageError(grin_core::core::transaction::Error),
#[error("Error signing kernel: {0:?}")]
KernelSigError(secp256k1zkp::Error),
#[error("Built kernel failed to verify: {0:?}")]
KernelVerifyError(grin_core::core::transaction::Error),
#[error("Output blinding factor is invalid: {0:?}")]
OutputBlindError(secp256k1zkp::Error),
#[error("Error encrypting request: {0:?}")]
EncryptRequestError(grin_wallet_libwallet::Error),
#[error("Error decrypting response: {0:?}")]
@@ -48,67 +33,6 @@ pub enum WalletError {
ResponseParseError(grin_api::json_rpc::Error),
}
/// Builds and verifies a 'Transaction' using the provided components.
pub fn assemble_tx(
wallet: &Arc<dyn Wallet>,
inputs: &Vec<Input>,
outputs: &Vec<Output>,
fee_base: u64,
total_fee: u64,
excesses: &Vec<SecretKey>,
) -> Result<Transaction, WalletError> {
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 = SecretKey::from_slice(&secp, &wallet_output.0.as_ref())
.map_err(WalletError::OutputBlindError)?;
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()])
.map_err(WalletError::KernelExcessError)?;
// build and verify kernel
let mut kernel = TxKernel::with_features(KernelFeatures::Plain {
fee: FeeFields::new(0, kernel_fee).map_err(WalletError::KernelFeeError)?,
});
let msg = kernel
.msg_to_sign()
.map_err(WalletError::KernelSigMessageError)?;
kernel.excess = secp::commit(0, &kern_excess).map_err(WalletError::KernelExcessError)?;
kernel.excess_sig = secp::sign(&kern_excess, &msg).map_err(WalletError::KernelSigError)?;
kernel.verify().map_err(WalletError::KernelVerifyError)?;
// 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 {
@@ -272,15 +196,34 @@ impl Wallet for HttpWallet {
#[cfg(test)]
pub mod mock {
use super::{Wallet, WalletError};
use crate::secp;
use crate::crypto::secp;
use std::borrow::BorrowMut;
use grin_core::core::{Output, OutputFeatures};
use grin_keychain::BlindingFactor;
use secp256k1zkp::pedersen::Commitment;
use secp256k1zkp::Secp256k1;
use std::sync::{Arc, Mutex};
/// HTTP (JSONRPC) implementation of the 'Wallet' trait.
/// Mock implementation of the 'Wallet' trait for unit-tests.
#[derive(Clone)]
pub struct MockWallet {}
pub struct MockWallet {
built_outputs: Arc<Mutex<Vec<Commitment>>>,
}
impl MockWallet {
/// Creates a new, empty MockWallet.
pub fn new() -> Self {
MockWallet {
built_outputs: Arc::new(Mutex::new(Vec::new())),
}
}
/// Returns the commitments of all outputs built for the wallet.
pub fn built_outputs(&self) -> Vec<Commitment> {
self.built_outputs.lock().unwrap().clone()
}
}
impl Wallet for MockWallet {
/// Builds an 'Output' for the wallet using the 'build_output' RPC API.
@@ -297,6 +240,10 @@ pub mod mock {
None,
);
let output = Output::new(OutputFeatures::Plain, commit.clone(), proof);
let mut locked = self.built_outputs.lock().unwrap();
locked.borrow_mut().push(output.commitment().clone());
Ok((BlindingFactor::from_secret_key(blind), output))
}
}