mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-20 19:11:09 +03:00
commit
7882eb62b3
12 changed files with 281 additions and 313 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<EncryptedServerKey> {
|
||||
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<SecretKey> {
|
||||
pub fn decrypt(&self, password: &str) -> Result<SecretKey, ConfigError> {
|
||||
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<ServerConfig> {
|
||||
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<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| 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);
|
||||
|
||||
|
|
161
src/error.rs
161
src/error.rs
|
@ -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<ErrorKind>,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type StdResult<T, E> = std::result::Result<T, E>;
|
||||
|
||||
/// 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<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Error {
|
||||
ErrorKind::IOErr(format!("{}", e), e.kind()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::ErrorKind> for Error {
|
||||
fn from(e: io::ErrorKind) -> Error {
|
||||
ErrorKind::IOErr(format!("{}", io::Error::from(e)), e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<grin_core::ser::Error> 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<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Error {
|
||||
Error {
|
||||
inner: Context::new(kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Context<ErrorKind>> for Error {
|
||||
fn from(inner: Context<ErrorKind>) -> Error {
|
||||
Error { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<secp256k1zkp::Error> for Error {
|
||||
fn from(_error: secp256k1zkp::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::SecpError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hmac::digest::InvalidLength> for Error {
|
||||
fn from(_error: hmac::digest::InvalidLength) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::InvalidKeyLength),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<grin_api::Error> for Error {
|
||||
fn from(e: grin_api::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::GrinApiError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<grin_api::json_rpc::Error> for Error {
|
||||
fn from(e: grin_api::json_rpc::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::GrinApiError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<grin_core::core::transaction::Error> for Error {
|
||||
fn from(e: grin_core::core::transaction::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::GrinCoreError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libwallet::Error> for Error {
|
||||
fn from(e: libwallet::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::LibWalletError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(e: serde_json::Error) -> Error {
|
||||
Error {
|
||||
inner: Context::new(ErrorKind::SerdeJsonError(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ use tokio::runtime::Runtime;
|
|||
extern crate clap;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod node;
|
||||
mod onion;
|
||||
mod rpc;
|
||||
|
|
62
src/node.rs
62
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<Option<OutputPrintable>>;
|
||||
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>, NodeError>;
|
||||
|
||||
/// Gets the height of the chain tip
|
||||
fn get_chain_height(&self) -> Result<u64>;
|
||||
fn get_chain_height(&self) -> Result<u64, NodeError>;
|
||||
|
||||
/// 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<dyn GrinNode>, commit: &Commitment) -> Result<bool> {
|
||||
pub fn is_unspent(node: &Arc<dyn GrinNode>, commit: &Commitment) -> Result<bool, NodeError> {
|
||||
let utxo = node.get_utxo(&commit)?;
|
||||
Ok(utxo.is_some())
|
||||
}
|
||||
|
@ -34,7 +45,7 @@ pub fn is_spendable(
|
|||
node: &Arc<dyn GrinNode>,
|
||||
output_commit: &Commitment,
|
||||
next_block_height: u64,
|
||||
) -> Result<bool> {
|
||||
) -> Result<bool, NodeError> {
|
||||
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<dyn GrinNode>, output_commit: &Commitment) -> Result<Option<Input>> {
|
||||
pub fn build_input(
|
||||
node: &Arc<dyn GrinNode>,
|
||||
output_commit: &Commitment,
|
||||
) -> Result<Option<Input>, 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<D> {
|
||||
) -> Result<D, NodeError> {
|
||||
let url = format!("http://{}{}", self.node_url, ENDPOINT);
|
||||
let req = build_request(method, params);
|
||||
let res =
|
||||
client::post::<Request, Response>(url.as_str(), self.node_api_secret.clone(), &req)?;
|
||||
let parsed = res.clone().into_result()?;
|
||||
client::post::<Request, Response>(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<Option<OutputPrintable>> {
|
||||
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>, NodeError> {
|
||||
let commits: Vec<String> = vec![output_commit.to_hex()];
|
||||
let start_height: Option<u64> = None;
|
||||
let end_height: Option<u64> = None;
|
||||
|
@ -129,17 +147,17 @@ impl GrinNode for HttpGrinNode {
|
|||
Ok(Some(outputs[0].clone()))
|
||||
}
|
||||
|
||||
fn get_chain_height(&self) -> Result<u64> {
|
||||
fn get_chain_height(&self) -> Result<u64, NodeError> {
|
||||
let params = json!([]);
|
||||
let tip_json = self.send_json_request::<serde_json::Value>("get_tip", ¶ms)?;
|
||||
|
||||
let tip: Result<Tip> = serde_json::from_value(tip_json["Ok"].clone())
|
||||
.map_err(|e| ErrorKind::SerdeJsonError(e.to_string()).into());
|
||||
let tip = serde_json::from_value::<Tip>(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::<serde_json::Value>("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<Option<OutputPrintable>> {
|
||||
fn get_utxo(
|
||||
&self,
|
||||
output_commit: &Commitment,
|
||||
) -> Result<Option<OutputPrintable>, 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<u64> {
|
||||
fn get_chain_height(&self) -> Result<u64, NodeError> {
|
||||
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(())
|
||||
|
|
90
src/onion.rs
90
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<Sha256>;
|
||||
type RawBytes = Vec<u8>;
|
||||
|
@ -27,14 +30,14 @@ pub struct Onion {
|
|||
}
|
||||
|
||||
impl Onion {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, 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<RawBytes> = 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<SecretKey> {
|
||||
) -> Result<SecretKey, OnionError> {
|
||||
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<ChaCha20> {
|
||||
fn new_stream_cipher(shared_secret: &SharedSecret) -> Result<ChaCha20, OnionError> {
|
||||
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<ChaCha20> {
|
|||
}
|
||||
|
||||
impl Writeable for Onion {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> std::result::Result<(), ser::Error> {
|
||||
fn write<W: Writer>(&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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
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<InvalidLength> for OnionError {
|
||||
fn from(_err: InvalidLength) -> OnionError {
|
||||
InvalidKeyLength
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ser::Error> 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<Hop>) -> Result<Onion> {
|
||||
pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, OnionError> {
|
||||
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<PublicKey> {
|
||||
pub fn next_ephemeral_pubkey(
|
||||
onion: &Onion,
|
||||
server_key: &SecretKey,
|
||||
) -> Result<PublicKey, OnionError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
10
src/rpc.rs
10
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<Value>;
|
||||
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>) -> Result<Value>;
|
||||
// fn derive_kernel(&self, tx: Tx) -> Result<Value>;
|
||||
// fn derive_outputs(&self, entries: Vec<Onion>) -> jsonrpc_core::Result<Value>;
|
||||
// fn derive_kernel(&self, tx: Tx) -> jsonrpc_core::Result<Value>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -73,7 +73,7 @@ impl From<SwapError> for Error {
|
|||
|
||||
impl API for RPCServer {
|
||||
/// Implements the 'swap' API
|
||||
fn swap(&self, swap: SwapReq) -> Result<Value> {
|
||||
fn swap(&self, swap: SwapReq) -> jsonrpc_core::Result<Value> {
|
||||
self.server
|
||||
.lock()
|
||||
.unwrap()
|
||||
|
|
52
src/secp.rs
52
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<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 {
|
||||
|
@ -33,7 +47,11 @@ impl ComSignature {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn sign(amount: u64, blind: &SecretKey, msg: &Vec<u8>) -> Result<ComSignature> {
|
||||
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];
|
||||
|
@ -62,7 +80,7 @@ impl ComSignature {
|
|||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify(&self, commit: &Commitment, msg: &Vec<u8>) -> Result<()> {
|
||||
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())?;
|
||||
|
@ -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<u8>,
|
||||
) -> Result<SecretKey> {
|
||||
) -> Result<SecretKey, ComSigError> {
|
||||
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<R: Reader>(reader: &mut R) -> std::result::Result<Self, ser::Error> {
|
||||
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)?;
|
||||
|
@ -140,7 +158,7 @@ impl Readable for ComSignature {
|
|||
}
|
||||
|
||||
impl Writeable for ComSignature {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> std::result::Result<(), ser::Error> {
|
||||
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)?;
|
||||
|
@ -155,7 +173,7 @@ pub fn random_secret() -> SecretKey {
|
|||
}
|
||||
|
||||
/// Deserialize a SecretKey from a Reader
|
||||
pub fn read_secret_key<R: Reader>(reader: &mut R) -> std::result::Result<SecretKey, ser::Error> {
|
||||
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)?;
|
||||
|
@ -163,14 +181,17 @@ pub fn read_secret_key<R: Reader>(reader: &mut R) -> std::result::Result<SecretK
|
|||
}
|
||||
|
||||
/// Build a Pedersen Commitment using the provided value and blinding factor
|
||||
pub fn commit(value: u64, blind: &SecretKey) -> Result<Commitment> {
|
||||
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> {
|
||||
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())?;
|
||||
|
||||
|
@ -180,7 +201,7 @@ pub fn add_excess(commitment: &Commitment, excess: &SecretKey) -> Result<Commitm
|
|||
}
|
||||
|
||||
/// Subtracts a value (v*H) from an existing commitment
|
||||
pub fn sub_value(commitment: &Commitment, value: u64) -> Result<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()])?;
|
||||
|
@ -188,7 +209,7 @@ pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment> {
|
|||
}
|
||||
|
||||
/// Signs the message with the provided SecretKey
|
||||
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> {
|
||||
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)?;
|
||||
|
@ -197,15 +218,14 @@ pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> {
|
|||
|
||||
#[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();
|
||||
|
|
|
@ -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<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
/// 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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
10
src/types.rs
10
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<u8>) -> Result<Payload> {
|
||||
pub fn deserialize(bytes: &Vec<u8>) -> Result<Payload, ser::Error> {
|
||||
let payload: Payload = ser::deserialize_default(&mut &bytes[..])?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, 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<R: Reader>(reader: &mut R) -> StdResult<Payload, ser::Error> {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<Payload, ser::Error> {
|
||||
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<W: Writer>(&self, writer: &mut W) -> StdResult<(), ser::Error> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
|
|
102
src/wallet.rs
102
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<SecretKey>,
|
||||
) -> Result<Transaction> {
|
||||
) -> Result<Transaction, WalletError> {
|
||||
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<String>,
|
||||
wallet_pass: &ZeroingString,
|
||||
) -> Result<HttpWallet> {
|
||||
) -> Result<HttpWallet, WalletError> {
|
||||
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<String>,
|
||||
) -> Result<SecretKey> {
|
||||
) -> Result<SecretKey, WalletError> {
|
||||
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<D> {
|
||||
) -> Result<D, WalletError> {
|
||||
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::<EncryptedRequest, EncryptedResponse>(
|
||||
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<String>,
|
||||
method: &str,
|
||||
params: &serde_json::Value,
|
||||
) -> Result<D> {
|
||||
) -> Result<D, WalletError> {
|
||||
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
||||
let req = build_request(method, params);
|
||||
let res =
|
||||
client::post::<Request, Response>(url.as_str(), wallet_owner_secret.clone(), &req)?;
|
||||
let parsed = res.clone().into_result()?;
|
||||
client::post::<Request, Response>(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(),
|
||||
|
|
Loading…
Reference in a new issue