diff --git a/Cargo.lock b/Cargo.lock
index 1bb4713..924569a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -955,6 +955,21 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+[[package]]
+name = "function_name"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7"
+dependencies = [
+ "function_name-proc-macro",
+]
+
+[[package]]
+name = "function_name-proc-macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333"
+
[[package]]
name = "futures"
version = "0.1.31"
@@ -1737,6 +1752,31 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+[[package]]
+name = "headers"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
+dependencies = [
+ "base64 0.13.0",
+ "bitflags 1.3.2",
+ "bytes 1.1.0",
+ "headers-core",
+ "http",
+ "httpdate 1.0.1",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
[[package]]
name = "heck"
version = "0.3.3"
@@ -1897,6 +1937,24 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-proxy"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc"
+dependencies = [
+ "bytes 1.1.0",
+ "futures 0.3.17",
+ "headers",
+ "http",
+ "hyper 0.14.14",
+ "hyper-tls 0.5.0",
+ "native-tls",
+ "tokio 1.12.0",
+ "tokio-native-tls",
+ "tower-service",
+]
+
[[package]]
name = "hyper-rustls"
version = "0.20.0"
@@ -1956,6 +2014,19 @@ dependencies = [
"tokio-tls",
]
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes 1.1.0",
+ "hyper 0.14.14",
+ "native-tls",
+ "tokio 1.12.0",
+ "tokio-native-tls",
+]
+
[[package]]
name = "i18n-config"
version = "0.4.2"
@@ -2478,6 +2549,7 @@ dependencies = [
"curve25519-dalek 2.1.3",
"dirs",
"ed25519-dalek",
+ "function_name",
"futures 0.3.17",
"grin_api 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)",
"grin_chain 5.2.0-alpha.1 (git+https://github.com/mimblewimble/grin)",
@@ -2493,6 +2565,7 @@ dependencies = [
"grin_wallet_util",
"hmac 0.12.0",
"hyper 0.14.14",
+ "hyper-proxy",
"itertools",
"jsonrpc-core 18.0.0",
"jsonrpc-derive",
@@ -3280,7 +3353,7 @@ dependencies = [
"http-body 0.3.1",
"hyper 0.13.10",
"hyper-rustls 0.21.0",
- "hyper-tls",
+ "hyper-tls 0.4.3",
"ipnet",
"js-sys",
"lazy_static",
@@ -3634,6 +3707,17 @@ dependencies = [
"yaml-rust 0.4.5",
]
+[[package]]
+name = "sha1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cc229fb94bcb689ffc39bd4ded842f6ff76885efede7c6d1ffb62582878bea"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.10.1",
+]
+
[[package]]
name = "sha2"
version = "0.8.2"
@@ -4019,6 +4103,16 @@ dependencies = [
"syn 1.0.84",
]
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio 1.12.0",
+]
+
[[package]]
name = "tokio-rustls"
version = "0.13.1"
diff --git a/Cargo.toml b/Cargo.toml
index 7078fb5..2e3e3f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,9 +14,11 @@ clap = { version = "2.33", features = ["yaml"] }
curve25519-dalek = "2.1"
dirs = "2.0"
ed25519-dalek = "1.0.1"
+function_name = "0.3.0"
futures = "0.3"
hmac = { version = "0.12.0", features = ["std"]}
hyper = { version = "0.14", features = ["full"] }
+hyper-proxy = "0.9.1"
itertools = { version = "0.10.3"}
jsonrpc-core = "18.0"
jsonrpc-derive = "18.0"
diff --git a/README.md b/README.md
index 3a2a1fc..e9efe52 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
# MWixnet
This is an implementation of @tromp's [CoinSwap Proposal](https://forum.grin.mw/t/mimblewimble-coinswap-proposal/8322) with some slight modifications.
-A set of n CoinSwap servers (nodei with i=1...n) are agreed upon in advance. They each have a known public key.
+A set of n CoinSwap servers (Ni with i=1...n) are agreed upon in advance. They each have a known public key.
+
+We refer to the first server (N1) as the "Swap Server." This is the server that wallets can submit their coinswaps too.
+
+We refer to the remaining servers (N2...Nn) as "Mixers."
### Setup
#### init-config
@@ -19,7 +23,7 @@ A grin-wallet account must be created for receiving extra mwixnet fees. The wall
With your wallet and fully synced node both online and listening at the addresses configured, the mwixnet server can be started by running `mwixnet` and providing the server key password and wallet password when prompted.
### SWAP API
-The first CoinSwap server (n1) provides the `swap` API, publicly available for use by GRIN wallets.
+The Swap Server (N1) provides the `swap` API, which is publicly available for use by GRIN wallets.
**jsonrpc:** `2.0`
**method:** `swap`
diff --git a/mwixnet.yml b/mwixnet.yml
index bd1c5d8..b3b3091 100644
--- a/mwixnet.yml
+++ b/mwixnet.yml
@@ -38,6 +38,18 @@ args:
help: Address to bind the rpc server to (e.g. 127.0.0.1:3000)
long: bind_addr
takes_value: true
+ - socks_addr:
+ help: Address to bind the SOCKS5 tor proxy to (e.g. 127.0.0.1:3001)
+ long: socks_addr
+ takes_value: true
+ - prev_server:
+ help: Hex public key of the previous swap/mix server
+ long: prev_server
+ takes_value: true
+ - next_server:
+ help: Hex public key of the next mix server
+ long: next_server
+ takes_value: true
subcommands:
- init-config:
about: Writes a new configuration file
\ No newline at end of file
diff --git a/src/client.rs b/src/client.rs
new file mode 100644
index 0000000..d286a1b
--- /dev/null
+++ b/src/client.rs
@@ -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) -> Result<(Vec, 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(
+ &self,
+ addr: &OnionV3Address,
+ method: &str,
+ params: &serde_json::Value,
+ ) -> Result {
+ 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) -> Result<(Vec, 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, TxComponents)>(&self.addr, "mix", ¶ms)
+ }
+}
+
+#[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, TxComponents)>,
+ }
+
+ impl MockMixClient {
+ pub fn new() -> MockMixClient {
+ MockMixClient {
+ results: HashMap::new(),
+ }
+ }
+
+ pub fn set_response(&mut self, onions: &Vec, r: (Vec, TxComponents)) {
+ self.results.insert(onions.clone(), r);
+ }
+ }
+
+ impl MixClient for MockMixClient {
+ fn mix_outputs(
+ &self,
+ onions: &Vec,
+ ) -> Result<(Vec, 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,
+ }
+
+ impl MixClient for DirectMixClient {
+ fn mix_outputs(
+ &self,
+ onions: &Vec,
+ ) -> Result<(Vec, 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())
+ }
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index d6928e8..1fab7dc 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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,
+ /// 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,
+ /// public key of the next mix server
+ #[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
+ pub next_server: Option,
}
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 {
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,
wallet_owner_url: SocketAddr,
wallet_owner_secret_path: Option,
+ #[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
+ prev_server: Option,
+ #[serde(with = "crate::crypto::dalek::option_dalek_pubkey_serde", default)]
+ next_server: Option,
}
/// 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 {
- 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,
+ next_server: &Option,
+ ) -> Result> {
+ 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() {
diff --git a/src/secp.rs b/src/crypto/comsig.rs
similarity index 61%
rename from src/secp.rs
rename to src/crypto/comsig.rs
index 72fd14d..55fc627 100644
--- a/src/secp.rs
+++ b/src/crypto/comsig.rs
@@ -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 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,
- ) -> Result {
- 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) -> 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,
- ) -> Result {
- 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(comsig: &ComSignature, serializer: S) -> Result
- 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
- 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(reader: &mut R) -> 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) -> 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(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(|_| 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);
- 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 {
- 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 {
- 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 {
- 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 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,
+ ) -> Result {
+ 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) -> 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,
+ ) -> Result {
+ 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(comsig: &ComSignature, serializer: S) -> Result
+ 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
+ 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(reader: &mut R) -> Result {
+ 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(&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(())
+ }
+}
diff --git a/src/crypto/dalek.rs b/src/crypto/dalek.rs
new file mode 100644
index 0000000..45474b3
--- /dev/null
+++ b/src/crypto/dalek.rs
@@ -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 {
+ 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 for DalekPublicKey {
+ fn as_ref(&self) -> &PublicKey {
+ &self.0
+ }
+}
+
+/// Serializes an Option 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(pk: &Option, serializer: S) -> Result
+ 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