diff --git a/Cargo.lock b/Cargo.lock index de4c36d..2a7812e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2476,7 +2476,6 @@ dependencies = [ "chacha20", "clap", "dirs", - "failure", "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)", diff --git a/Cargo.toml b/Cargo.toml index 7a4e3d4..1d704e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ bytes = "0.5.6" chacha20 = "0.8.1" clap = { version = "2.33", features = ["yaml"] } dirs = "2.0" -failure = "0.1.8" futures = "0.3" hmac = { version = "0.12.0", features = ["std"]} hyper = { version = "0.14", features = ["full"] } diff --git a/src/config.rs b/src/config.rs index 99f5e9f..d6928e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,3 @@ -use crate::error::{self, Result}; use crate::secp::SecretKey; use core::num::NonZeroU32; @@ -11,6 +10,8 @@ use std::fs::File; use std::io::prelude::*; use std::net::SocketAddr; use std::path::PathBuf; +use std::result::Result; +use thiserror::Error; const GRIN_HOME: &str = ".grin"; const NODE_API_SECRET_FILE_NAME: &str = ".api_secret"; @@ -45,6 +46,27 @@ impl ServerConfig { } } +/// Error types for saving or loading configs +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("Error while writing config to file: {0:?}")] + FileWriteError(std::io::Error), + #[error("Error while encoding config as toml: {0:?}")] + EncodingError(toml::ser::Error), + #[error("Error while decoding toml config: {0:?}")] + DecodingError(toml::de::Error), + #[error("{0} not valid hex")] + InvalidHex(String), + #[error("Error decrypting seed: {0:?}")] + DecryptionError(ring::error::Unspecified), + #[error("Decrypted server key is invalid")] + InvalidServerKey, + #[error( + "Unable to read server config. Perform init-config or pass in config path.\nError: {0:?}" + )] + ReadConfigError(std::io::Error), +} + /// Encrypted server key, for storing on disk and decrypting with a password. /// Includes a salt used by key derivation and a nonce used when sealing the encrypted data. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -58,10 +80,7 @@ impl EncryptedServerKey { /// Generates a random salt for pbkdf2 key derivation and a random nonce for aead sealing. /// Then derives an encryption key from the password and salt. Finally, it encrypts and seals /// the server key with chacha20-poly1305 using the derived key and random nonce. - pub fn from_secret_key( - server_key: &SecretKey, - password: &ZeroingString, - ) -> Result { + pub fn from_secret_key(server_key: &SecretKey, password: &ZeroingString) -> EncryptedServerKey { let salt: [u8; 8] = thread_rng().gen(); let password = password.as_bytes(); let mut key = [0; 32]; @@ -85,28 +104,23 @@ impl EncryptedServerKey { aad, &mut enc_bytes, ) - .map_err(|e| { - error::ErrorKind::SaveConfigError(format!( - "Failure while encrypting server key: {}", - e - )) - })?; + .unwrap(); - Ok(EncryptedServerKey { + EncryptedServerKey { encrypted_key: enc_bytes.to_hex(), salt: salt.to_hex(), nonce: nonce.to_hex(), - }) + } } /// Decrypt the server secret key using the provided password. - pub fn decrypt(&self, password: &str) -> Result { + pub fn decrypt(&self, password: &str) -> Result { let mut encrypted_seed = grin_util::from_hex(&self.encrypted_key.clone()) - .map_err(|_| error::ErrorKind::LoadConfigError("Seed not valid hex".to_string()))?; + .map_err(|_| ConfigError::InvalidHex("Seed".to_string()))?; let salt = grin_util::from_hex(&self.salt.clone()) - .map_err(|_| error::ErrorKind::LoadConfigError("Salt not valid hex".to_string()))?; + .map_err(|_| ConfigError::InvalidHex("Salt".to_string()))?; let nonce = grin_util::from_hex(&self.nonce.clone()) - .map_err(|_| error::ErrorKind::LoadConfigError("Nonce not valid hex".to_string()))?; + .map_err(|_| ConfigError::InvalidHex("Nonce".to_string()))?; let password = password.as_bytes(); let mut key = [0; 32]; pbkdf2::derive( @@ -128,18 +142,15 @@ impl EncryptedServerKey { aad, &mut encrypted_seed, ) - .map_err(|e| { - error::ErrorKind::LoadConfigError(format!("Error decrypting seed: {}", e)) - })?; + .map_err(|e| ConfigError::DecryptionError(e))?; for _ in 0..aead::AES_256_GCM.tag_len() { encrypted_seed.pop(); } let secp = secp256k1zkp::Secp256k1::new(); - let decrypted = SecretKey::from_slice(&secp, &encrypted_seed).map_err(|_| { - error::ErrorKind::LoadConfigError("Decrypted key not valid".to_string()) - })?; + let decrypted = SecretKey::from_slice(&secp, &encrypted_seed) + .map_err(|_| ConfigError::InvalidServerKey)?; Ok(decrypted) } } @@ -163,8 +174,8 @@ pub fn write_config( config_path: &PathBuf, server_config: &ServerConfig, password: &ZeroingString, -) -> Result<()> { - let encrypted = EncryptedServerKey::from_secret_key(&server_config.key, &password)?; +) -> Result<(), ConfigError> { + let encrypted = EncryptedServerKey::from_secret_key(&server_config.key, &password); let raw_config = RawConfig { encrypted_key: encrypted.encrypted_key, @@ -177,28 +188,25 @@ pub fn write_config( wallet_owner_url: server_config.wallet_owner_url, wallet_owner_secret_path: server_config.wallet_owner_secret_path.clone(), }; - let encoded: String = toml::to_string(&raw_config).map_err(|e| { - error::ErrorKind::SaveConfigError(format!("Error while encoding config as toml: {}", e)) - })?; + let encoded: String = + toml::to_string(&raw_config).map_err(|e| ConfigError::EncodingError(e))?; - let mut file = File::create(config_path)?; - file.write_all(encoded.as_bytes()).map_err(|e| { - error::ErrorKind::SaveConfigError(format!("Error while writing config to file: {}", e)) - })?; + let mut file = File::create(config_path).map_err(|e| ConfigError::FileWriteError(e))?; + file.write_all(encoded.as_bytes()) + .map_err(|e| ConfigError::FileWriteError(e))?; Ok(()) } /// Reads the server config from the config_path given and decrypts it with the provided password. -pub fn load_config(config_path: &PathBuf, password: &ZeroingString) -> Result { - let contents = std::fs::read_to_string(config_path).map_err(|e| { - error::ErrorKind::LoadConfigError(format!( - "Unable to read server config. Perform init-config or pass in config path.\nError: {}", - e - )) - })?; +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| error::ErrorKind::LoadConfigError(e.to_string()))?; + toml::from_str(&contents).map_err(|e| ConfigError::DecodingError(e))?; let encrypted_key = EncryptedServerKey { encrypted_key: raw_config.encrypted_key, @@ -261,7 +269,7 @@ mod tests { fn server_key_encrypt() { let password = ZeroingString::from("password"); let server_key = secp::random_secret(); - let mut enc_key = EncryptedServerKey::from_secret_key(&server_key, &password).unwrap(); + let mut enc_key = EncryptedServerKey::from_secret_key(&server_key, &password); let decrypted_key = enc_key.decrypt(&password).unwrap(); assert_eq!(server_key, decrypted_key); diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index fe54f7a..0000000 --- a/src/error.rs +++ /dev/null @@ -1,161 +0,0 @@ -use failure::{self, Context, Fail}; -use grin_wallet_libwallet as libwallet; -use std::fmt::{self, Display}; -use std::io; - -/// MWixnet error definition -#[derive(Debug)] -pub struct Error { - inner: Context, -} - -pub type Result = std::result::Result; -pub type StdResult = std::result::Result; - -/// MWixnet error types -#[derive(Clone, Debug, Eq, Fail, PartialEq)] -pub enum ErrorKind { - /// Error from secp256k1-zkp library - #[fail(display = "Secp Error")] - SecpError, - /// Invalid key length for MAC initialization - #[fail(display = "InvalidKeyLength")] - InvalidKeyLength, - /// Wraps an io error produced when reading or writing - #[fail(display = "IO Error: {}", _0)] - IOErr(String, io::ErrorKind), - /// Data wasn't in a consumable format - #[fail(display = "CorruptedData")] - CorruptedData, - /// Error from grin's api crate - #[fail(display = "GRIN API Error: {}", _0)] - GrinApiError(String), - /// Error from grin core - #[fail(display = "GRIN Core Error: {}", _0)] - GrinCoreError(String), - /// Error from grin-wallet's libwallet - #[fail(display = "libwallet error: {}", _0)] - LibWalletError(String), - /// Error from serde-json - #[fail(display = "serde json error: {}", _0)] - SerdeJsonError(String), - /// Error from invalid signature - #[fail(display = "invalid signature")] - InvalidSigError, - /// Error while saving config - #[fail(display = "save config error: {}", _0)] - SaveConfigError(String), - /// Error while loading config - #[fail(display = "load config error: {}", _0)] - LoadConfigError(String), -} - -impl std::error::Error for Error {} - -impl From for Error { - fn from(e: io::Error) -> Error { - ErrorKind::IOErr(format!("{}", e), e.kind()).into() - } -} - -impl From for Error { - fn from(e: io::ErrorKind) -> Error { - ErrorKind::IOErr(format!("{}", io::Error::from(e)), e).into() - } -} - -impl From for Error { - fn from(e: grin_core::ser::Error) -> Error { - Error { - inner: Context::new(ErrorKind::GrinCoreError(e.to_string())), - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Display::fmt(&self.inner, f) - } -} - -impl Error { - pub fn new(kind: ErrorKind) -> Error { - Error { - inner: Context::new(kind), - } - } - - pub fn message(&self) -> String { - format!("{}", self).into() - } -} - -impl From for Error { - fn from(kind: ErrorKind) -> Error { - Error { - inner: Context::new(kind), - } - } -} - -impl From> for Error { - fn from(inner: Context) -> Error { - Error { inner } - } -} - -impl From for Error { - fn from(_error: secp256k1zkp::Error) -> Error { - Error { - inner: Context::new(ErrorKind::SecpError), - } - } -} - -impl From for Error { - fn from(_error: hmac::digest::InvalidLength) -> Error { - Error { - inner: Context::new(ErrorKind::InvalidKeyLength), - } - } -} - -impl From for Error { - fn from(e: grin_api::Error) -> Error { - Error { - inner: Context::new(ErrorKind::GrinApiError(e.to_string())), - } - } -} - -impl From for Error { - fn from(e: grin_api::json_rpc::Error) -> Error { - Error { - inner: Context::new(ErrorKind::GrinApiError(e.to_string())), - } - } -} - -impl From for Error { - fn from(e: grin_core::core::transaction::Error) -> Error { - Error { - inner: Context::new(ErrorKind::GrinCoreError(e.to_string())), - } - } -} - -impl From for Error { - fn from(e: libwallet::Error) -> Error { - Error { - inner: Context::new(ErrorKind::LibWalletError(e.to_string())), - } - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Error { - Error { - inner: Context::new(ErrorKind::SerdeJsonError(e.to_string())), - } - } -} diff --git a/src/main.rs b/src/main.rs index f68e400..681f04d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ use tokio::runtime::Runtime; extern crate clap; mod config; -mod error; mod node; mod onion; mod rpc; diff --git a/src/node.rs b/src/node.rs index a32ea22..c4941cc 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,4 +1,3 @@ -use crate::error::{ErrorKind, Result}; use crate::secp::Commitment; use grin_api::client; @@ -11,20 +10,32 @@ use grin_util::ToHex; use serde_json::json; use std::net::SocketAddr; use std::sync::Arc; +use thiserror::Error; pub trait GrinNode: Send + Sync { /// Retrieves the unspent output with a matching commitment - fn get_utxo(&self, output_commit: &Commitment) -> Result>; + fn get_utxo(&self, output_commit: &Commitment) -> Result, NodeError>; /// Gets the height of the chain tip - fn get_chain_height(&self) -> Result; + fn get_chain_height(&self) -> Result; /// Posts a transaction to the grin node - fn post_tx(&self, tx: &Transaction) -> Result<()>; + fn post_tx(&self, tx: &Transaction) -> Result<(), NodeError>; +} + +/// Error types for interacting with nodes +#[derive(Error, Debug)] +pub enum NodeError { + #[error("Error decoding JSON response: {0:?}")] + DecodeResponseError(serde_json::Error), + #[error("JSON-RPC API communication error: {0:?}")] + ApiCommError(grin_api::Error), + #[error("Error decoding JSON-RPC response: {0:?}")] + ResponseParseError(grin_api::json_rpc::Error), } /// Checks if a commitment is in the UTXO set -pub fn is_unspent(node: &Arc, commit: &Commitment) -> Result { +pub fn is_unspent(node: &Arc, commit: &Commitment) -> Result { let utxo = node.get_utxo(&commit)?; Ok(utxo.is_some()) } @@ -34,7 +45,7 @@ pub fn is_spendable( node: &Arc, output_commit: &Commitment, next_block_height: u64, -) -> Result { +) -> Result { let output = node.get_utxo(&output_commit)?; if let Some(out) = output { let is_coinbase = match out.output_type { @@ -59,7 +70,10 @@ pub fn is_spendable( } /// Builds an input for an unspent output commitment -pub fn build_input(node: &Arc, output_commit: &Commitment) -> Result> { +pub fn build_input( + node: &Arc, + output_commit: &Commitment, +) -> Result, NodeError> { let output = node.get_utxo(&output_commit)?; if let Some(out) = output { @@ -96,18 +110,22 @@ impl HttpGrinNode { &self, method: &str, params: &serde_json::Value, - ) -> Result { + ) -> Result { let url = format!("http://{}{}", self.node_url, ENDPOINT); let req = build_request(method, params); let res = - client::post::(url.as_str(), self.node_api_secret.clone(), &req)?; - let parsed = res.clone().into_result()?; + client::post::(url.as_str(), self.node_api_secret.clone(), &req) + .map_err(NodeError::ApiCommError)?; + let parsed = res + .clone() + .into_result() + .map_err(NodeError::ResponseParseError)?; Ok(parsed) } } impl GrinNode for HttpGrinNode { - fn get_utxo(&self, output_commit: &Commitment) -> Result> { + fn get_utxo(&self, output_commit: &Commitment) -> Result, NodeError> { let commits: Vec = vec![output_commit.to_hex()]; let start_height: Option = None; let end_height: Option = None; @@ -129,17 +147,17 @@ impl GrinNode for HttpGrinNode { Ok(Some(outputs[0].clone())) } - fn get_chain_height(&self) -> Result { + fn get_chain_height(&self) -> Result { let params = json!([]); let tip_json = self.send_json_request::("get_tip", ¶ms)?; - let tip: Result = serde_json::from_value(tip_json["Ok"].clone()) - .map_err(|e| ErrorKind::SerdeJsonError(e.to_string()).into()); + let tip = serde_json::from_value::(tip_json["Ok"].clone()) + .map_err(NodeError::DecodeResponseError)?; - Ok(tip?.height) + Ok(tip.height) } - fn post_tx(&self, tx: &Transaction) -> Result<()> { + fn post_tx(&self, tx: &Transaction) -> Result<(), NodeError> { let params = json!([tx, true]); self.send_json_request::("push_transaction", ¶ms)?; Ok(()) @@ -148,8 +166,7 @@ impl GrinNode for HttpGrinNode { #[cfg(test)] pub mod mock { - use super::GrinNode; - use crate::error::Result; + use super::{GrinNode, NodeError}; use crate::secp::Commitment; use grin_api::{OutputPrintable, OutputType}; @@ -198,7 +215,10 @@ pub mod mock { } impl GrinNode for MockGrinNode { - fn get_utxo(&self, output_commit: &Commitment) -> Result> { + fn get_utxo( + &self, + output_commit: &Commitment, + ) -> Result, NodeError> { if let Some(utxo) = self.utxos.get(&output_commit) { return Ok(Some(utxo.clone())); } @@ -206,11 +226,11 @@ pub mod mock { Ok(None) } - fn get_chain_height(&self) -> Result { + fn get_chain_height(&self) -> Result { Ok(100) } - fn post_tx(&self, tx: &Transaction) -> Result<()> { + fn post_tx(&self, tx: &Transaction) -> Result<(), NodeError> { let mut write = self.txns_posted.write().unwrap(); write.push(tx.clone()); Ok(()) diff --git a/src/onion.rs b/src/onion.rs index cc59280..935778f 100644 --- a/src/onion.rs +++ b/src/onion.rs @@ -1,16 +1,19 @@ -use crate::error::Result; use crate::secp::{self, Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret}; use crate::types::Payload; +use crate::onion::OnionError::{InvalidKeyLength, SerializationError}; use chacha20::cipher::{NewCipher, StreamCipher}; use chacha20::{ChaCha20, Key, Nonce}; use grin_core::ser::{self, ProtocolVersion, Writeable, Writer}; use grin_util::{self, ToHex}; +use hmac::digest::InvalidLength; use hmac::{Hmac, Mac}; use serde::ser::SerializeStruct; use serde::Deserialize; use sha2::{Digest, Sha256}; use std::fmt; +use std::result::Result; +use thiserror::Error; type HmacSha256 = Hmac; type RawBytes = Vec; @@ -27,14 +30,14 @@ pub struct Onion { } impl Onion { - pub fn serialize(&self) -> Result> { + pub fn serialize(&self) -> Result, ser::Error> { let mut vec = vec![]; ser::serialize_default(&mut vec, &self)?; Ok(vec) } /// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload - pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<(Payload, Onion)> { + pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<(Payload, Onion), OnionError> { let secp = Secp256k1::new(); let shared_secret = SharedSecret::new(&secp, &self.ephemeral_pubkey, &secret_key); @@ -42,7 +45,8 @@ impl Onion { let mut decrypted_bytes = self.enc_payloads[0].clone(); cipher.apply_keystream(&mut decrypted_bytes); - let decrypted_payload = Payload::deserialize(&decrypted_bytes)?; + let decrypted_payload = Payload::deserialize(&decrypted_bytes) + .map_err(|e| OnionError::DeserializationError(e))?; let enc_payloads: Vec = self .enc_payloads @@ -59,11 +63,15 @@ impl Onion { let blinding_factor = calc_blinding_factor(&shared_secret, &self.ephemeral_pubkey)?; let mut ephemeral_pubkey = self.ephemeral_pubkey.clone(); - ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?; + ephemeral_pubkey + .mul_assign(&secp, &blinding_factor) + .map_err(|e| OnionError::CalcPubKeyError(e))?; let mut commitment = self.commit.clone(); - commitment = secp::add_excess(&commitment, &decrypted_payload.excess)?; - commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())?; + commitment = secp::add_excess(&commitment, &decrypted_payload.excess) + .map_err(|e| OnionError::CalcCommitError(e))?; + commitment = secp::sub_value(&commitment, decrypted_payload.fee.into()) + .map_err(|e| OnionError::CalcCommitError(e))?; let peeled_onion = Onion { ephemeral_pubkey, @@ -77,7 +85,7 @@ impl Onion { fn calc_blinding_factor( shared_secret: &SharedSecret, ephemeral_pubkey: &PublicKey, -) -> Result { +) -> Result { let serialized_pubkey = ser::ser_vec(&ephemeral_pubkey, ProtocolVersion::local())?; let mut hasher = Sha256::default(); @@ -85,11 +93,12 @@ fn calc_blinding_factor( hasher.update(&shared_secret[0..32]); let secp = Secp256k1::new(); - let blind = SecretKey::from_slice(&secp, &hasher.finalize())?; + let blind = SecretKey::from_slice(&secp, &hasher.finalize()) + .map_err(|e| OnionError::CalcBlindError(e))?; Ok(blind) } -fn new_stream_cipher(shared_secret: &SharedSecret) -> Result { +fn new_stream_cipher(shared_secret: &SharedSecret) -> Result { let mut mu_hmac = HmacSha256::new_from_slice(b"MWIXNET")?; mu_hmac.update(&shared_secret[0..32]); let mukey = mu_hmac.finalize().into_bytes(); @@ -101,7 +110,7 @@ fn new_stream_cipher(shared_secret: &SharedSecret) -> Result { } impl Writeable for Onion { - fn write(&self, writer: &mut W) -> std::result::Result<(), ser::Error> { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { self.ephemeral_pubkey.write(writer)?; writer.write_fixed_bytes(&self.commit)?; writer.write_u64(self.enc_payloads.len() as u64)?; @@ -114,7 +123,7 @@ impl Writeable for Onion { } impl serde::ser::Serialize for Onion { - fn serialize(&self, serializer: S) -> std::result::Result + fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { @@ -134,7 +143,7 @@ impl serde::ser::Serialize for Onion { } impl<'de> serde::de::Deserialize<'de> for Onion { - fn deserialize(deserializer: D) -> std::result::Result + fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { @@ -155,7 +164,7 @@ impl<'de> serde::de::Deserialize<'de> for Onion { formatter.write_str("an Onion") } - fn visit_map(self, mut map: A) -> std::result::Result + fn visit_map(self, mut map: A) -> Result where A: serde::de::MapAccess<'de>, { @@ -207,10 +216,38 @@ impl<'de> serde::de::Deserialize<'de> for Onion { } } +/// Error types for creating and peeling Onions +#[derive(Clone, Error, Debug, PartialEq)] +pub enum OnionError { + #[error("Invalid key length for MAC initialization")] + InvalidKeyLength, + #[error("Serialization error occurred: {0:?}")] + SerializationError(ser::Error), + #[error("Deserialization error occurred: {0:?}")] + DeserializationError(ser::Error), + #[error("Error calculating blinding factor: {0:?}")] + CalcBlindError(secp256k1zkp::Error), + #[error("Error calculating ephemeral pubkey: {0:?}")] + CalcPubKeyError(secp256k1zkp::Error), + #[error("Error calculating commitment: {0:?}")] + CalcCommitError(secp256k1zkp::Error), +} + +impl From for OnionError { + fn from(_err: InvalidLength) -> OnionError { + InvalidKeyLength + } +} + +impl From for OnionError { + fn from(err: ser::Error) -> OnionError { + SerializationError(err) + } +} + #[cfg(test)] pub mod test_util { - use super::{Onion, RawBytes}; - use crate::error::Result; + use super::{Onion, OnionError, RawBytes}; use crate::secp::{Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret}; use crate::types::Payload; @@ -224,7 +261,7 @@ pub mod test_util { } /// Create an Onion for the Commitment, encrypting the payload for each hop - pub fn create_onion(commitment: &Commitment, hops: &Vec) -> Result { + pub fn create_onion(commitment: &Commitment, hops: &Vec) -> Result { let secp = Secp256k1::new(); let session_key = secp::random_secret(); let mut ephemeral_key = session_key.clone(); @@ -234,12 +271,15 @@ pub mod test_util { for hop in hops { let shared_secret = SharedSecret::new(&secp, &hop.pubkey, &ephemeral_key); - let ephemeral_pubkey = PublicKey::from_secret_key(&secp, &ephemeral_key)?; + let ephemeral_pubkey = PublicKey::from_secret_key(&secp, &ephemeral_key) + .map_err(|e| OnionError::CalcPubKeyError(e))?; let blinding_factor = super::calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?; shared_secrets.push(shared_secret); enc_payloads.push(hop.payload.serialize()?); - ephemeral_key.mul_assign(&secp, &blinding_factor)?; + ephemeral_key + .mul_assign(&secp, &blinding_factor) + .map_err(|e| OnionError::CalcPubKeyError(e))?; } for i in (0..shared_secrets.len()).rev() { @@ -250,7 +290,8 @@ pub mod test_util { } let onion = Onion { - ephemeral_pubkey: PublicKey::from_secret_key(&secp, &session_key)?, + ephemeral_pubkey: PublicKey::from_secret_key(&secp, &session_key) + .map_err(|e| OnionError::CalcPubKeyError(e))?, commit: commitment.clone(), enc_payloads, }; @@ -258,12 +299,17 @@ pub mod test_util { } /// Calculates the expected next ephemeral pubkey after peeling a layer off of the Onion. - pub fn next_ephemeral_pubkey(onion: &Onion, server_key: &SecretKey) -> Result { + pub fn next_ephemeral_pubkey( + onion: &Onion, + server_key: &SecretKey, + ) -> Result { let secp = Secp256k1::new(); let mut ephemeral_pubkey = onion.ephemeral_pubkey.clone(); let shared_secret = SharedSecret::new(&secp, &ephemeral_pubkey, &server_key); let blinding_factor = super::calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?; - ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?; + ephemeral_pubkey + .mul_assign(&secp, &blinding_factor) + .map_err(|e| OnionError::CalcPubKeyError(e))?; Ok(ephemeral_pubkey) } } diff --git a/src/rpc.rs b/src/rpc.rs index eac079b..2d09bca 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -6,7 +6,7 @@ use crate::server::{Server, ServerImpl, SwapError}; use crate::wallet::Wallet; use grin_util::StopState; -use jsonrpc_core::{Result, Value}; +use jsonrpc_core::Value; use jsonrpc_derive::rpc; use jsonrpc_http_server::jsonrpc_core::*; use jsonrpc_http_server::*; @@ -25,11 +25,11 @@ pub struct SwapReq { #[rpc(server)] pub trait API { #[rpc(name = "swap")] - fn swap(&self, swap: SwapReq) -> Result; + fn swap(&self, swap: SwapReq) -> jsonrpc_core::Result; // milestone 3: Used by mwixnet coinswap servers to communicate with each other - // fn derive_outputs(&self, entries: Vec) -> Result; - // fn derive_kernel(&self, tx: Tx) -> Result; + // fn derive_outputs(&self, entries: Vec) -> jsonrpc_core::Result; + // fn derive_kernel(&self, tx: Tx) -> jsonrpc_core::Result; } #[derive(Clone)] @@ -73,7 +73,7 @@ impl From for Error { impl API for RPCServer { /// Implements the 'swap' API - fn swap(&self, swap: SwapReq) -> Result { + fn swap(&self, swap: SwapReq) -> jsonrpc_core::Result { self.server .lock() .unwrap() diff --git a/src/secp.rs b/src/secp.rs index 8196c4c..cff866c 100644 --- a/src/secp.rs +++ b/src/secp.rs @@ -8,12 +8,11 @@ pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY}; pub use secp256k1zkp::pedersen::{Commitment, RangeProof}; pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature}; -use crate::error::{Error, ErrorKind, Result}; - use blake2::blake2b::Blake2b; use byteorder::{BigEndian, ByteOrder}; use grin_core::ser::{self, Readable, Reader, Writeable, Writer}; use secp256k1zkp::rand::thread_rng; +use thiserror::Error; /// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys #[derive(Clone)] @@ -23,6 +22,21 @@ pub struct ComSignature { 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 { @@ -33,7 +47,11 @@ impl ComSignature { } #[allow(dead_code)] - pub fn sign(amount: u64, blind: &SecretKey, msg: &Vec) -> Result { + pub fn sign( + amount: u64, + blind: &SecretKey, + msg: &Vec, + ) -> Result { let secp = Secp256k1::with_caps(ContextFlag::Commit); let mut amt_bytes = [0; 32]; @@ -62,7 +80,7 @@ impl ComSignature { } #[allow(non_snake_case)] - pub fn verify(&self, commit: &Commitment, msg: &Vec) -> Result<()> { + 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())?; @@ -75,7 +93,7 @@ impl ComSignature { let S2 = secp.commit_sum(commits, Vec::new())?; if S1 != S2 { - return Err(Error::new(ErrorKind::InvalidSigError)); + return Err(ComSigError::InvalidSig); } Ok(()) @@ -86,7 +104,7 @@ impl ComSignature { commit: &Commitment, nonce_commit: &Commitment, msg: &Vec, - ) -> Result { + ) -> Result { let mut challenge_hasher = Blake2b::new(32); challenge_hasher.update(&commit.0); challenge_hasher.update(&nonce_commit.0); @@ -131,7 +149,7 @@ pub mod comsig_serde { #[allow(non_snake_case)] impl Readable for ComSignature { - fn read(reader: &mut R) -> std::result::Result { + fn read(reader: &mut R) -> Result { let R = Commitment::read(reader)?; let s = read_secret_key(reader)?; let t = read_secret_key(reader)?; @@ -140,7 +158,7 @@ impl Readable for ComSignature { } impl Writeable for ComSignature { - fn write(&self, writer: &mut W) -> std::result::Result<(), ser::Error> { + 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)?; @@ -155,7 +173,7 @@ pub fn random_secret() -> SecretKey { } /// Deserialize a SecretKey from a Reader -pub fn read_secret_key(reader: &mut R) -> std::result::Result { +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)?; @@ -163,14 +181,17 @@ pub fn read_secret_key(reader: &mut R) -> std::result::Result Result { +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 { +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())?; @@ -180,7 +201,7 @@ pub fn add_excess(commitment: &Commitment, excess: &SecretKey) -> Result Result { +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()])?; @@ -188,7 +209,7 @@ pub fn sub_value(commitment: &Commitment, value: u64) -> Result { } /// Signs the message with the provided SecretKey -pub fn sign(sk: &SecretKey, msg: &Message) -> Result { +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)?; @@ -197,15 +218,14 @@ pub fn sign(sk: &SecretKey, msg: &Message) -> Result { #[cfg(test)] mod tests { - use super::{ComSignature, ContextFlag, Secp256k1, SecretKey}; - use crate::error::Result; + 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<()> { + fn verify_comsig() -> Result<(), ComSigError> { let secp = Secp256k1::with_caps(ContextFlag::Commit); let amount = thread_rng().next_u64(); diff --git a/src/server.rs b/src/server.rs index ba20e9a..99dc49d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use crate::config::ServerConfig; use crate::node::{self, GrinNode}; -use crate::onion::Onion; +use crate::onion::{Onion, OnionError}; use crate::secp::{ComSignature, Commitment, RangeProof, Secp256k1, SecretKey}; use crate::wallet::{self, Wallet}; @@ -43,8 +43,8 @@ pub enum SwapError { CoinNotFound { commit: Commitment }, #[error("Output {commit:?} is already in the swap list.")] AlreadySwapped { commit: Commitment }, - #[error("Failed to peel onion layer: {0}")] - PeelOnionFailure(String), + #[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("{0}")] @@ -60,7 +60,7 @@ pub trait Server: Send + Sync { /// and assemble the coinswap transaction, posting the transaction to the configured node. /// /// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes. - fn execute_round(&self) -> crate::error::Result<()>; + fn execute_round(&self) -> Result<(), Box>; } /// The standard MWixnet server implementation @@ -126,7 +126,7 @@ impl Server for ServerImpl { let peeled = onion .peel_layer(&self.server_config.key) - .map_err(|e| SwapError::PeelOnionFailure(e.message()))?; + .map_err(|e| SwapError::PeelOnionFailure(e))?; // Verify the fee meets the minimum let fee: u64 = peeled.0.fee.into(); @@ -168,7 +168,7 @@ impl Server for ServerImpl { Ok(()) } - fn execute_round(&self) -> crate::error::Result<()> { + fn execute_round(&self) -> Result<(), Box> { let mut locked_state = self.submissions.lock().unwrap(); let next_block_height = self.node.get_chain_height()? + 1; @@ -258,7 +258,7 @@ pub mod mock { Ok(()) } - fn execute_round(&self) -> crate::error::Result<()> { + fn execute_round(&self) -> Result<(), Box> { Ok(()) } } diff --git a/src/types.rs b/src/types.rs index 9a586f1..3a1d8a5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use crate::error::{Result, StdResult}; use crate::secp::{self, RangeProof, SecretKey}; use grin_core::core::FeeFields; @@ -7,6 +6,7 @@ use serde::{Deserialize, Serialize}; const CURRENT_VERSION: u8 = 0; +// todo: Belongs in Onion #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Payload { pub excess: SecretKey, @@ -15,13 +15,13 @@ pub struct Payload { } impl Payload { - pub fn deserialize(bytes: &Vec) -> Result { + pub fn deserialize(bytes: &Vec) -> Result { let payload: Payload = ser::deserialize_default(&mut &bytes[..])?; Ok(payload) } #[cfg(test)] - pub fn serialize(&self) -> Result> { + pub fn serialize(&self) -> Result, ser::Error> { let mut vec = vec![]; ser::serialize_default(&mut vec, &self)?; Ok(vec) @@ -29,7 +29,7 @@ impl Payload { } impl Readable for Payload { - fn read(reader: &mut R) -> StdResult { + fn read(reader: &mut R) -> Result { let version = reader.read_u8()?; if version != CURRENT_VERSION { return Err(ser::Error::UnsupportedProtocolVersion); @@ -53,7 +53,7 @@ impl Readable for Payload { } impl Writeable for Payload { - fn write(&self, writer: &mut W) -> StdResult<(), ser::Error> { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { writer.write_u8(CURRENT_VERSION)?; writer.write_fixed_bytes(&self.excess)?; writer.write_u64(self.fee.into())?; diff --git a/src/wallet.rs b/src/wallet.rs index 1520812..4b68121 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,4 +1,3 @@ -use crate::error::{ErrorKind, Result}; use crate::secp; use grin_api::client; @@ -15,10 +14,38 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use std::net::SocketAddr; use std::sync::Arc; +use thiserror::Error; pub trait Wallet: Send + Sync { /// Builds an output for the wallet with the provided amount. - fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)>; + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output), WalletError>; +} + +/// 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:?}")] + DecryptResponseError(grin_wallet_libwallet::Error), + #[error("Error decoding JSON response: {0:?}")] + DecodeResponseError(serde_json::Error), + #[error("JSON-RPC API communication error: {0:?}")] + ApiCommError(grin_api::Error), + #[error("Error decoding JSON-RPC response: {0:?}")] + ResponseParseError(grin_api::json_rpc::Error), } /// Builds and verifies a 'Transaction' using the provided components. @@ -29,7 +56,7 @@ pub fn assemble_tx( fee_base: u64, total_fee: u64, excesses: &Vec, -) -> Result { +) -> Result { let secp = Secp256k1::with_caps(ContextFlag::Commit); let txn_inputs = Inputs::from(inputs.as_slice()); let mut txn_outputs = outputs.clone(); @@ -52,10 +79,8 @@ pub fn assemble_tx( let wallet_output = wallet.build_output(amount)?; txn_outputs.push(wallet_output.1); - let output_excess = wallet_output - .0 - .secret_key(&secp) - .map_err(|_| ErrorKind::CorruptedData)?; + let output_excess = SecretKey::from_slice(&secp, &wallet_output.0.as_ref()) + .map_err(WalletError::OutputBlindError)?; txn_excesses.push(output_excess); } @@ -63,16 +88,20 @@ pub fn assemble_tx( let offset = secp::random_secret(); // calculate kernel excess - let kern_excess = secp.blind_sum(txn_excesses, vec![offset.clone()])?; + 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).unwrap(), + fee: FeeFields::new(0, kernel_fee).map_err(WalletError::KernelFeeError)?, }); - let msg = kernel.msg_to_sign()?; - kernel.excess = secp::commit(0, &kern_excess)?; - kernel.excess_sig = secp::sign(&kern_excess, &msg)?; - kernel.verify()?; + 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]) @@ -106,7 +135,7 @@ impl HttpWallet { wallet_owner_url: &SocketAddr, wallet_owner_secret: &Option, wallet_pass: &ZeroingString, - ) -> Result { + ) -> Result { println!("Opening wallet at {}", wallet_owner_url); let shared_key = HttpWallet::init_secure_api(&wallet_owner_url, &wallet_owner_secret)?; @@ -134,10 +163,10 @@ impl HttpWallet { fn init_secure_api( wallet_owner_url: &SocketAddr, wallet_owner_secret: &Option, - ) -> Result { + ) -> Result { let secp = Secp256k1::new(); let ephemeral_sk = secp::random_secret(); - let ephemeral_pk = PublicKey::from_secret_key(&secp, &ephemeral_sk)?; + let ephemeral_pk = PublicKey::from_secret_key(&secp, &ephemeral_sk).unwrap(); let init_params = json!({ "ecdh_pubkey": ephemeral_pk.serialize_vec(&secp, true).to_hex() }); @@ -151,10 +180,10 @@ impl HttpWallet { let shared_key = { let mut shared_pubkey = response_pk.ecdh_pubkey.clone(); - shared_pubkey.mul_assign(&secp, &ephemeral_sk)?; + shared_pubkey.mul_assign(&secp, &ephemeral_sk).unwrap(); let x_coord = shared_pubkey.serialize_vec(&secp, true); - SecretKey::from_slice(&secp, &x_coord[1..])? + SecretKey::from_slice(&secp, &x_coord[1..]).unwrap() }; Ok(shared_key) @@ -166,7 +195,7 @@ impl HttpWallet { method: &str, params: &serde_json::Value, shared_key: &SecretKey, - ) -> Result { + ) -> Result { let url = format!("http://{}{}", wallet_owner_url, ENDPOINT); let req = json!({ "method": method, @@ -174,15 +203,21 @@ impl HttpWallet { "id": JsonId::IntId(1), "jsonrpc": "2.0", }); - let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key)?; + let enc_req = EncryptedRequest::from_json(&JsonId::IntId(1), &req, &shared_key) + .map_err(WalletError::EncryptRequestError)?; let res = client::post::( url.as_str(), wallet_owner_secret.clone(), &enc_req, - )?; - let decrypted = res.decrypt(&shared_key)?; - let response: Response = serde_json::from_value(decrypted.clone())?; - let parsed = serde_json::from_value(response.result.unwrap().get("Ok").unwrap().clone())?; + ) + .map_err(WalletError::ApiCommError)?; + let decrypted = res + .decrypt(&shared_key) + .map_err(WalletError::DecryptResponseError)?; + let response: Response = + serde_json::from_value(decrypted).map_err(WalletError::DecodeResponseError)?; + let ok = response.result.unwrap().get("Ok").unwrap().clone(); + let parsed = serde_json::from_value(ok).map_err(WalletError::DecodeResponseError)?; Ok(parsed) } @@ -191,12 +226,16 @@ impl HttpWallet { wallet_owner_secret: &Option, method: &str, params: &serde_json::Value, - ) -> Result { + ) -> Result { let url = format!("http://{}{}", wallet_owner_url, ENDPOINT); let req = build_request(method, params); let res = - client::post::(url.as_str(), wallet_owner_secret.clone(), &req)?; - let parsed = res.clone().into_result()?; + client::post::(url.as_str(), wallet_owner_secret.clone(), &req) + .map_err(WalletError::ApiCommError)?; + let parsed = res + .clone() + .into_result() + .map_err(WalletError::ResponseParseError)?; Ok(parsed) } } @@ -213,7 +252,7 @@ pub struct OutputWithBlind { impl Wallet for HttpWallet { /// Builds an 'Output' for the wallet using the 'build_output' RPC API. - fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)> { + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output), WalletError> { let req_json = json!({ "token": self.token.keychain_mask.clone().unwrap().0, "features": "Plain", @@ -232,8 +271,7 @@ impl Wallet for HttpWallet { #[cfg(test)] pub mod mock { - use super::Wallet; - use crate::error::Result; + use super::{Wallet, WalletError}; use crate::secp; use grin_core::core::{Output, OutputFeatures}; @@ -246,10 +284,10 @@ pub mod mock { impl Wallet for MockWallet { /// Builds an 'Output' for the wallet using the 'build_output' RPC API. - fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)> { + fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output), WalletError> { let secp = Secp256k1::new(); let blind = secp::random_secret(); - let commit = secp::commit(amount, &blind)?; + let commit = secp::commit(amount, &blind).unwrap(); let proof = secp.bullet_proof( amount, blind.clone(),