mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-20 19:11:09 +03:00
switch to thiserror for wallet
This commit is contained in:
parent
fdbf25c965
commit
10faae9efe
3 changed files with 87 additions and 38 deletions
16
src/error.rs
16
src/error.rs
|
@ -12,7 +12,7 @@ pub struct Error {
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// MWixnet error types
|
/// MWixnet error types
|
||||||
#[derive(Clone, Debug, Eq, Fail, PartialEq)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
/// Error from secp256k1-zkp library
|
/// Error from secp256k1-zkp library
|
||||||
#[fail(display = "Secp Error")]
|
#[fail(display = "Secp Error")]
|
||||||
|
@ -20,9 +20,6 @@ pub enum ErrorKind {
|
||||||
/// Wraps an io error produced when reading or writing
|
/// Wraps an io error produced when reading or writing
|
||||||
#[fail(display = "IO Error: {}", _0)]
|
#[fail(display = "IO Error: {}", _0)]
|
||||||
IOErr(String, io::ErrorKind),
|
IOErr(String, io::ErrorKind),
|
||||||
/// Data wasn't in a consumable format
|
|
||||||
#[fail(display = "CorruptedData")]
|
|
||||||
CorruptedData,
|
|
||||||
/// Error from grin's api crate
|
/// Error from grin's api crate
|
||||||
#[fail(display = "GRIN API Error: {}", _0)]
|
#[fail(display = "GRIN API Error: {}", _0)]
|
||||||
GrinApiError(String),
|
GrinApiError(String),
|
||||||
|
@ -38,6 +35,9 @@ pub enum ErrorKind {
|
||||||
/// Error from invalid signature
|
/// Error from invalid signature
|
||||||
#[fail(display = "invalid signature")]
|
#[fail(display = "invalid signature")]
|
||||||
InvalidSigError,
|
InvalidSigError,
|
||||||
|
/// Wallet error
|
||||||
|
#[fail(display = "wallet error: {}", _0)]
|
||||||
|
WalletError(crate::wallet::WalletError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
|
@ -137,3 +137,11 @@ impl From<serde_json::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::wallet::WalletError> for Error {
|
||||||
|
fn from(e: crate::wallet::WalletError) -> Error {
|
||||||
|
Error {
|
||||||
|
inner: Context::new(ErrorKind::WalletError(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -163,7 +163,10 @@ 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
|
/// 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,
|
||||||
|
) -> std::result::Result<Commitment, secp256k1zkp::Error> {
|
||||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||||
let commit = secp.commit(value, blind.clone())?;
|
let commit = secp.commit(value, blind.clone())?;
|
||||||
Ok(commit)
|
Ok(commit)
|
||||||
|
@ -194,7 +197,7 @@ pub fn sub_value(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs the message with the provided SecretKey
|
/// Signs the message with the provided SecretKey
|
||||||
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> {
|
pub fn sign(sk: &SecretKey, msg: &Message) -> std::result::Result<Signature, secp256k1zkp::Error> {
|
||||||
let secp = Secp256k1::with_caps(ContextFlag::Full);
|
let secp = Secp256k1::with_caps(ContextFlag::Full);
|
||||||
let pubkey = PublicKey::from_secret_key(&secp, &sk)?;
|
let pubkey = PublicKey::from_secret_key(&secp, &sk)?;
|
||||||
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?;
|
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?;
|
||||||
|
|
102
src/wallet.rs
102
src/wallet.rs
|
@ -1,4 +1,3 @@
|
||||||
use crate::error::{ErrorKind, Result};
|
|
||||||
use crate::secp;
|
use crate::secp;
|
||||||
|
|
||||||
use grin_api::client;
|
use grin_api::client;
|
||||||
|
@ -15,10 +14,38 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub trait Wallet: Send + Sync {
|
pub trait Wallet: Send + Sync {
|
||||||
/// Builds an output for the wallet with the provided amount.
|
/// 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.
|
/// Builds and verifies a 'Transaction' using the provided components.
|
||||||
|
@ -29,7 +56,7 @@ pub fn assemble_tx(
|
||||||
fee_base: u64,
|
fee_base: u64,
|
||||||
total_fee: u64,
|
total_fee: u64,
|
||||||
excesses: &Vec<SecretKey>,
|
excesses: &Vec<SecretKey>,
|
||||||
) -> Result<Transaction> {
|
) -> Result<Transaction, WalletError> {
|
||||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||||
let txn_inputs = Inputs::from(inputs.as_slice());
|
let txn_inputs = Inputs::from(inputs.as_slice());
|
||||||
let mut txn_outputs = outputs.clone();
|
let mut txn_outputs = outputs.clone();
|
||||||
|
@ -52,10 +79,8 @@ pub fn assemble_tx(
|
||||||
let wallet_output = wallet.build_output(amount)?;
|
let wallet_output = wallet.build_output(amount)?;
|
||||||
txn_outputs.push(wallet_output.1);
|
txn_outputs.push(wallet_output.1);
|
||||||
|
|
||||||
let output_excess = wallet_output
|
let output_excess = SecretKey::from_slice(&secp, &wallet_output.0.as_ref())
|
||||||
.0
|
.map_err(WalletError::OutputBlindError)?;
|
||||||
.secret_key(&secp)
|
|
||||||
.map_err(|_| ErrorKind::CorruptedData)?;
|
|
||||||
txn_excesses.push(output_excess);
|
txn_excesses.push(output_excess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,16 +88,20 @@ pub fn assemble_tx(
|
||||||
let offset = secp::random_secret();
|
let offset = secp::random_secret();
|
||||||
|
|
||||||
// calculate kernel excess
|
// 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
|
// build and verify kernel
|
||||||
let mut kernel = TxKernel::with_features(KernelFeatures::Plain {
|
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()?;
|
let msg = kernel
|
||||||
kernel.excess = secp::commit(0, &kern_excess)?;
|
.msg_to_sign()
|
||||||
kernel.excess_sig = secp::sign(&kern_excess, &msg)?;
|
.map_err(WalletError::KernelSigMessageError)?;
|
||||||
kernel.verify()?;
|
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
|
// assemble the transaction
|
||||||
let tx = Transaction::new(txn_inputs, &txn_outputs, &[kernel])
|
let tx = Transaction::new(txn_inputs, &txn_outputs, &[kernel])
|
||||||
|
@ -106,7 +135,7 @@ impl HttpWallet {
|
||||||
wallet_owner_url: &SocketAddr,
|
wallet_owner_url: &SocketAddr,
|
||||||
wallet_owner_secret: &Option<String>,
|
wallet_owner_secret: &Option<String>,
|
||||||
wallet_pass: &ZeroingString,
|
wallet_pass: &ZeroingString,
|
||||||
) -> Result<HttpWallet> {
|
) -> Result<HttpWallet, WalletError> {
|
||||||
println!("Opening wallet at {}", wallet_owner_url);
|
println!("Opening wallet at {}", wallet_owner_url);
|
||||||
let shared_key = HttpWallet::init_secure_api(&wallet_owner_url, &wallet_owner_secret)?;
|
let shared_key = HttpWallet::init_secure_api(&wallet_owner_url, &wallet_owner_secret)?;
|
||||||
|
|
||||||
|
@ -134,10 +163,10 @@ impl HttpWallet {
|
||||||
fn init_secure_api(
|
fn init_secure_api(
|
||||||
wallet_owner_url: &SocketAddr,
|
wallet_owner_url: &SocketAddr,
|
||||||
wallet_owner_secret: &Option<String>,
|
wallet_owner_secret: &Option<String>,
|
||||||
) -> Result<SecretKey> {
|
) -> Result<SecretKey, WalletError> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let ephemeral_sk = secp::random_secret();
|
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!({
|
let init_params = json!({
|
||||||
"ecdh_pubkey": ephemeral_pk.serialize_vec(&secp, true).to_hex()
|
"ecdh_pubkey": ephemeral_pk.serialize_vec(&secp, true).to_hex()
|
||||||
});
|
});
|
||||||
|
@ -151,10 +180,10 @@ impl HttpWallet {
|
||||||
|
|
||||||
let shared_key = {
|
let shared_key = {
|
||||||
let mut shared_pubkey = response_pk.ecdh_pubkey.clone();
|
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);
|
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)
|
Ok(shared_key)
|
||||||
|
@ -166,7 +195,7 @@ impl HttpWallet {
|
||||||
method: &str,
|
method: &str,
|
||||||
params: &serde_json::Value,
|
params: &serde_json::Value,
|
||||||
shared_key: &SecretKey,
|
shared_key: &SecretKey,
|
||||||
) -> Result<D> {
|
) -> Result<D, WalletError> {
|
||||||
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
||||||
let req = json!({
|
let req = json!({
|
||||||
"method": method,
|
"method": method,
|
||||||
|
@ -174,15 +203,21 @@ impl HttpWallet {
|
||||||
"id": JsonId::IntId(1),
|
"id": JsonId::IntId(1),
|
||||||
"jsonrpc": "2.0",
|
"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>(
|
let res = client::post::<EncryptedRequest, EncryptedResponse>(
|
||||||
url.as_str(),
|
url.as_str(),
|
||||||
wallet_owner_secret.clone(),
|
wallet_owner_secret.clone(),
|
||||||
&enc_req,
|
&enc_req,
|
||||||
)?;
|
)
|
||||||
let decrypted = res.decrypt(&shared_key)?;
|
.map_err(WalletError::ApiCommError)?;
|
||||||
let response: Response = serde_json::from_value(decrypted.clone())?;
|
let decrypted = res
|
||||||
let parsed = serde_json::from_value(response.result.unwrap().get("Ok").unwrap().clone())?;
|
.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)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,12 +226,16 @@ impl HttpWallet {
|
||||||
wallet_owner_secret: &Option<String>,
|
wallet_owner_secret: &Option<String>,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: &serde_json::Value,
|
params: &serde_json::Value,
|
||||||
) -> Result<D> {
|
) -> Result<D, WalletError> {
|
||||||
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
let url = format!("http://{}{}", wallet_owner_url, ENDPOINT);
|
||||||
let req = build_request(method, params);
|
let req = build_request(method, params);
|
||||||
let res =
|
let res =
|
||||||
client::post::<Request, Response>(url.as_str(), wallet_owner_secret.clone(), &req)?;
|
client::post::<Request, Response>(url.as_str(), wallet_owner_secret.clone(), &req)
|
||||||
let parsed = res.clone().into_result()?;
|
.map_err(WalletError::ApiCommError)?;
|
||||||
|
let parsed = res
|
||||||
|
.clone()
|
||||||
|
.into_result()
|
||||||
|
.map_err(WalletError::ResponseParseError)?;
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +252,7 @@ pub struct OutputWithBlind {
|
||||||
|
|
||||||
impl Wallet for HttpWallet {
|
impl Wallet for HttpWallet {
|
||||||
/// Builds an 'Output' for the wallet using the 'build_output' RPC API.
|
/// 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!({
|
let req_json = json!({
|
||||||
"token": self.token.keychain_mask.clone().unwrap().0,
|
"token": self.token.keychain_mask.clone().unwrap().0,
|
||||||
"features": "Plain",
|
"features": "Plain",
|
||||||
|
@ -232,8 +271,7 @@ impl Wallet for HttpWallet {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod mock {
|
pub mod mock {
|
||||||
use super::Wallet;
|
use super::{Wallet, WalletError};
|
||||||
use crate::error::Result;
|
|
||||||
use crate::secp;
|
use crate::secp;
|
||||||
|
|
||||||
use grin_core::core::{Output, OutputFeatures};
|
use grin_core::core::{Output, OutputFeatures};
|
||||||
|
@ -246,10 +284,10 @@ pub mod mock {
|
||||||
|
|
||||||
impl Wallet for MockWallet {
|
impl Wallet for MockWallet {
|
||||||
/// Builds an 'Output' for the wallet using the 'build_output' RPC API.
|
/// 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 secp = Secp256k1::new();
|
||||||
let blind = secp::random_secret();
|
let blind = secp::random_secret();
|
||||||
let commit = secp::commit(amount, &blind)?;
|
let commit = secp::commit(amount, &blind).unwrap();
|
||||||
let proof = secp.bullet_proof(
|
let proof = secp.bullet_proof(
|
||||||
amount,
|
amount,
|
||||||
blind.clone(),
|
blind.clone(),
|
||||||
|
|
Loading…
Reference in a new issue