switch to thiserror for wallet

This commit is contained in:
scilio 2022-08-22 12:53:29 -04:00
parent fdbf25c965
commit 10faae9efe
3 changed files with 87 additions and 38 deletions

View file

@ -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)),
}
}
}

View file

@ -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)?;

View file

@ -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(),