milestone 2

This commit is contained in:
scilio 2022-04-26 23:13:44 -04:00
parent 68929fd493
commit 115e0d2022
13 changed files with 760 additions and 1222 deletions

10
Cargo.lock generated
View file

@ -1572,7 +1572,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_api" name = "grin_wallet_api"
version = "5.1.0-alpha.1" version = "5.1.0-alpha.1"
source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.12.3",
"chrono", "chrono",
@ -1596,7 +1596,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_config" name = "grin_wallet_config"
version = "5.1.0-alpha.1" version = "5.1.0-alpha.1"
source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213"
dependencies = [ dependencies = [
"dirs", "dirs",
"grin_wallet_util", "grin_wallet_util",
@ -1609,7 +1609,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_impls" name = "grin_wallet_impls"
version = "5.1.0-alpha.1" version = "5.1.0-alpha.1"
source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.12.3",
"blake2-rfc", "blake2-rfc",
@ -1643,7 +1643,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_libwallet" name = "grin_wallet_libwallet"
version = "5.1.0-alpha.1" version = "5.1.0-alpha.1"
source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213"
dependencies = [ dependencies = [
"age", "age",
"base64 0.9.3", "base64 0.9.3",
@ -1676,7 +1676,7 @@ dependencies = [
[[package]] [[package]]
name = "grin_wallet_util" name = "grin_wallet_util"
version = "5.1.0-alpha.1" version = "5.1.0-alpha.1"
source = "git+https://github.com/mimblewimble/grin-wallet#c424a0ed10b5b2565d9dbb5c222544ddd22c3167" source = "git+https://github.com/mimblewimble/grin-wallet?branch=master#cecb084753bb4cd545c11e4802f4e2f4b11b9213"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"ed25519-dalek", "ed25519-dalek",

View file

@ -35,6 +35,6 @@ grin_core = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alp
grin_chain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_chain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_keychain = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" }
grin_servers = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" } grin_servers = { git = "https://github.com/mimblewimble/grin", version = "5.2.0-alpha.1" }
grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" }
grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" }
grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", version = "5.1.0-alpha.1" } grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", branch = "master" }

View file

@ -11,12 +11,13 @@ The first CoinSwap server (n<sub>1</sub>) provides the `swap` API, publicly avai
**params:** **params:**
``` ```
[{ [{
"comsig": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", "comsig": "0835f4b8b9cd286c9e35475f575c3e4ae71ceb4ff36598504662627afd628a17d6ba7dedb1aa4c47f0fabad026b76fc86d06f3bef8d0621b8ac4601d4b1b98401586ca3374a401508f32049212478ae91cfa474dfaa5ef2c3dd559d5a292e02334",
"msg": "00010203",
"onion": { "onion": {
"commit": "0967593792bc958cd73848c0b948ecab2c6e996ab3c550d462fe41359e447b651f", "commit": "099a8922343f242dd3da29935ba5bbc7e38bf68eccfb8c96aec87aec0535199139",
"data": ["3719e5fba260c71a5a4bcf9d9caa58cd5dc49531388782fae7699c6fa6b30b09fe42"], "data":[
"pubkey": "020dd38a220280f14515f6901a3a366cb7b87630814e4b68b3189a32df964961e5" "758bc7339edfe8a1f117ef2ef02de58b33d99e820ffd2c21a3d108f4cbdadfbcbc061b705fc351ae2fb34a855bb64180fe8b4913ea13b3bf826773e4b293166cdad1fdf59cadd7d33f73a8f3fc0f339a849f9c505c573d8cc2f006082d8f5bf2c2c84c18d5873a821d9f60bcdd44cf0566d04d761a1eda005cd19ab0b1c593da4656dd2a09322fe1d725f61e8855c927844ec9e80b9260a58012f7c519c5ca3d9b192ab1104d30ac09fb844bd7f692214e5cf0f6cdf1477c20708c2f3ecb098dce6be661907593918840b0fe8eb5043996dace45f2fb66e8f1e2a732035b4e6447b8904f313badebcba99f75c003e297d8dd815915f534dfa7ed416af0c415b60d2a0186752af6af33b781f31fdd3016aeee3bd2e47743fe2ce391b3354b9036b56ec38ed7539adafbc96bef1dbaf354a805b03ac0df7a0d32cff91716926bce68c8ccebb607340f2ffe09c08a9c9fd282ea19b33c69107ed5c54d4872eb0ed83c38d7e07606722069d7709fb914e1e02ea23323f3ae9252902dbfa6f15bd83a3f64587c9ae23aaf96b2a95e1341da12a6e423cf95375184752e10c1dd1a599db74ac0c3d74ec270c589f6a3bdd0877eb986d9a58a8548b917e22bfb93a4a06c36d7cad8d4a8791a8d1e1dc683429b440b136c43ad2f664dafc5156b808050a3c4d28771877d3f1d3a9daa2585eae259aaa64745c6cd260f577e538e27be3c985db41b7c456b63c5b18d7d17420a277d4abc04ae892ceb26940b09fb322445846c14898f5f59305490b1338c56384cd0c7bf5950a0a403aec4d2c2f5e2378b5eb7b1e7fcdbd8d6cc547f3b5a372b22e50e37d858bb197392a10fb9e6e292d6ed6bd8eab1fef7f2d069b6250a0e3e597ccf9a062e04b68821f5c57328ddab775d141147b71c1764c911bad03d8b88e2e62034bc899395514ecab4dec8ab341ba114f0a4e5d1dcfa182396c0e4826ddee187b07bb524dfeaa5297f7a5465f99eaaaa37f082c787b94811feb15b57d68369e6a7e3761d"
],
"pubkey": "033946e6a495e7278027b38be3d500cfc23d3e0836f1b7e24513841437f316ccb0"
} }
}] }]
``` ```

View file

@ -15,6 +15,7 @@ use std::path::PathBuf;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ServerConfig { pub struct ServerConfig {
pub key: SecretKey, pub key: SecretKey,
pub interval_s: u32,
pub addr: SocketAddr, pub addr: SocketAddr,
pub grin_node_url: SocketAddr, pub grin_node_url: SocketAddr,
pub wallet_owner_url: SocketAddr, pub wallet_owner_url: SocketAddr,
@ -34,7 +35,6 @@ impl EncryptedServerKey {
password: &ZeroingString, password: &ZeroingString,
) -> Result<EncryptedServerKey> { ) -> Result<EncryptedServerKey> {
let salt: [u8; 8] = thread_rng().gen(); let salt: [u8; 8] = thread_rng().gen();
let nonce: [u8; 12] = thread_rng().gen();
let password = password.as_bytes(); let password = password.as_bytes();
let mut key = [0; 32]; let mut key = [0; 32];
ring::pbkdf2::derive( ring::pbkdf2::derive(
@ -49,6 +49,7 @@ impl EncryptedServerKey {
let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap();
let sealing_key: aead::LessSafeKey = aead::LessSafeKey::new(unbound_key); let sealing_key: aead::LessSafeKey = aead::LessSafeKey::new(unbound_key);
let nonce: [u8; 12] = thread_rng().gen();
let aad = aead::Aad::from(&[]); let aad = aead::Aad::from(&[]);
let _ = sealing_key.seal_in_place_append_tag( let _ = sealing_key.seal_in_place_append_tag(
aead::Nonce::assume_unique_for_key(nonce), aead::Nonce::assume_unique_for_key(nonce),
@ -106,6 +107,7 @@ struct RawConfig {
pub encrypted_key: String, pub encrypted_key: String,
pub salt: String, pub salt: String,
pub nonce: String, pub nonce: String,
pub interval_s: u32,
pub addr: SocketAddr, pub addr: SocketAddr,
pub grin_node_url: SocketAddr, pub grin_node_url: SocketAddr,
pub wallet_owner_url: SocketAddr, pub wallet_owner_url: SocketAddr,
@ -119,6 +121,7 @@ pub fn write_config(config_path: &PathBuf, server_config: &ServerConfig, passwo
encrypted_key: encrypted.encrypted_key, encrypted_key: encrypted.encrypted_key,
salt: encrypted.salt, salt: encrypted.salt,
nonce: encrypted.nonce, nonce: encrypted.nonce,
interval_s: server_config.interval_s,
addr: server_config.addr, addr: server_config.addr,
grin_node_url: server_config.grin_node_url, grin_node_url: server_config.grin_node_url,
wallet_owner_url: server_config.wallet_owner_url, wallet_owner_url: server_config.wallet_owner_url,
@ -146,6 +149,7 @@ pub fn load_config(config_path: &PathBuf, password: &ZeroingString) -> Result<Se
Ok(ServerConfig { Ok(ServerConfig {
key: secret_key, key: secret_key,
interval_s: raw_config.interval_s,
addr: raw_config.addr, addr: raw_config.addr,
grin_node_url: raw_config.grin_node_url, grin_node_url: raw_config.grin_node_url,
wallet_owner_url: raw_config.wallet_owner_url wallet_owner_url: raw_config.wallet_owner_url

View file

@ -10,13 +10,11 @@ pub struct Error {
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
pub type StdResult<T, E> = std::result::Result<T, E>;
#[derive(Clone, Debug, Eq, Fail, PartialEq)] #[derive(Clone, Debug, Eq, Fail, PartialEq)]
/// MWixnet error types /// MWixnet error types
pub enum ErrorKind { pub enum ErrorKind {
/// Unsupported payload version
#[fail(display = "Unsupported Payload Version")]
UnsupportedPayload,
/// Error from secp256k1-zkp library /// Error from secp256k1-zkp library
#[fail(display = "Secp Error")] #[fail(display = "Secp Error")]
SecpError, SecpError,
@ -29,23 +27,9 @@ pub enum ErrorKind {
String, String,
io::ErrorKind, io::ErrorKind,
), ),
/// Expected a given value that wasn't found
#[fail(display = "UnexpectedData")]
UnexpectedData {
/// What we wanted
expected: Vec<u8>,
/// What we got
received: Vec<u8>,
},
/// Data wasn't in a consumable format /// Data wasn't in a consumable format
#[fail(display = "CorruptedData")] #[fail(display = "CorruptedData")]
CorruptedData, CorruptedData,
/// Incorrect number of elements (when deserializing a vec via read_multi say).
#[fail(display = "CountError")]
CountError,
/// When asked to read too much data
#[fail(display = "TooLargeReadErr")]
TooLargeReadErr,
/// Error from grin's api crate /// Error from grin's api crate
#[fail(display = "GRIN API Error")] #[fail(display = "GRIN API Error")]
GrinApiError, GrinApiError,
@ -85,6 +69,14 @@ impl From<io::ErrorKind> for Error {
} }
} }
impl From<grin_core::ser::Error> for Error {
fn from(_e: grin_core::ser::Error) -> Error {
Error {
inner: Context::new(ErrorKind::GrinCoreError),
}
}
}
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.inner, f) Display::fmt(&self.inner, f)
@ -98,10 +90,6 @@ impl Error {
} }
} }
pub fn kind(&self) -> ErrorKind {
self.inner.get_context().clone()
}
pub fn message(&self) -> String { pub fn message(&self) -> String {
format!("{}", self).into() format!("{}", self).into()
} }

View file

@ -1,11 +1,13 @@
use config::ServerConfig; use config::ServerConfig;
use error::{Error, ErrorKind}; use error::{Error, ErrorKind};
use wallet::Wallet; use node::HttpGrinNode;
use wallet::HttpWallet;
use clap::App; use clap::App;
use grin_util::ZeroingString; use grin_util::{StopState, ZeroingString};
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@ -18,11 +20,12 @@ mod error;
mod node; mod node;
mod onion; mod onion;
mod secp; mod secp;
mod ser;
mod server; mod server;
mod types; mod types;
mod wallet; mod wallet;
const DEFAULT_INTERVAL: u32 = 12 * 60 * 60;
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> { fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let yml = load_yaml!("../mwixnet.yml"); let yml = load_yaml!("../mwixnet.yml");
let args = App::from_yaml(yml).get_matches(); let args = App::from_yaml(yml).get_matches();
@ -38,6 +41,7 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let password = args.value_of("pass").ok_or(Error::new(ErrorKind::LoadConfigError))?; let password = args.value_of("pass").ok_or(Error::new(ErrorKind::LoadConfigError))?;
let password = ZeroingString::from(password); let password = ZeroingString::from(password);
let round_time = args.value_of("round_time").map(|t| t.parse::<u32>().unwrap() );
let bind_addr = args.value_of("bind_addr"); let bind_addr = args.value_of("bind_addr");
let grin_node_url = args.value_of("grin_node_url"); let grin_node_url = args.value_of("grin_node_url");
let wallet_owner_url = args.value_of("wallet_owner_url"); let wallet_owner_url = args.value_of("wallet_owner_url");
@ -50,6 +54,7 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let server_config = ServerConfig { let server_config = ServerConfig {
key: secp::random_secret(), key: secp::random_secret(),
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?, addr: bind_addr.unwrap_or("0.0.0.0:3000").parse()?,
grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?, grin_node_url: grin_node_url.unwrap_or("127.0.0.1:3413").parse()?,
wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?, wallet_owner_url: wallet_owner_url.unwrap_or("127.0.0.1:3420").parse()?,
@ -79,13 +84,24 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
// Open wallet // Open wallet
let wallet_pass = args.value_of("wallet_pass").ok_or(error::Error::new(error::ErrorKind::LoadConfigError))?; let wallet_pass = args.value_of("wallet_pass").ok_or(error::Error::new(error::ErrorKind::LoadConfigError))?;
let wallet_pass = grin_util::ZeroingString::from(wallet_pass); let wallet_pass = grin_util::ZeroingString::from(wallet_pass);
let wallet = Wallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?; let wallet = HttpWallet::open_wallet(&server_config.wallet_owner_url, &wallet_pass)?;
// Create GrinNode
let node = HttpGrinNode::new(&server_config.grin_node_url, &None);
let stop_state = Arc::new(StopState::new());
let stop_state_clone = stop_state.clone();
std::thread::spawn(move || {
let shutdown_signal = async move { let shutdown_signal = async move {
// Wait for the CTRL+C signal // Wait for the CTRL+C signal
tokio::signal::ctrl_c() tokio::signal::ctrl_c()
.await .await
.expect("failed to install CTRL+C signal handler"); .expect("failed to install CTRL+C signal handler");
}; };
server::listen(&server_config, &wallet, shutdown_signal) futures::executor::block_on(shutdown_signal);
stop_state_clone.stop();
});
server::listen(&server_config, Arc::new(wallet), Arc::new(node), &stop_state)
} }

View file

@ -1,63 +1,32 @@
use crate::config::ServerConfig;
use crate::error::{ErrorKind, Result}; use crate::error::{ErrorKind, Result};
use crate::secp::Commitment; use crate::secp::Commitment;
use grin_api::client; use grin_api::client;
use grin_api::json_rpc::{build_request, Request, Response}; use grin_api::json_rpc::{build_request, Request, Response};
use grin_api::{BlockPrintable, LocatedTxKernel, OutputPrintable, OutputType, Tip}; use grin_api::{OutputPrintable, OutputType, Tip};
use grin_core::consensus::COINBASE_MATURITY; use grin_core::consensus::COINBASE_MATURITY;
use grin_core::core::{Input, OutputFeatures, Transaction}; use grin_core::core::{Input, OutputFeatures, Transaction};
use grin_util::ToHex; use grin_util::ToHex;
use serde_json::json; use serde_json::json;
use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::{Arc, RwLock};
const ENDPOINT: &str = "/v2/foreign"; pub trait GrinNode : Send + Sync {
/// Retrieves the unspent output with a matching commitment
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>>;
#[derive(Clone)] /// Gets the height of the chain tip
pub struct HTTPNodeClient { fn get_chain_height(&self) -> Result<u64>;
node_url: SocketAddr,
node_api_secret: Option<String>, /// Posts a transaction to the grin node
fn post_tx(&self, tx: &Transaction) -> Result<()>;
} }
impl HTTPNodeClient { /// Checks whether a commitment is spendable at the block height provided
/// Create a new client that will communicate with the given grin node pub fn is_spendable(node: &Arc<dyn GrinNode>, output_commit: &Commitment, next_block_height: u64) -> Result<bool> {
pub fn new(node_url: &SocketAddr, node_api_secret: Option<String>) -> HTTPNodeClient { let output = node.get_utxo(&output_commit)?;
HTTPNodeClient {
node_url: node_url.to_owned(),
node_api_secret: node_api_secret,
}
}
fn send_json_request<D: serde::de::DeserializeOwned>(
&self,
method: &str,
params: &serde_json::Value,
) -> Result<D> {
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()?;
Ok(parsed)
}
}
#[derive(Clone)]
pub struct GrinNode {
client: HTTPNodeClient,
}
impl GrinNode {
pub fn new(server_config: &ServerConfig) -> GrinNode {
GrinNode {
client: HTTPNodeClient::new(&server_config.grin_node_url, None),
}
}
// Checks whether a commitment is spendable at the block height provided
pub fn is_spendable(&self, output_commit: &Commitment, next_block_height: u64) -> Result<bool> {
let output = self.get_output(&output_commit)?;
if let Some(out) = output { if let Some(out) = output {
let is_coinbase = match out.output_type { let is_coinbase = match out.output_type {
OutputType::Coinbase => true, OutputType::Coinbase => true,
@ -81,8 +50,8 @@ impl GrinNode {
} }
/// Builds an input for an unspent output commitment /// Builds an input for an unspent output commitment
pub fn build_input(&self, output_commit: &Commitment) -> Result<Option<Input>> { pub fn build_input(node: &Arc<dyn GrinNode>, output_commit: &Commitment) -> Result<Option<Input>> {
let output = self.get_output(&output_commit)?; let output = node.get_utxo(&output_commit)?;
if let Some(out) = output { if let Some(out) = output {
let features = match out.output_type { let features = match out.output_type {
@ -97,7 +66,39 @@ impl GrinNode {
Ok(None) Ok(None)
} }
fn get_output(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> { /// HTTP (JSONRPC) implementation of the 'GrinNode' trait
#[derive(Clone)]
pub struct HttpGrinNode {
node_url: SocketAddr,
node_api_secret: Option<String>,
}
const ENDPOINT: &str = "/v2/foreign";
impl HttpGrinNode {
pub fn new(node_url: &SocketAddr, node_api_secret: &Option<String>) -> HttpGrinNode {
HttpGrinNode {
node_url: node_url.to_owned(),
node_api_secret: node_api_secret.to_owned(),
}
}
fn send_json_request<D: serde::de::DeserializeOwned>(
&self,
method: &str,
params: &serde_json::Value,
) -> Result<D> {
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()?;
Ok(parsed)
}
}
impl GrinNode for HttpGrinNode {
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> {
let commits : Vec<String> = vec![output_commit.to_hex()]; let commits : Vec<String> = vec![output_commit.to_hex()];
let start_height : Option<u64> = None; let start_height : Option<u64> = None;
let end_height : Option<u64> = None; let end_height : Option<u64> = None;
@ -105,7 +106,7 @@ impl GrinNode {
let include_merkle_proof : Option<bool> = Some(false); let include_merkle_proof : Option<bool> = Some(false);
let params = json!([Some(commits), start_height, end_height, include_proof, include_merkle_proof]); let params = json!([Some(commits), start_height, end_height, include_proof, include_merkle_proof]);
let outputs = self.client.send_json_request::<Vec<OutputPrintable>>("get_outputs", &params)?; let outputs = self.send_json_request::<Vec<OutputPrintable>>("get_outputs", &params)?;
if outputs.is_empty() { if outputs.is_empty() {
return Ok(None); return Ok(None);
} }
@ -113,10 +114,9 @@ impl GrinNode {
Ok(Some(outputs[0].clone())) Ok(Some(outputs[0].clone()))
} }
/// Gets the height of the chain tip fn get_chain_height(&self) -> Result<u64> {
pub fn get_chain_height(&self) -> Result<u64> {
let params = json!([]); let params = json!([]);
let tip_json = self.client.send_json_request::<serde_json::Value>("get_tip", &params)?; let tip_json = self.send_json_request::<serde_json::Value>("get_tip", &params)?;
let tip: Result<Tip> = serde_json::from_value(tip_json["Ok"].clone()) let tip: Result<Tip> = serde_json::from_value(tip_json["Ok"].clone())
.map_err(|_| ErrorKind::SerdeJsonError.into()); .map_err(|_| ErrorKind::SerdeJsonError.into());
@ -124,46 +124,67 @@ impl GrinNode {
Ok(tip?.height) Ok(tip?.height)
} }
/// Posts a transaction to the grin node fn post_tx(&self, tx: &Transaction) -> Result<()> {
pub fn post_tx(&self, tx: &Transaction) -> Result<()> {
let params = json!([tx, true]); let params = json!([tx, true]);
self.client.send_json_request::<serde_json::Value>("push_transaction", &params)?; self.send_json_request::<serde_json::Value>("push_transaction", &params)?;
Ok(()) Ok(())
} }
// milestone 3: needed to handle chain reorgs
pub fn chain_has_kernel(
&self,
kernel_excess: &Commitment,
start_height: Option<u64>,
end_height: Option<u64>,
) -> Result<bool> {
let params = json!([kernel_excess, start_height, end_height]);
let located_kernel_json = self.client.send_json_request::<serde_json::Value>("get_kernel", &params)?;
let located_kernel : Result<LocatedTxKernel> = serde_json::from_value(located_kernel_json["Ok"].clone())
.map_err(|_| ErrorKind::SerdeJsonError.into());
Ok(located_kernel.is_ok())
} }
// milestone 3: needed to handle chain reorgs pub struct MockGrinNode {
pub fn block_has_kernel(&self, block_hash: &String, kernel_excess: &Commitment) -> Result<bool> { utxos: HashMap<Commitment, OutputPrintable>,
let block = self.get_block(None, Some(block_hash.clone()), None)?; txns_posted: RwLock<Vec<Transaction>>,
let found = block.kernels.into_iter()
.any(|kern| kern.excess == kernel_excess.to_hex());
Ok(found)
} }
// milestone 3: needed to handle chain reorgs impl MockGrinNode {
fn get_block(&self, pub fn new() -> MockGrinNode {
height: Option<u64>, MockGrinNode {
hash: Option<String>, utxos: HashMap::new(),
commit: Option<String>, txns_posted: RwLock::new(Vec::new()),
) -> Result<BlockPrintable> { }
let params = json!([height, hash, commit]); }
let block_printable = self.client.send_json_request::<BlockPrintable>("get_block", &params)?;
Ok(block_printable) pub fn add_utxo(&mut self, output_commit: &Commitment, utxo: &OutputPrintable) {
self.utxos.insert(output_commit.clone(), utxo.clone());
}
pub fn add_default_utxo(&mut self, output_commit: &Commitment) {
let utxo = OutputPrintable{
output_type: OutputType::Transaction,
commit: output_commit.to_owned(),
spent: false,
proof: None,
proof_hash: String::from(""),
block_height: None,
merkle_proof: None,
mmr_index: 0
};
self.add_utxo(&output_commit, &utxo);
}
pub fn get_posted_txns(&self) -> Vec<Transaction> {
let read = self.txns_posted.read().unwrap();
read.clone()
}
}
impl GrinNode for MockGrinNode {
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> {
if let Some(utxo) = self.utxos.get(&output_commit) {
return Ok(Some(utxo.clone()));
}
Ok(None)
}
fn get_chain_height(&self) -> Result<u64> {
Ok(100)
}
fn post_tx(&self, tx: &Transaction) -> Result<()> {
let mut write = self.txns_posted.write().unwrap();
write.push(tx.clone());
Ok(())
} }
} }

View file

@ -1,61 +1,46 @@
use crate::error::Result; use crate::error::Result;
use crate::secp::{self, Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret}; use crate::secp::{self, Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret};
use crate::types::{Hop, Onion, RawBytes, Payload, deserialize_payload, serialize_payload}; use crate::types::Payload;
use crate::ser;
use chacha20::{ChaCha20, Key, Nonce}; use chacha20::{ChaCha20, Key, Nonce};
use chacha20::cipher::{NewCipher, StreamCipher}; use chacha20::cipher::{NewCipher, StreamCipher};
use grin_core::ser::{self, ProtocolVersion, Writeable, Writer};
use grin_util::{self, ToHex};
use hmac::{Hmac, Mac}; use hmac::{Hmac, Mac};
use serde::{Deserialize};
use serde::ser::SerializeStruct;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fmt;
type HmacSha256 = Hmac<Sha256>; type HmacSha256 = Hmac<Sha256>;
type RawBytes = Vec<u8>;
/// Create an Onion for the Commitment, encrypting the payload for each hop #[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)] // used by component tests pub struct Onion {
pub fn create_onion(commitment: &Commitment, session_key: &SecretKey, hops: &Vec<Hop>) -> Result<Onion> { pub ephemeral_pubkey: PublicKey,
let secp = Secp256k1::new(); pub commit: Commitment,
let mut ephemeral_key = session_key.clone(); pub enc_payloads: Vec<RawBytes>,
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
let mut enc_payloads: Vec<RawBytes> = Vec::new();
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 blinding_factor = calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?;
shared_secrets.push(shared_secret);
enc_payloads.push(serialize_payload(&hop.payload)?);
ephemeral_key.mul_assign(&secp, &blinding_factor)?;
} }
for i in (0..shared_secrets.len()).rev() { impl Onion {
let mut cipher = new_stream_cipher(&shared_secrets[i])?; pub fn serialize(&self) -> Result<Vec<u8>> {
for j in i..shared_secrets.len() { let mut vec = vec![];
cipher.apply_keystream(&mut enc_payloads[j]); ser::serialize_default(&mut vec, &self)?;
} Ok(vec)
}
let onion = Onion{
ephemeral_pubkey: secp::to_public_key(&session_key)?,
commit: commitment.clone(),
enc_payloads: enc_payloads,
};
Ok(onion)
} }
/// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload /// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload
pub fn peel_layer(onion: &Onion, secret_key: &SecretKey) -> Result<(Payload, Onion)> { pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<(Payload, Onion)> {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let shared_secret = SharedSecret::new(&secp, &onion.ephemeral_pubkey, &secret_key); let shared_secret = SharedSecret::new(&secp, &self.ephemeral_pubkey, &secret_key);
let mut cipher = new_stream_cipher(&shared_secret)?; let mut cipher = new_stream_cipher(&shared_secret)?;
let mut decrypted_bytes = onion.enc_payloads[0].clone(); let mut decrypted_bytes = self.enc_payloads[0].clone();
cipher.apply_keystream(&mut decrypted_bytes); cipher.apply_keystream(&mut decrypted_bytes);
let decrypted_payload = deserialize_payload(&decrypted_bytes)?; let decrypted_payload = Payload::deserialize(&decrypted_bytes)?;
let enc_payloads : Vec<RawBytes> = onion.enc_payloads.iter() let enc_payloads : Vec<RawBytes> = self.enc_payloads.iter()
.enumerate() .enumerate()
.filter(|&(i, _)| i != 0) .filter(|&(i, _)| i != 0)
.map(|(_, enc_payload)| { .map(|(_, enc_payload)| {
@ -65,12 +50,12 @@ pub fn peel_layer(onion: &Onion, secret_key: &SecretKey) -> Result<(Payload, Oni
}) })
.collect(); .collect();
let blinding_factor = calc_blinding_factor(&shared_secret, &onion.ephemeral_pubkey)?; let blinding_factor = calc_blinding_factor(&shared_secret, &self.ephemeral_pubkey)?;
let mut ephemeral_pubkey = onion.ephemeral_pubkey.clone(); let mut ephemeral_pubkey = self.ephemeral_pubkey.clone();
ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?; ephemeral_pubkey.mul_assign(&secp, &blinding_factor)?;
let mut commitment = onion.commit.clone(); let mut commitment = self.commit.clone();
commitment = secp::add_excess(&commitment, &decrypted_payload.excess)?; commitment = secp::add_excess(&commitment, &decrypted_payload.excess)?;
commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())?; commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())?;
@ -81,9 +66,10 @@ pub fn peel_layer(onion: &Onion, secret_key: &SecretKey) -> Result<(Payload, Oni
}; };
Ok((decrypted_payload, peeled_onion)) Ok((decrypted_payload, peeled_onion))
} }
}
fn calc_blinding_factor(shared_secret: &SharedSecret, ephemeral_pubkey: &PublicKey) -> Result<SecretKey> { fn calc_blinding_factor(shared_secret: &SharedSecret, ephemeral_pubkey: &PublicKey) -> Result<SecretKey> {
let serialized_pubkey = ser::ser_vec(&ephemeral_pubkey)?; let serialized_pubkey = ser::ser_vec(&ephemeral_pubkey, ProtocolVersion::local())?;
let mut hasher = Sha256::default(); let mut hasher = Sha256::default();
hasher.update(&serialized_pubkey); hasher.update(&serialized_pubkey);
@ -105,11 +91,160 @@ fn new_stream_cipher(shared_secret: &SharedSecret) -> Result<ChaCha20> {
Ok(ChaCha20::new(&key, &nonce)) Ok(ChaCha20::new(&key, &nonce))
} }
impl Writeable for Onion {
fn write<W: Writer>(&self, writer: &mut W) -> std::result::Result<(), ser::Error> {
self.ephemeral_pubkey.write(writer)?;
writer.write_fixed_bytes(&self.commit)?;
writer.write_u64(self.enc_payloads.len() as u64)?;
for p in &self.enc_payloads {
writer.write_u64(p.len() as u64)?;
p.write(writer)?;
}
Ok(())
}
}
impl serde::ser::Serialize for Onion {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let mut state = serializer.serialize_struct("Onion", 3)?;
let secp = Secp256k1::new();
state.serialize_field("pubkey", &self.ephemeral_pubkey.serialize_vec(&secp, true).to_hex())?;
state.serialize_field("commit", &self.commit.to_hex())?;
let hex_payloads: Vec<String> = self.enc_payloads.iter().map(|v| v.to_hex()).collect();
state.serialize_field("data", &hex_payloads)?;
state.end()
}
}
impl<'de> serde::de::Deserialize<'de> for Onion {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Pubkey,
Commit,
Data
}
struct OnionVisitor;
impl<'de> serde::de::Visitor<'de> for OnionVisitor {
type Value = Onion;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an Onion")
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut pubkey = None;
let mut commit = None;
let mut data = None;
while let Some(key) = map.next_key()? {
match key {
Field::Pubkey => {
let val: String = map.next_value()?;
let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
let secp = Secp256k1::new();
pubkey = Some(PublicKey::from_slice(&secp, &vec[..]).map_err(serde::de::Error::custom)?);
}
Field::Commit => {
let val: String = map.next_value()?;
let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
commit = Some(Commitment::from_vec(vec));
}
Field::Data => {
let val: Vec<String> = map.next_value()?;
let mut vec: Vec<Vec<u8>> = Vec::new();
for hex in val {
vec.push(grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?);
}
data = Some(vec);
}
}
}
Ok(Onion {
ephemeral_pubkey: pubkey.unwrap(),
commit: commit.unwrap(),
enc_payloads: data.unwrap(),
})
}
}
const FIELDS: &[&str] = &[
"pubkey",
"commit",
"data"
];
deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { pub mod test_util {
use super::super::secp; use super::{Onion, RawBytes};
use super::super::types; use crate::error::Result;
use super::super::onion; use crate::types::{Payload};
use crate::secp::{Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret};
use chacha20::cipher::{StreamCipher};
pub struct Hop {
pub pubkey: PublicKey,
pub payload: Payload,
}
/// Create an Onion for the Commitment, encrypting the payload for each hop
pub fn create_onion(commitment: &Commitment, session_key: &SecretKey, hops: &Vec<Hop>) -> Result<Onion> {
let secp = Secp256k1::new();
let mut ephemeral_key = session_key.clone();
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
let mut enc_payloads: Vec<RawBytes> = Vec::new();
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 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)?;
}
for i in (0..shared_secrets.len()).rev() {
let mut cipher = super::new_stream_cipher(&shared_secrets[i])?;
for j in i..shared_secrets.len() {
cipher.apply_keystream(&mut enc_payloads[j]);
}
}
let onion = Onion{
ephemeral_pubkey: PublicKey::from_secret_key(&secp, session_key)?,
commit: commitment.clone(),
enc_payloads: enc_payloads,
};
Ok(onion)
}
}
#[cfg(test)]
pub mod tests {
use super::test_util::{self, Hop};
use crate::types::{Payload};
use crate::secp;
use grin_core::core::FeeFields; use grin_core::core::FeeFields;
@ -124,7 +259,7 @@ mod tests {
let commitment = secp::commit(in_value, &blind).unwrap(); let commitment = secp::commit(in_value, &blind).unwrap();
let session_key = secp::random_secret(); let session_key = secp::random_secret();
let mut hops : Vec<types::Hop> = Vec::new(); let mut hops : Vec<Hop> = Vec::new();
let mut keys : Vec<secp::SecretKey> = Vec::new(); let mut keys : Vec<secp::SecretKey> = Vec::new();
let mut final_commit = secp::commit(out_value, &blind).unwrap(); let mut final_commit = secp::commit(out_value, &blind).unwrap();
@ -146,9 +281,9 @@ mod tests {
None None
}; };
hops.push(types::Hop{ hops.push(Hop{
pubkey: secp::PublicKey::from_secret_key(&secp, &keys[i]).unwrap(), pubkey: secp::PublicKey::from_secret_key(&secp, &keys[i]).unwrap(),
payload: types::Payload{ payload: Payload{
excess: excess, excess: excess,
fee: FeeFields::from(fee_per_hop as u32), fee: FeeFields::from(fee_per_hop as u32),
rangeproof: proof, rangeproof: proof,
@ -156,15 +291,15 @@ mod tests {
}); });
} }
let mut onion_packet = onion::create_onion(&commitment, &session_key, &hops).unwrap(); let mut onion_packet = test_util::create_onion(&commitment, &session_key, &hops).unwrap();
let mut payload = types::Payload{ let mut payload = Payload{
excess: secp::random_secret(), excess: secp::random_secret(),
fee: FeeFields::from(fee_per_hop as u32), fee: FeeFields::from(fee_per_hop as u32),
rangeproof: None rangeproof: None
}; };
for i in 0..5 { for i in 0..5 {
let peeled = onion::peel_layer(&onion_packet, &keys[i]).unwrap(); let peeled = onion_packet.peel_layer(&keys[i]).unwrap();
payload = peeled.0; payload = peeled.0;
onion_packet = peeled.1; onion_packet = peeled.1;
} }

View file

@ -5,27 +5,26 @@ pub use secp256k1zkp::pedersen::{Commitment, RangeProof};
pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY}; pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY};
pub use secp256k1zkp::constants::{AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE, SECRET_KEY_SIZE}; pub use secp256k1zkp::constants::{AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE, SECRET_KEY_SIZE};
use crate::ser::{self, Readable, Reader, Writeable, Writer};
use crate::error::{Error, ErrorKind, Result}; use crate::error::{Error, ErrorKind, Result};
use blake2::blake2b::Blake2b; use blake2::blake2b::Blake2b;
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use secp256k1zkp::rand::thread_rng; use secp256k1zkp::rand::thread_rng;
use std::cmp;
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys /// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys
pub struct ComSignature { pub struct ComSignature {
pub pub_nonce: Commitment, pub_nonce: Commitment,
pub s: SecretKey, s: SecretKey,
pub t: SecretKey, t: SecretKey,
} }
impl ComSignature { impl ComSignature {
pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature { pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature {
ComSignature { ComSignature {
pub_nonce: pub_nonce.clone(), pub_nonce: pub_nonce.to_owned(),
s: s.clone(), s: s.to_owned(),
t: t.clone(), t: t.to_owned(),
} }
} }
@ -34,7 +33,7 @@ impl ComSignature {
let secp = Secp256k1::with_caps(ContextFlag::Commit); let secp = Secp256k1::with_caps(ContextFlag::Commit);
let mut amt_bytes = [0; 32]; let mut amt_bytes = [0; 32];
BigEndian::write_u64(&mut amt_bytes[24..31], amount); BigEndian::write_u64(&mut amt_bytes[24..32], amount);
let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?; let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?;
let k_1 = SecretKey::new(&secp, &mut thread_rng()); let k_1 = SecretKey::new(&secp, &mut thread_rng());
@ -71,7 +70,7 @@ impl ComSignature {
let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()]; let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()];
let S2 = secp.commit_sum(commits, Vec::new())?; let S2 = secp.commit_sum(commits, Vec::new())?;
if S1 == S2 { if S1 != S2 {
return Err(Error::new(ErrorKind::InvalidSigError)); return Err(Error::new(ErrorKind::InvalidSigError));
} }
@ -94,9 +93,8 @@ impl ComSignature {
/// Serializes a ComSignature to and from hex /// Serializes a ComSignature to and from hex
pub mod comsig_serde { pub mod comsig_serde {
use super::ComSignature; use super::ComSignature;
use super::ser::{self, BinReader, Readable}; use grin_core::ser::{self, ProtocolVersion};
use serde::{Deserialize, Serializer}; use serde::{Deserialize, Serializer};
use std::io::Cursor;
use grin_util::ToHex; use grin_util::ToHex;
/// Serializes a ComSignature as a hex string /// Serializes a ComSignature as a hex string
@ -105,7 +103,7 @@ pub mod comsig_serde {
S: Serializer, S: Serializer,
{ {
use serde::ser::Error; use serde::ser::Error;
let bytes = ser::ser_vec(&comsig).map_err(Error::custom)?; let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?;
serializer.serialize_str(&bytes.to_hex()) serializer.serialize_str(&bytes.to_hex())
} }
@ -117,18 +115,29 @@ pub mod comsig_serde {
use serde::de::Error; use serde::de::Error;
let bytes = String::deserialize(deserializer) let bytes = String::deserialize(deserializer)
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?; .and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?;
let sig: ComSignature = grin_core::ser::deserialize_default(&mut &bytes[..])
let mut cursor = Cursor::new(&bytes); .map_err(Error::custom)?;
let mut reader = BinReader::new(&mut cursor); Ok(sig)
ComSignature::read(&mut reader).map_err(Error::custom)
} }
} }
/// Compute a PublicKey from a SecretKey #[allow(non_snake_case)]
pub fn to_public_key(secret_key: &SecretKey) -> Result<PublicKey> { impl Readable for ComSignature {
let secp = Secp256k1::new(); fn read<R: Reader>(reader: &mut R) -> std::result::Result<Self, ser::Error> {
let pubkey = PublicKey::from_secret_key(&secp, secret_key)?; let R = Commitment::read(reader)?;
Ok(pubkey) let s = read_secret_key(reader)?;
let t = read_secret_key(reader)?;
Ok(ComSignature::new(&R, &s, &t))
}
}
impl Writeable for ComSignature {
fn write<W: Writer>(&self, writer: &mut W) -> std::result::Result<(), ser::Error> {
writer.write_fixed_bytes(self.pub_nonce.0)?;
writer.write_fixed_bytes(self.s.0)?;
writer.write_fixed_bytes(self.t.0)?;
Ok(())
}
} }
/// Generate a random SecretKey. /// Generate a random SecretKey.
@ -137,6 +146,14 @@ pub fn random_secret() -> SecretKey {
SecretKey::new(&secp, &mut thread_rng()) SecretKey::new(&secp, &mut thread_rng())
} }
/// Deserialize a SecretKey from a Reader
pub fn read_secret_key<R: Reader>(reader: &mut R) -> std::result::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)?;
Ok(pk)
}
/// 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) -> Result<Commitment> {
let secp = Secp256k1::with_caps(ContextFlag::Commit); let secp = Secp256k1::with_caps(ContextFlag::Commit);
@ -154,6 +171,7 @@ pub fn add_excess(commitment: &Commitment, excess: &SecretKey) -> Result<Commitm
Ok(sum) Ok(sum)
} }
/// 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> {
let secp = Secp256k1::with_caps(ContextFlag::Commit); let secp = Secp256k1::with_caps(ContextFlag::Commit);
let neg_commit : Commitment = secp.commit(value, ZERO_KEY)?; let neg_commit : Commitment = secp.commit(value, ZERO_KEY)?;
@ -161,18 +179,7 @@ pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment> {
Ok(sum) Ok(sum)
} }
pub fn add_blinds(excesses: &Vec<SecretKey>) -> Result<SecretKey> { /// Signs the message with the provided SecretKey
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let sum = secp.blind_sum(excesses.clone(), Vec::new())?;
Ok(sum)
}
pub fn sub_blinds(minuend: &SecretKey, subtrahend: &SecretKey) -> Result<SecretKey> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let result = secp.blind_sum(vec![minuend.clone()], vec![subtrahend.clone()])?;
Ok(result)
}
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> { pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> {
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)?;
@ -180,107 +187,33 @@ pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature> {
Ok(sig) Ok(sig)
} }
/// secp256k1-zkp object serialization #[cfg(test)]
mod tests {
use super::{ComSignature, ContextFlag, Secp256k1, SecretKey};
use crate::error::Result;
impl Readable for Commitment { use secp256k1zkp::rand::{RngCore, thread_rng};
fn read<R: Reader>(reader: &mut R) -> Result<Commitment> { use rand::Rng;
let a = reader.read_fixed_bytes(PEDERSEN_COMMITMENT_SIZE)?;
let mut c = [0; PEDERSEN_COMMITMENT_SIZE];
c[..PEDERSEN_COMMITMENT_SIZE].clone_from_slice(&a[..PEDERSEN_COMMITMENT_SIZE]);
Ok(Commitment(c))
}
}
impl Writeable for Commitment { /// Test signing and verification of ComSignatures
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> { #[test]
writer.write_fixed_bytes(self) fn verify_comsig() -> Result<()> {
} let secp = Secp256k1::with_caps(ContextFlag::Commit);
}
impl Readable for RangeProof { let amount = thread_rng().next_u64();
fn read<R: Reader>(reader: &mut R) -> Result<RangeProof> { let blind = SecretKey::new(&secp, &mut thread_rng());
let len = reader.read_u64()?; let msg: [u8; 16] = rand::thread_rng().gen();
let max_len = cmp::min(len as usize, MAX_PROOF_SIZE); let comsig = ComSignature::sign(amount, &blind, &msg.to_vec())?;
let p = reader.read_fixed_bytes(max_len)?;
let mut proof = [0; MAX_PROOF_SIZE];
proof[..p.len()].clone_from_slice(&p[..]);
Ok(RangeProof {
plen: proof.len(),
proof,
})
}
}
impl Writeable for RangeProof { let commit = secp.commit(amount, blind.clone())?;
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> { assert!(comsig.verify(&commit, &msg.to_vec()).is_ok());
writer.write_bytes(self)
}
}
impl Readable for Signature { let wrong_msg: [u8; 16] = rand::thread_rng().gen();
fn read<R: Reader>(reader: &mut R) -> Result<Signature> { assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err());
let a = reader.read_fixed_bytes(AGG_SIGNATURE_SIZE)?;
let mut c = [0; AGG_SIGNATURE_SIZE];
c[..AGG_SIGNATURE_SIZE].clone_from_slice(&a[..AGG_SIGNATURE_SIZE]);
Ok(Signature::from_raw_data(&c).unwrap())
}
}
impl Writeable for Signature { let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?;
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> { assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err());
writer.write_fixed_bytes(self)
}
}
impl Readable for PublicKey {
// Read the public key in compressed form
fn read<R: Reader>(reader: &mut R) -> Result<Self> {
let buf = reader.read_fixed_bytes(COMPRESSED_PUBLIC_KEY_SIZE)?;
let secp = Secp256k1::with_caps(ContextFlag::None);
let pk = PublicKey::from_slice(&secp, &buf).map_err(|_| ErrorKind::CorruptedData)?;
Ok(pk)
}
}
impl Writeable for PublicKey {
// Write the public key in compressed form
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
let secp = Secp256k1::with_caps(ContextFlag::None);
writer.write_fixed_bytes(self.serialize_vec(&secp, true))?;
Ok(()) Ok(())
} }
} }
impl Readable for SecretKey {
fn read<R: Reader>(reader: &mut R) -> Result<Self> {
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(|_| ErrorKind::CorruptedData)?;
Ok(pk)
}
}
impl Writeable for SecretKey {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
writer.write_fixed_bytes(self.0)?;
Ok(())
}
}
#[allow(non_snake_case)]
impl Readable for ComSignature {
fn read<R: Reader>(reader: &mut R) -> Result<ComSignature> {
let R = Commitment::read(reader)?;
let s = SecretKey::read(reader)?;
let t = SecretKey::read(reader)?;
Ok(ComSignature::new(&R, &s, &t))
}
}
impl Writeable for ComSignature {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
writer.write_fixed_bytes(self.pub_nonce.0)?;
writer.write_fixed_bytes(self.s.0)?;
writer.write_fixed_bytes(self.t.0)
}
}

View file

@ -1,624 +0,0 @@
// Copyright 2021 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Serialization and deserialization layer specialized for binary encoding.
//! Ensures consistency and safety. Basically a minimal subset or
//! rustc_serialize customized for our need.
//!
//! To use it simply implement `Writeable` or `Readable` and then use the
//! `serialize` or `deserialize` functions on them as appropriate.
use crate::error::{Error, ErrorKind, Result};
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
use bytes::Buf;
use std::io::{self, Read, Write};
use std::marker;
/// Implementations defined how different numbers and binary structures are
/// written to an underlying stream or container (depending on implementation).
pub trait Writer {
/// Writes a u8 as bytes
fn write_u8(&mut self, n: u8) -> Result<()> {
self.write_fixed_bytes(&[n])
}
/// Writes a u16 as bytes
fn write_u16(&mut self, n: u16) -> Result<()> {
let mut bytes = [0; 2];
BigEndian::write_u16(&mut bytes, n);
self.write_fixed_bytes(&bytes)
}
/// Writes a u32 as bytes
fn write_u32(&mut self, n: u32) -> Result<()> {
let mut bytes = [0; 4];
BigEndian::write_u32(&mut bytes, n);
self.write_fixed_bytes(&bytes)
}
/// Writes a u32 as bytes
fn write_i32(&mut self, n: i32) -> Result<()> {
let mut bytes = [0; 4];
BigEndian::write_i32(&mut bytes, n);
self.write_fixed_bytes(&bytes)
}
/// Writes a u64 as bytes
fn write_u64(&mut self, n: u64) -> Result<()> {
let mut bytes = [0; 8];
BigEndian::write_u64(&mut bytes, n);
self.write_fixed_bytes(&bytes)
}
/// Writes a i64 as bytes
fn write_i64(&mut self, n: i64) -> Result<()> {
let mut bytes = [0; 8];
BigEndian::write_i64(&mut bytes, n);
self.write_fixed_bytes(&bytes)
}
/// Writes a variable number of bytes. The length is encoded as a 64-bit
/// prefix.
fn write_bytes<T: AsRef<[u8]>>(&mut self, bytes: T) -> Result<()> {
self.write_u64(bytes.as_ref().len() as u64)?;
self.write_fixed_bytes(bytes)
}
/// Writes a fixed number of bytes. The reader is expected to know the actual length on read.
fn write_fixed_bytes<T: AsRef<[u8]>>(&mut self, bytes: T) -> Result<()>;
/// Writes a fixed length of "empty" bytes.
fn write_empty_bytes(&mut self, length: usize) -> Result<()> {
self.write_fixed_bytes(vec![0u8; length])
}
}
/// Implementations defined how different numbers and binary structures are
/// read from an underlying stream or container (depending on implementation).
pub trait Reader {
/// Read a u8 from the underlying Read
fn read_u8(&mut self) -> Result<u8>;
/// Read a u16 from the underlying Read
fn read_u16(&mut self) -> Result<u16>;
/// Read a u32 from the underlying Read
fn read_u32(&mut self) -> Result<u32>;
/// Read a u64 from the underlying Read
fn read_u64(&mut self) -> Result<u64>;
/// Read a i32 from the underlying Read
fn read_i32(&mut self) -> Result<i32>;
/// Read a i64 from the underlying Read
fn read_i64(&mut self) -> Result<i64>;
/// Read a u64 len prefix followed by that number of exact bytes.
fn read_bytes_len_prefix(&mut self) -> Result<Vec<u8>>;
/// Read a fixed number of bytes from the underlying reader.
fn read_fixed_bytes(&mut self, length: usize) -> Result<Vec<u8>>;
/// Consumes a byte from the reader, producing an error if it doesn't have
/// the expected value
fn expect_u8(&mut self, val: u8) -> Result<u8>;
/// Read a fixed number of "empty" bytes from the underlying reader.
/// It is an error if any non-empty bytes encountered.
fn read_empty_bytes(&mut self, length: usize) -> Result<()> {
for _ in 0..length {
if self.read_u8()? != 0u8 {
return Err(ErrorKind::CorruptedData.into());
}
}
Ok(())
}
}
/// Trait that every type that can be serialized as binary must implement.
/// Writes directly to a Writer, a utility type thinly wrapping an
/// underlying Write implementation.
pub trait Writeable {
/// Write the data held by this Writeable to the provided writer
fn write<W: Writer>(&self, writer: &mut W) -> Result<()>;
}
/// Reader that exposes an Iterator interface.
pub struct IteratingReader<'a, T, R: Reader> {
count: u64,
curr: u64,
reader: &'a mut R,
_marker: marker::PhantomData<T>,
}
impl<'a, T, R: Reader> IteratingReader<'a, T, R> {
/// Constructor to create a new iterating reader for the provided underlying reader.
/// Takes a count so we know how many to iterate over.
pub fn new(reader: &'a mut R, count: u64) -> Self {
let curr = 0;
IteratingReader {
count,
curr,
reader,
_marker: marker::PhantomData,
}
}
}
impl<'a, T, R> Iterator for IteratingReader<'a, T, R>
where
T: Readable,
R: Reader,
{
type Item = T;
fn next(&mut self) -> Option<T> {
if self.curr >= self.count {
return None;
}
self.curr += 1;
T::read(self.reader).ok()
}
}
/// Reads multiple serialized items into a Vec.
pub fn read_multi<T, R>(reader: &mut R, count: u64) -> Result<Vec<T>>
where
T: Readable,
R: Reader,
{
// Very rudimentary check to ensure we do not overflow anything
// attempting to read huge amounts of data.
// Probably better than checking if count * size overflows a u64 though.
if count > 1_000_000 {
return Err(ErrorKind::TooLargeReadErr.into());
}
let res: Vec<T> = IteratingReader::new(reader, count).collect();
if res.len() as u64 != count {
return Err(ErrorKind::CountError.into());
}
Ok(res)
}
/// Trait that every type that can be deserialized from binary must implement.
/// Reads directly to a Reader, a utility type thinly wrapping an
/// underlying Read implementation.
pub trait Readable
where
Self: Sized,
{
/// Reads the data necessary to this Readable from the provided reader
fn read<R: Reader>(reader: &mut R) -> Result<Self>;
}
/// Deserializes a Readable from any std::io::Read implementation.
pub fn deserialize<T: Readable, R: Read>(source: &mut R) -> Result<T> {
let mut reader = BinReader::new(source);
T::read(&mut reader)
}
/// Serializes a Writeable into any std::io::Write implementation.
pub fn serialize<W: Writeable>(sink: &mut dyn Write, thing: &W) -> Result<()> {
let mut writer = BinWriter::new(sink);
thing.write(&mut writer)
}
/// Utility function to serialize a writeable directly in memory using a
/// Vec<u8>.
pub fn ser_vec<W: Writeable>(thing: &W) -> Result<Vec<u8>> {
let mut vec = vec![];
serialize(&mut vec, thing)?;
Ok(vec)
}
/// Utility to read from a binary source
pub struct BinReader<'a, R: Read> {
source: &'a mut R,
}
impl<'a, R: Read> BinReader<'a, R> {
/// Constructor for a new BinReader for the provided source.
pub fn new(source: &'a mut R) -> Self {
BinReader { source }
}
}
fn map_io_err(err: io::Error) -> Error {
ErrorKind::IOErr(format!("{}", err), err.kind()).into()
}
/// Utility wrapper for an underlying byte Reader. Defines higher level methods
/// to read numbers, byte vectors, hashes, etc.
impl<'a, R: Read> Reader for BinReader<'a, R> {
fn read_u8(&mut self) -> Result<u8> {
self.source.read_u8().map_err(map_io_err)
}
fn read_u16(&mut self) -> Result<u16> {
self.source.read_u16::<BigEndian>().map_err(map_io_err)
}
fn read_u32(&mut self) -> Result<u32> {
self.source.read_u32::<BigEndian>().map_err(map_io_err)
}
fn read_i32(&mut self) -> Result<i32> {
self.source.read_i32::<BigEndian>().map_err(map_io_err)
}
fn read_u64(&mut self) -> Result<u64> {
self.source.read_u64::<BigEndian>().map_err(map_io_err)
}
fn read_i64(&mut self) -> Result<i64> {
self.source.read_i64::<BigEndian>().map_err(map_io_err)
}
/// Read a variable size vector from the underlying Read. Expects a usize
fn read_bytes_len_prefix(&mut self) -> Result<Vec<u8>> {
let len = self.read_u64()?;
self.read_fixed_bytes(len as usize)
}
/// Read a fixed number of bytes.
fn read_fixed_bytes(&mut self, len: usize) -> Result<Vec<u8>> {
// not reading more than 100k bytes in a single read
if len > 100_000 {
return Err(ErrorKind::TooLargeReadErr.into());
}
let mut buf = vec![0; len];
self.source
.read_exact(&mut buf)
.map(move |_| buf)
.map_err(map_io_err)
}
fn expect_u8(&mut self, val: u8) -> Result<u8> {
let b = self.read_u8()?;
if b == val {
Ok(b)
} else {
Err(ErrorKind::UnexpectedData {
expected: vec![val],
received: vec![b],
}.into())
}
}
}
/// A reader that reads straight off a stream.
/// Tracks total bytes read so we can verify we read the right number afterwards.
pub struct StreamingReader<'a> {
total_bytes_read: u64,
stream: &'a mut dyn Read,
}
impl<'a> StreamingReader<'a> {
/// Create a new streaming reader with the provided underlying stream.
/// Also takes a duration to be used for each individual read_exact call.
pub fn new(stream: &'a mut dyn Read) -> StreamingReader<'a> {
StreamingReader {
total_bytes_read: 0,
stream,
}
}
/// Returns the total bytes read via this streaming reader.
pub fn total_bytes_read(&self) -> u64 {
self.total_bytes_read
}
}
/// Note: We use read_fixed_bytes() here to ensure our "async" I/O behaves as expected.
impl<'a> Reader for StreamingReader<'a> {
fn read_u8(&mut self) -> Result<u8> {
let buf = self.read_fixed_bytes(1)?;
Ok(buf[0])
}
fn read_u16(&mut self) -> Result<u16> {
let buf = self.read_fixed_bytes(2)?;
Ok(BigEndian::read_u16(&buf[..]))
}
fn read_u32(&mut self) -> Result<u32> {
let buf = self.read_fixed_bytes(4)?;
Ok(BigEndian::read_u32(&buf[..]))
}
fn read_i32(&mut self) -> Result<i32> {
let buf = self.read_fixed_bytes(4)?;
Ok(BigEndian::read_i32(&buf[..]))
}
fn read_u64(&mut self) -> Result<u64> {
let buf = self.read_fixed_bytes(8)?;
Ok(BigEndian::read_u64(&buf[..]))
}
fn read_i64(&mut self) -> Result<i64> {
let buf = self.read_fixed_bytes(8)?;
Ok(BigEndian::read_i64(&buf[..]))
}
/// Read a variable size vector from the underlying stream. Expects a usize
fn read_bytes_len_prefix(&mut self) -> Result<Vec<u8>> {
let len = self.read_u64()?;
self.total_bytes_read += 8;
self.read_fixed_bytes(len as usize)
}
/// Read a fixed number of bytes.
fn read_fixed_bytes(&mut self, len: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
self.stream.read_exact(&mut buf)?;
self.total_bytes_read += len as u64;
Ok(buf)
}
fn expect_u8(&mut self, val: u8) -> Result<u8> {
let b = self.read_u8()?;
if b == val {
Ok(b)
} else {
Err(ErrorKind::UnexpectedData {
expected: vec![val],
received: vec![b],
}.into())
}
}
}
/// Protocol version-aware wrapper around a `Buf` impl
pub struct BufReader<'a, B: Buf> {
inner: &'a mut B,
bytes_read: usize,
}
impl<'a, B: Buf> BufReader<'a, B> {
/// Construct a new BufReader
pub fn new(buf: &'a mut B) -> Self {
Self {
inner: buf,
bytes_read: 0,
}
}
/// Check whether the buffer has enough bytes remaining to perform a read
fn has_remaining(&mut self, len: usize) -> Result<()> {
if self.inner.remaining() >= len {
self.bytes_read += len;
Ok(())
} else {
Err(io::ErrorKind::UnexpectedEof.into())
}
}
/// The total bytes read
pub fn bytes_read(&self) -> u64 {
self.bytes_read as u64
}
/// Convenience function to read from the buffer and deserialize
pub fn body<T: Readable>(&mut self) -> Result<T> {
T::read(self)
}
}
impl<'a, B: Buf> Reader for BufReader<'a, B> {
fn read_u8(&mut self) -> Result<u8> {
self.has_remaining(1)?;
Ok(self.inner.get_u8())
}
fn read_u16(&mut self) -> Result<u16> {
self.has_remaining(2)?;
Ok(self.inner.get_u16())
}
fn read_u32(&mut self) -> Result<u32> {
self.has_remaining(4)?;
Ok(self.inner.get_u32())
}
fn read_u64(&mut self) -> Result<u64> {
self.has_remaining(8)?;
Ok(self.inner.get_u64())
}
fn read_i32(&mut self) -> Result<i32> {
self.has_remaining(4)?;
Ok(self.inner.get_i32())
}
fn read_i64(&mut self) -> Result<i64> {
self.has_remaining(8)?;
Ok(self.inner.get_i64())
}
fn read_bytes_len_prefix(&mut self) -> Result<Vec<u8>> {
let len = self.read_u64()?;
self.read_fixed_bytes(len as usize)
}
fn read_fixed_bytes(&mut self, len: usize) -> Result<Vec<u8>> {
// not reading more than 100k bytes in a single read
if len > 100_000 {
return Err(ErrorKind::TooLargeReadErr.into());
}
self.has_remaining(len)?;
let mut buf = vec![0; len];
self.inner.copy_to_slice(&mut buf[..]);
Ok(buf)
}
fn expect_u8(&mut self, val: u8) -> Result<u8> {
let b = self.read_u8()?;
if b == val {
Ok(b)
} else {
Err(ErrorKind::UnexpectedData {
expected: vec![val],
received: vec![b],
}.into())
}
}
}
/// Utility wrapper for an underlying byte Writer. Defines higher level methods
/// to write numbers, byte vectors, hashes, etc.
pub struct BinWriter<'a> {
sink: &'a mut dyn Write,
}
impl<'a> BinWriter<'a> {
/// Wraps a standard Write in a new BinWriter
pub fn new(sink: &'a mut dyn Write) -> BinWriter<'a> {
BinWriter { sink }
}
}
impl<'a> Writer for BinWriter<'a> {
fn write_fixed_bytes<T: AsRef<[u8]>>(&mut self, bytes: T) -> Result<()> {
self.sink.write_all(bytes.as_ref())?;
Ok(())
}
}
macro_rules! impl_int {
($int:ty, $w_fn:ident, $r_fn:ident) => {
impl Writeable for $int {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
writer.$w_fn(*self)
}
}
impl Readable for $int {
fn read<R: Reader>(reader: &mut R) -> Result<$int> {
reader.$r_fn()
}
}
};
}
impl_int!(u8, write_u8, read_u8);
impl_int!(u16, write_u16, read_u16);
impl_int!(u32, write_u32, read_u32);
impl_int!(i32, write_i32, read_i32);
impl_int!(u64, write_u64, read_u64);
impl_int!(i64, write_i64, read_i64);
impl<T> Readable for Vec<T>
where
T: Readable,
{
fn read<R: Reader>(reader: &mut R) -> Result<Vec<T>> {
let mut buf = Vec::new();
loop {
let elem = T::read(reader);
match elem {
Ok(e) => buf.push(e),
// Err(ErrorKind::IOErr(ref _d, ref kind)) if *kind == io::ErrorKind::UnexpectedEof => {
// break;
// }
Err(e) => {
match e.kind() {
ErrorKind::IOErr(ref _d, ref kind) if *kind == io::ErrorKind::UnexpectedEof => {
break;
},
_ => return Err(e),
}
},
}
}
Ok(buf)
}
}
impl<T> Writeable for Vec<T>
where
T: Writeable,
{
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
for elmt in self {
elmt.write(writer)?;
}
Ok(())
}
}
impl<'a, A: Writeable> Writeable for &'a A {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
Writeable::write(*self, writer)
}
}
impl<A: Writeable, B: Writeable> Writeable for (A, B) {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
Writeable::write(&self.0, writer)?;
Writeable::write(&self.1, writer)
}
}
impl<A: Readable, B: Readable> Readable for (A, B) {
fn read<R: Reader>(reader: &mut R) -> Result<(A, B)> {
Ok((Readable::read(reader)?, Readable::read(reader)?))
}
}
impl<A: Writeable, B: Writeable, C: Writeable> Writeable for (A, B, C) {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
Writeable::write(&self.0, writer)?;
Writeable::write(&self.1, writer)?;
Writeable::write(&self.2, writer)
}
}
impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B, C, D) {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> {
Writeable::write(&self.0, writer)?;
Writeable::write(&self.1, writer)?;
Writeable::write(&self.2, writer)?;
Writeable::write(&self.3, writer)
}
}
impl<A: Readable, B: Readable, C: Readable> Readable for (A, B, C) {
fn read<R: Reader>(reader: &mut R) -> Result<(A, B, C)> {
Ok((
Readable::read(reader)?,
Readable::read(reader)?,
Readable::read(reader)?,
))
}
}
impl<A: Readable, B: Readable, C: Readable, D: Readable> Readable for (A, B, C, D) {
fn read<R: Reader>(reader: &mut R) -> Result<(A, B, C, D)> {
Ok((
Readable::read(reader)?,
Readable::read(reader)?,
Readable::read(reader)?,
Readable::read(reader)?,
))
}
}
/// Serializes a Vec<u8> to and from hex
pub mod vec_serde {
use serde::{Deserialize, Serializer};
use grin_util::ToHex;
/// Serializes a Vec<u8> as a hex string
pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&bytes.to_hex())
}
/// Creates a Vec<u8> from a hex string
pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))
}
}

View file

@ -1,13 +1,12 @@
use crate::config::ServerConfig; use crate::config::ServerConfig;
use crate::node::GrinNode; use crate::node::{self, GrinNode};
use crate::onion; use crate::onion::Onion;
use crate::secp::{self, ComSignature, SecretKey}; use crate::secp::{self, ComSignature, Secp256k1, SecretKey};
use crate::ser; use crate::wallet::{self, Wallet};
use crate::types::Onion;
use crate::wallet::Wallet;
use grin_core::core::{Input, Output, OutputFeatures, TransactionBody}; use grin_core::core::{Input, Output, OutputFeatures, TransactionBody};
use grin_core::global::DEFAULT_ACCEPT_FEE_BASE; use grin_core::global::DEFAULT_ACCEPT_FEE_BASE;
use grin_util::StopState;
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use jsonrpc_http_server::*; use jsonrpc_http_server::*;
use jsonrpc_http_server::jsonrpc_core::*; use jsonrpc_http_server::jsonrpc_core::*;
@ -27,8 +26,6 @@ pub struct Submission {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SwapReq { pub struct SwapReq {
pub onion: Onion, pub onion: Onion,
#[serde(with = "ser::vec_serde")]
pub msg: Vec<u8>,
#[serde(with = "secp::comsig_serde")] #[serde(with = "secp::comsig_serde")]
pub comsig: ComSignature, pub comsig: ComSignature,
} }
@ -50,15 +47,14 @@ pub trait Server {
#[derive(Clone)] #[derive(Clone)]
pub struct ServerImpl { pub struct ServerImpl {
server_config: ServerConfig, server_config: ServerConfig,
stop_state: Arc<StopState>,
wallet: Wallet, wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
node: GrinNode,
} }
impl ServerImpl { impl ServerImpl {
pub fn new(server_config: ServerConfig, wallet: Wallet, node: GrinNode) -> Self { pub fn new(server_config: ServerConfig, stop_state: Arc<StopState>, wallet: Arc<dyn Wallet>, node: Arc<dyn GrinNode>) -> Self {
ServerImpl { server_config, wallet, node } ServerImpl { server_config, stop_state, wallet, node }
} }
/// The fee base to use. For now, just using the default. /// The fee base to use. For now, just using the default.
@ -68,7 +64,7 @@ impl ServerImpl {
/// Minimum fee to perform a swap. /// Minimum fee to perform a swap.
/// Requires enough fee for the mwixnet server's kernel, 1 input and its output to swap. /// Requires enough fee for the mwixnet server's kernel, 1 input and its output to swap.
pub fn get_minimum_swap_fee(&self) -> u64 { fn get_minimum_swap_fee(&self) -> u64 {
TransactionBody::weight_by_iok(1, 1, 1) * self.get_fee_base() TransactionBody::weight_by_iok(1, 1, 1) * self.get_fee_base()
} }
@ -77,15 +73,19 @@ impl ServerImpl {
/// ///
/// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes. /// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes.
pub fn execute_round(&self) -> crate::error::Result<()> { pub fn execute_round(&self) -> crate::error::Result<()> {
let locked_state = SERVER_STATE.lock().unwrap(); let mut locked_state = SERVER_STATE.lock().unwrap();
let next_block_height = self.node.get_chain_height()? + 1; let next_block_height = self.node.get_chain_height()? + 1;
let spendable : Vec<Submission> = locked_state let spendable : Vec<Submission> = locked_state
.iter() .iter()
.filter(|s| self.node.is_spendable(&s.input.commit, next_block_height).unwrap_or(false)) .filter(|s| node::is_spendable(&self.node, &s.input.commit, next_block_height).unwrap_or(false))
.cloned() .cloned()
.collect(); .collect();
if spendable.len() == 0 {
return Ok(())
}
let total_fee : u64 = spendable let total_fee : u64 = spendable
.iter() .iter()
.enumerate() .enumerate()
@ -110,11 +110,10 @@ impl ServerImpl {
.map(|(_, s)| s.excess.clone()) .map(|(_, s)| s.excess.clone())
.collect(); .collect();
let excess_sum = secp::add_blinds(&excesses)?; let tx = wallet::assemble_tx(&self.wallet, &inputs, &outputs, self.get_fee_base(), total_fee, &excesses)?;
let tx = self.wallet.assemble_tx(&inputs, &outputs, total_fee, &excess_sum)?;
self.node.post_tx(&tx)?; self.node.post_tx(&tx)?;
locked_state.clear();
Ok(()) Ok(())
} }
@ -124,26 +123,39 @@ impl Server for ServerImpl {
/// Implements the 'swap' API /// Implements the 'swap' API
fn swap(&self, swap: SwapReq) -> Result<Value> { fn swap(&self, swap: SwapReq) -> Result<Value> {
// Verify commitment signature to ensure caller owns the output // Verify commitment signature to ensure caller owns the output
let _ = swap.comsig.verify(&swap.onion.commit, &swap.msg) let serialized_onion = swap.onion.serialize()
.map_err(|_| jsonrpc_core::Error::internal_error())?;
let _ = swap.comsig.verify(&swap.onion.commit, &serialized_onion)
.map_err(|_| jsonrpc_core::Error::invalid_params("ComSignature invalid"))?; .map_err(|_| jsonrpc_core::Error::invalid_params("ComSignature invalid"))?;
// Verify that commitment is unspent // Verify that commitment is unspent
let input = self.node.build_input(&swap.onion.commit) let input = node::build_input(&self.node, &swap.onion.commit)
.map_err(|_| jsonrpc_core::Error::internal_error())?; .map_err(|_| jsonrpc_core::Error::internal_error())?;
let input = input.ok_or(jsonrpc_core::Error::invalid_params("Commitment not found"))?; let input = input.ok_or(jsonrpc_core::Error::invalid_params("Commitment not found"))?;
let peeled = onion::peel_layer(&swap.onion, &self.server_config.key) let peeled = swap.onion.peel_layer(&self.server_config.key)
.map_err(|e| jsonrpc_core::Error::invalid_params(e.message()))?; .map_err(|e| jsonrpc_core::Error::invalid_params(e.message()))?;
// Verify the fee meets the minimum
let fee: u64 = peeled.0.fee.into(); let fee: u64 = peeled.0.fee.into();
if fee < self.get_minimum_swap_fee() { if fee < self.get_minimum_swap_fee() {
return Err(jsonrpc_core::Error::invalid_params("Fee does not meet minimum")); return Err(jsonrpc_core::Error::invalid_params("Fee does not meet minimum"));
} }
// Calculate final output commitment
let output_commit = secp::add_excess(&swap.onion.commit, &peeled.0.excess) let output_commit = secp::add_excess(&swap.onion.commit, &peeled.0.excess)
.map_err(|_| jsonrpc_core::Error::internal_error())?; .map_err(|_| jsonrpc_core::Error::internal_error())?;
let output_commit = secp::sub_value(&output_commit, fee)
.map_err(|_| jsonrpc_core::Error::internal_error())?;
// Verify the bullet proof and build the final output
let output = match peeled.0.rangeproof { let output = match peeled.0.rangeproof {
Some(r) => Some(Output::new(OutputFeatures::Plain, output_commit, r)), Some(r) => {
let secp = Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
secp.verify_bullet_proof(output_commit, r, None)
.map_err(|_| jsonrpc_core::Error::invalid_params("RangeProof invalid"))?;
Some(Output::new(OutputFeatures::Plain, output_commit, r))
},
None => None None => None
}; };
SERVER_STATE.lock().unwrap().push(Submission{ SERVER_STATE.lock().unwrap().push(Submission{
@ -158,11 +170,9 @@ impl Server for ServerImpl {
} }
/// Spin up the JSON-RPC web server /// Spin up the JSON-RPC web server
pub fn listen<F>(server_config: &ServerConfig, wallet: &Wallet, shutdown_signal: F) -> std::result::Result<(), Box<dyn std::error::Error>> pub fn listen(server_config: &ServerConfig, wallet: Arc<dyn Wallet>, node: Arc<dyn GrinNode>, stop_state: &Arc<StopState>) -> std::result::Result<(), Box<dyn std::error::Error>>
where
F: futures::future::Future<Output = ()> + Send + 'static,
{ {
let server_impl = Arc::new(ServerImpl::new(server_config.clone(), wallet.clone(), GrinNode::new(&server_config))); let server_impl = Arc::new(ServerImpl::new(server_config.clone(), stop_state.clone(), wallet.clone(), node.clone()));
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.extend_with(ServerImpl::to_delegate(server_impl.as_ref().clone())); io.extend_with(ServerImpl::to_delegate(server_impl.as_ref().clone()));
@ -179,28 +189,47 @@ where
.start_http(&server_config.addr) .start_http(&server_config.addr)
.expect("Unable to start RPC server"); .expect("Unable to start RPC server");
std::thread::spawn(move || { let close_handle = server.close_handle();
let config = server_config.clone();
let round_handle = std::thread::spawn(move || {
let mut secs = 0;
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(30)); // todo: Graceful shutdown if server_impl.as_ref().stop_state.is_stopped() {
close_handle.close();
break;
}
std::thread::sleep(std::time::Duration::from_secs(1));
secs = (secs + 1) % config.interval_s;
if secs == 0 {
let _ = server_impl.as_ref().execute_round(); let _ = server_impl.as_ref().execute_round();
secs = 0;
}
} }
}); });
let close_handle = server.close_handle();
std::thread::spawn(move || {
futures::executor::block_on(shutdown_signal);
close_handle.close();
});
server.wait(); server.wait();
round_handle.join().unwrap();
Ok(()) Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{config, onion, secp, server, types, wallet}; use crate::config::ServerConfig;
use grin_core::core::FeeFields; use crate::node::{GrinNode, MockGrinNode};
use crate::onion::test_util::{self, Hop};
use crate::secp::{self, ComSignature, PublicKey, Secp256k1};
use crate::server::{self, SwapReq};
use crate::types::Payload;
use crate::wallet::{MockWallet, Wallet};
use grin_core::core::{Committed, FeeFields, Transaction};
use grin_util::StopState;
use std::net::TcpListener; use std::net::TcpListener;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::thread; use std::thread;
@ -213,9 +242,15 @@ mod tests {
} }
/// Spin up a temporary web service, query the API, then cleanup and return response /// Spin up a temporary web service, query the API, then cleanup and return response
fn make_request(server_key: secp::SecretKey, req: String) -> Result<String, Box<dyn std::error::Error>> { fn make_request(
let server_config = config::ServerConfig { server_key: secp::SecretKey,
wallet: Arc<dyn Wallet>,
node: Arc<dyn GrinNode>,
req: String
) -> Result<String, Box<dyn std::error::Error>> {
let server_config = ServerConfig {
key: server_key, key: server_key,
interval_s: 1,
addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?, addr: TcpListener::bind("127.0.0.1:0")?.local_addr()?,
grin_node_url: "127.0.0.1:3413".parse()?, grin_node_url: "127.0.0.1:3413".parse()?,
wallet_owner_url: "127.0.0.1:3420".parse()? wallet_owner_url: "127.0.0.1:3420".parse()?
@ -225,11 +260,17 @@ mod tests {
let (shutdown_sender, shutdown_receiver) = futures::channel::oneshot::channel(); let (shutdown_sender, shutdown_receiver) = futures::channel::oneshot::channel();
let uri = format!("http://{}/v1", server_config.addr); let uri = format!("http://{}/v1", server_config.addr);
let wallet = wallet::Wallet::open_wallet(&server_config.wallet_owner_url, &grin_util::ZeroingString::from("wallet_pass"))?; let stop_state = Arc::new(StopState::new());
let stop_state_clone = stop_state.clone();
// Spawn the server task // Spawn the server task
threaded_rt.spawn(async move { threaded_rt.spawn(async move {
server::listen(&server_config, &wallet, async { shutdown_receiver.await.ok(); }).unwrap() server::listen(&server_config, wallet, node, &stop_state).unwrap()
});
threaded_rt.spawn(async move {
futures::executor::block_on(shutdown_receiver).unwrap();
stop_state_clone.stop();
}); });
// Wait for listener // Wait for listener
@ -247,6 +288,9 @@ mod tests {
let response = threaded_rt.block_on(do_request)?; let response = threaded_rt.block_on(do_request)?;
let response_str: String = threaded_rt.block_on(body_to_string(response)); let response_str: String = threaded_rt.block_on(body_to_string(response));
// Wait for at least one round to execute
thread::sleep(Duration::from_millis(1500));
shutdown_sender.send(()).ok(); shutdown_sender.send(()).ok();
// Wait for shutdown // Wait for shutdown
@ -257,52 +301,110 @@ mod tests {
} }
/// Single hop to demonstrate request validation and onion unwrapping. /// Single hop to demonstrate request validation and onion unwrapping.
/// UTXO creation and bulletproof generation reserved for milestones 2 & 3.
#[test] #[test]
fn swap_lifecycle() -> Result<(), Box<dyn std::error::Error>> { fn swap_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
let secp = Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
let server_key = secp::random_secret(); let server_key = secp::random_secret();
let secp = secp::Secp256k1::new(); let value: u64 = 200_000_000;
let value: u64 = 100; let fee: u64= 50_000_000;
let fee: u64= 10;
let blind = secp::random_secret(); let blind = secp::random_secret();
let commitment = secp::commit(value, &blind)?; let commitment = secp::commit(value, &blind)?;
let session_key = secp::random_secret(); let hop_excess = secp::random_secret();
let nonce = secp::random_secret();
let hop = types::Hop { let mut final_blind = blind.clone();
pubkey: secp::PublicKey::from_secret_key(&secp, &server_key)?, final_blind.add_assign(&secp, &hop_excess).unwrap();
payload: types::Payload{ let proof = secp.bullet_proof(value - fee, final_blind.clone(), nonce.clone(), nonce.clone(), None, None);
let hop = Hop {
pubkey: PublicKey::from_secret_key(&secp, &server_key)?,
payload: Payload{
excess: hop_excess,
fee: FeeFields::from(fee as u32),
rangeproof: Some(proof),
}
};
let hops: Vec<Hop> = vec![hop];
let session_key = secp::random_secret();
let onion_packet = test_util::create_onion(&commitment, &session_key, &hops)?;
let comsig = ComSignature::sign(value, &blind, &onion_packet.serialize()?)?;
let swap = SwapReq{
onion: onion_packet,
comsig: comsig,
};
let wallet = MockWallet{};
let mut mut_node = MockGrinNode::new();
mut_node.add_default_utxo(&commitment);
let node = Arc::new(mut_node);
let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", serde_json::json!(swap));
println!("Request: {}", req);
let response = make_request(server_key, Arc::new(wallet), node.clone(), req)?;
let expected = "{\"jsonrpc\":\"2.0\",\"result\":\"success\",\"id\":\"1\"}\n";
assert_eq!(response, expected);
// check that the transaction was posted
let posted_txns = node.get_posted_txns();
assert_eq!(posted_txns.len(), 1);
let posted_txn : Transaction = posted_txns.into_iter().next().unwrap();
let input_commit = posted_txn.inputs_committed().into_iter().next().unwrap();
assert_eq!(input_commit, commitment);
Ok(())
}
/// Returns "Commitment not found" when there's no matching output in the UTXO set.
#[test]
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error>> {
let secp = Secp256k1::new();
let server_key = secp::random_secret();
let value: u64 = 200_000_000;
let fee: u64= 50_000_000;
let blind = secp::random_secret();
let commitment = secp::commit(value, &blind)?;
let hop = Hop {
pubkey: PublicKey::from_secret_key(&secp, &server_key)?,
payload: Payload{
excess: secp::random_secret(), excess: secp::random_secret(),
fee: FeeFields::from(fee as u32), fee: FeeFields::from(fee as u32),
rangeproof: None, rangeproof: None,
} }
}; };
let hops: Vec<types::Hop> = vec![hop]; let hops: Vec<Hop> = vec![hop];
let onion_packet = onion::create_onion(&commitment, &session_key, &hops)?; let session_key = secp::random_secret();
let msg : Vec<u8> = vec![0u8, 1u8, 2u8, 3u8]; let onion_packet = test_util::create_onion(&commitment, &session_key, &hops)?;
let comsig = secp::ComSignature::sign(value, &blind, &msg)?; let comsig = ComSignature::sign(value, &blind, &onion_packet.serialize()?)?;
let swap = server::SwapReq{ let swap = SwapReq{
onion: onion_packet, onion: onion_packet,
msg: msg,
comsig: comsig, comsig: comsig,
}; };
let wallet = MockWallet{};
let node = MockGrinNode::new();
let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", serde_json::json!(swap)); let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", serde_json::json!(swap));
let response = make_request(server_key, req)?; let response = make_request(server_key, Arc::new(wallet), Arc::new(node), req)?;
let expected = "{\"jsonrpc\":\"2.0\",\"result\":\"success\",\"id\":\"1\"}\n"; let expected = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Commitment not found\"},\"id\":\"1\"}\n";
assert_eq!(response, expected); assert_eq!(response, expected);
Ok(()) Ok(())
} }
// TODO: Test bulletproof verification and test minimum fee
#[test] #[test]
fn swap_bad_request() -> Result<(), Box<dyn std::error::Error>> { fn swap_bad_request() -> Result<(), Box<dyn std::error::Error>> {
let wallet = MockWallet{};
let node = MockGrinNode::new();
let params = "{ \"param\": \"Not a valid Swap request\" }"; let params = "{ \"param\": \"Not a valid Swap request\" }";
let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", params); let req = format!("{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}", params);
let response = make_request(secp::random_secret(), req)?; let response = make_request(secp::random_secret(), Arc::new(wallet), Arc::new(node), req)?;
let expected = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `onion`.\"},\"id\":\"1\"}\n"; let expected = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid params: missing field `onion`.\"},\"id\":\"1\"}\n";
assert_eq!(response, expected); assert_eq!(response, expected);
Ok(()) Ok(())
} }
// milestone 2 - add tests to cover invalid comsig's & inputs not in utxo set
} }

View file

@ -1,15 +1,9 @@
use crate::error::{ErrorKind, Result}; use crate::error::{Result, StdResult};
use crate::secp::{Commitment, PublicKey, RangeProof, SecretKey, Secp256k1}; use crate::secp::{self, RangeProof, SecretKey};
use crate::ser::{self, BinReader, Readable, Reader, Writeable, Writer};
use grin_core::core::FeeFields; use grin_core::core::FeeFields;
use grin_util::{self, ToHex}; use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde::ser::SerializeStruct;
use std::fmt;
use std::io::Cursor;
pub type RawBytes = Vec<u8>;
const CURRENT_VERSION : u8 = 0; const CURRENT_VERSION : u8 = 0;
@ -20,15 +14,30 @@ pub struct Payload {
pub rangeproof: Option<RangeProof>, pub rangeproof: Option<RangeProof>,
} }
impl Readable for Payload { impl Payload {
fn read<R: Reader>(reader: &mut R) -> Result<Payload> { pub fn deserialize(bytes: &Vec<u8>) -> Result<Payload> {
let version = reader.read_u8()?; let payload: Payload = grin_core::ser::deserialize_default(&mut &bytes[..])?;
if version != CURRENT_VERSION { Ok(payload)
return Err(ErrorKind::UnsupportedPayload.into());
} }
let excess = SecretKey::read(reader)?; #[cfg(test)]
let fee = FeeFields::try_from(reader.read_u64()?)?; pub fn serialize(&self) -> Result<Vec<u8>> {
let mut vec = vec![];
ser::serialize_default(&mut vec, &self)?;
Ok(vec)
}
}
impl Readable for Payload {
fn read<R: Reader>(reader: &mut R) -> StdResult<Payload, ser::Error> {
let version = reader.read_u8()?;
if version != CURRENT_VERSION {
return Err(ser::Error::UnsupportedProtocolVersion);
}
let excess = secp::read_secret_key(reader)?;
let fee = FeeFields::try_from(reader.read_u64()?)
.map_err(|_| ser::Error::CorruptedData)?;
let rangeproof = if reader.read_u8()? == 0 { let rangeproof = if reader.read_u8()? == 0 {
None None
} else { } else {
@ -45,7 +54,7 @@ impl Readable for Payload {
} }
impl Writeable for Payload { impl Writeable for Payload {
fn write<W: Writer>(&self, writer: &mut W) -> Result<()> { fn write<W: Writer>(&self, writer: &mut W) -> StdResult<(), ser::Error> {
writer.write_u8(CURRENT_VERSION)?; writer.write_u8(CURRENT_VERSION)?;
writer.write_fixed_bytes(&self.excess)?; writer.write_fixed_bytes(&self.excess)?;
writer.write_u64(self.fee.into())?; writer.write_u64(self.fee.into())?;
@ -61,113 +70,3 @@ impl Writeable for Payload {
Ok(()) Ok(())
} }
} }
pub fn serialize_payload(payload: &Payload) -> Result<Vec<u8>> {
ser::ser_vec(&payload)
}
pub fn deserialize_payload(bytes: &Vec<u8>) -> Result<Payload> {
let mut cursor = Cursor::new(&bytes);
let mut reader = BinReader::new(&mut cursor);
Payload::read(&mut reader)
}
pub struct Hop {
pub pubkey: PublicKey,
pub payload: Payload,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Onion {
pub ephemeral_pubkey: PublicKey,
pub commit: Commitment,
pub enc_payloads: Vec<RawBytes>,
}
impl serde::ser::Serialize for Onion {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let mut state = serializer.serialize_struct("Onion", 3)?;
let secp = Secp256k1::new();
state.serialize_field("pubkey", &self.ephemeral_pubkey.serialize_vec(&secp, true).to_hex())?;
state.serialize_field("commit", &self.commit.to_hex())?;
let hex_payloads: Vec<String> = self.enc_payloads.iter().map(|v| v.to_hex()).collect();
state.serialize_field("data", &hex_payloads)?;
state.end()
}
}
impl<'de> serde::de::Deserialize<'de> for Onion {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Pubkey,
Commit,
Data
}
struct OnionVisitor;
impl<'de> serde::de::Visitor<'de> for OnionVisitor {
type Value = Onion;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an Onion")
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut pubkey = None;
let mut commit = None;
let mut data = None;
while let Some(key) = map.next_key()? {
match key {
Field::Pubkey => {
let val: String = map.next_value()?;
let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
let secp = Secp256k1::new();
pubkey = Some(PublicKey::from_slice(&secp, &vec[..]).map_err(serde::de::Error::custom)?);
}
Field::Commit => {
let val: String = map.next_value()?;
let vec = grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
commit = Some(Commitment::from_vec(vec));
}
Field::Data => {
let val: Vec<String> = map.next_value()?;
let mut vec: Vec<Vec<u8>> = Vec::new();
for hex in val {
vec.push(grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?);
}
data = Some(vec);
}
}
}
Ok(Onion {
ephemeral_pubkey: pubkey.unwrap(),
commit: commit.unwrap(),
enc_payloads: data.unwrap(),
})
}
}
const FIELDS: &[&str] = &[
"pubkey",
"commit",
"data"
];
deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor)
}
}

View file

@ -1,36 +1,102 @@
use crate::error::Result; use crate::error::{ErrorKind, Result};
use crate::secp::{self, SecretKey}; use crate::secp::{self, SecretKey};
use grin_api::client; use grin_api::client;
use grin_api::json_rpc::{build_request, Request, Response}; use grin_api::json_rpc::{build_request, Request, Response};
use grin_core::core::{FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TxKernel}; use grin_core::core::{FeeFields, Input, Inputs, KernelFeatures, Output, Transaction, TransactionBody, TxKernel};
use grin_core::libtx::secp_ser;
use grin_keychain::BlindingFactor; use grin_keychain::BlindingFactor;
use grin_util::ZeroingString; use grin_util::ZeroingString;
use grin_wallet_api::Token; use grin_wallet_api::Token;
use secp256k1zkp::{ContextFlag, Secp256k1};
use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc;
pub trait Wallet : Send + Sync {
/// Builds an output for the wallet with the provided amount.
fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)>;
}
/// Builds and verifies a 'Transaction' using the provided components.
pub fn assemble_tx(wallet: &Arc<dyn Wallet>, inputs: &Vec<Input>, outputs: &Vec<Output>, fee_base: u64, total_fee: u64, excesses: &Vec<SecretKey>) -> Result<Transaction> {
let secp = Secp256k1::with_caps(ContextFlag::Commit);
let txn_inputs = Inputs::from(inputs.as_slice());
let mut txn_outputs = outputs.clone();
let mut txn_excesses = excesses.clone();
let mut kernel_fee = total_fee;
// calculate fee required if we add our own output
let fee_required = TransactionBody::weight_by_iok(inputs.len() as u64, (outputs.len() + 1) as u64, 1) * fee_base;
// calculate fee to spend the output to ensure there's enough leftover to cover the fees for spending it
let fee_to_spend = TransactionBody::weight_by_iok(1, 0, 0) * fee_base;
// collect any leftover fees
if total_fee > fee_required + fee_to_spend {
let amount = total_fee - fee_required;
kernel_fee -= amount;
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)?;
txn_excesses.push(output_excess);
}
// generate random transaction offset
let offset = secp::random_secret();
// calculate kernel excess
let kern_excess = secp.blind_sum(txn_excesses, vec![offset.clone()])?;
// build and verify kernel
let mut kernel = TxKernel::with_features(KernelFeatures::Plain {
fee: FeeFields::new(0, kernel_fee).unwrap(),
});
let msg = kernel.msg_to_sign()?;
kernel.excess = secp::commit(0, &kern_excess)?;
kernel.excess_sig = secp::sign(&kern_excess, &msg)?;
kernel.verify()?;
// assemble the transaction
let tx = Transaction::new(txn_inputs, &txn_outputs, &[kernel])
.with_offset(BlindingFactor::from_secret_key(offset));
Ok(tx)
}
/// HTTP (JSONRPC) implementation of the 'Wallet' trait.
#[derive(Clone)]
pub struct HttpWallet {
wallet_owner_url: SocketAddr,
token: Token,
}
const ENDPOINT: &str = "/v3/owner"; const ENDPOINT: &str = "/v3/owner";
#[derive(Clone)] impl HttpWallet {
pub struct HTTPWalletClient { /// Calls the 'open_wallet' using the RPC API.
wallet_owner_url: SocketAddr, pub fn open_wallet(wallet_owner_url: &SocketAddr, wallet_pass: &ZeroingString) -> Result<HttpWallet> {
} let open_wallet_params = json!({
"name": null,
"password": wallet_pass.to_string()
});
let token: Token = HttpWallet::send_json_request(&wallet_owner_url, "open_wallet", &open_wallet_params)?;
impl HTTPWalletClient { Ok(HttpWallet {
/// Create a new client that will communicate with the given grin node wallet_owner_url: wallet_owner_url.clone(),
pub fn new(wallet_owner_url: &SocketAddr) -> HTTPWalletClient { token: token,
HTTPWalletClient { })
wallet_owner_url: wallet_owner_url.to_owned(),
}
} }
fn send_json_request<D: serde::de::DeserializeOwned>( fn send_json_request<D: serde::de::DeserializeOwned>(
&self, wallet_owner_url: &SocketAddr,
method: &str, method: &str,
params: &serde_json::Value, params: &serde_json::Value,
) -> Result<D> { ) -> Result<D> {
let url = format!("http://{}{}", self.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 = client::post::<Request, Response>(url.as_str(), None, &req)?; let res = client::post::<Request, Response>(url.as_str(), None, &req)?;
let parsed = res.clone().into_result()?; let parsed = res.clone().into_result()?;
@ -38,54 +104,51 @@ impl HTTPWalletClient {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputWithBlind {
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::blind_from_hex"
)]
blind: BlindingFactor,
output: Output,
}
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)> {
let req_json = json!({
"token": self.token.keychain_mask.clone().unwrap().0,
"features": "Plain",
"amount": amount
});
let output: OutputWithBlind = HttpWallet::send_json_request(&self.wallet_owner_url, "build_output", &req_json)?;
Ok((output.blind, output.output))
}
}
use grin_core::core::OutputFeatures;
/// HTTP (JSONRPC) implementation of the 'Wallet' trait.
#[derive(Clone)] #[derive(Clone)]
pub struct Wallet { pub struct MockWallet {
pub client: HTTPWalletClient,
pub token: Token,
} }
impl Wallet { impl Wallet for MockWallet {
pub fn open_wallet(wallet_owner_url: &SocketAddr, wallet_pass: &ZeroingString) -> Result<Wallet> { /// Builds an 'Output' for the wallet using the 'build_output' RPC API.
let client = HTTPWalletClient::new(&wallet_owner_url); fn build_output(&self, amount: u64) -> Result<(BlindingFactor, Output)> {
let secp = Secp256k1::new();
let open_wallet_params = json!({ let blind = secp::random_secret();
"name": null, let commit = secp::commit(amount, &blind)?;
"password": wallet_pass.to_string() let proof = secp.bullet_proof(
}); amount,
let token: Token = client.send_json_request("open_wallet", &open_wallet_params)?; blind.clone(),
secp::random_secret(),
Ok(Wallet { secp::random_secret(),
client: client, None,
token: token, None,
}) );
} let output = Output::new(OutputFeatures::Plain, commit.clone(), proof);
Ok((BlindingFactor::from_secret_key(blind), output))
/// Builds and verifies a <code>Transaction</code> using the provided components.
pub fn assemble_tx(&self, inputs: &Vec<Input>, outputs: &Vec<Output>, total_fee: u64, total_excess: &SecretKey) -> Result<Transaction> {
// generate random transaction offset
let offset = secp::random_secret();
// build and verify kernel
let kern_excess = secp::sub_blinds(&total_excess, &offset)?;
let kern = Wallet::build_kernel(total_fee, &kern_excess)?;
// assemble the transaction
let tx = Transaction::new(Inputs::from(inputs.as_slice()), &outputs, &[kern])
.with_offset(BlindingFactor::from_secret_key(offset));
Ok(tx)
}
/// Builds and verifies a <code>TxKernel</code> from the provided fee and excess.
fn build_kernel(total_fee: u64, kern_excess: &SecretKey) -> Result<TxKernel> {
let mut kernel = TxKernel::with_features(KernelFeatures::Plain {
fee: FeeFields::new(0, total_fee).unwrap(),
});
let msg = kernel.msg_to_sign()?;
kernel.excess = secp::commit(0, &kern_excess)?;
kernel.excess_sig = secp::sign(&kern_excess, &msg)?;
kernel.verify()?;
Ok(kernel)
} }
} }