generate random pubkey for each payload (https://github.com/mimblewimble/mwixnet/issues/19)

This commit is contained in:
scilio
2023-05-22 20:13:30 -04:00
parent 76532c899e
commit d99aa6ec7c
14 changed files with 695 additions and 262 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ use secp256k1zkp::rand::thread_rng;
use thiserror::Error;
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct ComSignature {
pub_nonce: Commitment,
s: SecretKey,
+1 -2
View File
@@ -174,7 +174,6 @@ pub mod test_util {
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::dalek::test_util;
use crate::crypto::dalek::test_util::rand_keypair;
use grin_core::ser::{self, ProtocolVersion};
use grin_util::ToHex;
@@ -191,7 +190,7 @@ mod tests {
#[test]
fn pubkey_test() -> Result<(), Box<dyn std::error::Error>> {
// Test from_hex
let rand_pk = test_util::rand_keypair().1;
let rand_pk = rand_keypair().1;
let pk_from_hex = DalekPublicKey::from_hex(rand_pk.0.to_hex().as_str()).unwrap();
assert_eq!(rand_pk.0, pk_from_hex.0);
+2 -2
View File
@@ -90,7 +90,7 @@ pub mod test_util {
pub fn proof(
value: u64,
fee: u64,
fee: u32,
input_blind: &SecretKey,
hop_excesses: &Vec<&SecretKey>,
) -> (Commitment, RangeProof) {
@@ -101,7 +101,7 @@ pub mod test_util {
blind.add_assign(&secp, &hop_excess).unwrap();
}
let out_value = value - fee;
let out_value = value - (fee as u64);
let rp = secp.bullet_proof(
out_value,
+1 -1
View File
@@ -28,7 +28,7 @@ mod servers;
mod store;
mod tor;
mod tx;
mod types;
mod util;
mod wallet;
const DEFAULT_INTERVAL: u32 = 12 * 60 * 60;
+129 -120
View File
@@ -1,17 +1,17 @@
use crate::crypto::secp::{self, Commitment, SecretKey};
use crate::types::Payload;
use crate::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use crate::onion::OnionError::{InvalidKeyLength, SerializationError};
use crate::util::{read_optional, vec_to_array, write_optional};
use chacha20::cipher::{NewCipher, StreamCipher};
use chacha20::{ChaCha20, Key, Nonce};
use grin_core::core::FeeFields;
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use grin_util::{self, ToHex};
use hmac::digest::InvalidLength;
use hmac::{Hmac, Mac};
use serde::ser::SerializeStruct;
use serde::Deserialize;
use sha2::{Digest, Sha256};
use std::convert::TryInto;
use sha2::Sha256;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::result::Result;
@@ -21,6 +21,8 @@ use x25519_dalek::{PublicKey as xPublicKey, SharedSecret, StaticSecret};
type HmacSha256 = Hmac<Sha256>;
type RawBytes = Vec<u8>;
const CURRENT_ONION_VERSION: u8 = 0;
/// A data packet with layers of encryption
#[derive(Clone, Debug)]
pub struct Onion {
@@ -53,8 +55,59 @@ impl Hash for Onion {
}
}
fn vec_to_32_byte_arr(v: Vec<u8>) -> Result<[u8; 32], OnionError> {
v.try_into().map_err(|_| InvalidKeyLength)
/// A single, decrypted/peeled layer of an Onion.
#[derive(Debug, Clone)]
pub struct Payload {
pub next_ephemeral_pk: xPublicKey,
pub excess: SecretKey,
pub fee: FeeFields,
pub rangeproof: Option<RangeProof>,
}
impl Payload {
pub fn deserialize(bytes: &Vec<u8>) -> Result<Payload, ser::Error> {
let payload: Payload = ser::deserialize_default(&mut &bytes[..])?;
Ok(payload)
}
#[cfg(test)]
pub fn serialize(&self) -> Result<Vec<u8>, ser::Error> {
let mut vec = vec![];
ser::serialize_default(&mut vec, &self)?;
Ok(vec)
}
}
impl Readable for Payload {
fn read<R: Reader>(reader: &mut R) -> Result<Payload, ser::Error> {
let version = reader.read_u8()?;
if version != CURRENT_ONION_VERSION {
return Err(ser::Error::UnsupportedProtocolVersion);
}
let next_ephemeral_pk =
xPublicKey::from(vec_to_array::<32>(&reader.read_fixed_bytes(32)?)?);
let excess = secp::read_secret_key(reader)?;
let fee = FeeFields::try_from(reader.read_u64()?).map_err(|_| ser::Error::CorruptedData)?;
let rangeproof = read_optional(reader)?;
Ok(Payload {
next_ephemeral_pk,
excess,
fee,
rangeproof,
})
}
}
impl Writeable for Payload {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(CURRENT_ONION_VERSION)?;
writer.write_fixed_bytes(&self.next_ephemeral_pk.as_bytes())?;
writer.write_fixed_bytes(&self.excess)?;
writer.write_u64(self.fee.into())?;
write_optional(writer, &self.rangeproof)?;
Ok(())
}
}
/// An onion with a layer decrypted
@@ -74,8 +127,8 @@ impl Onion {
}
/// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload
pub fn peel_layer(&self, secret_key: &SecretKey) -> Result<PeeledOnion, OnionError> {
let shared_secret = StaticSecret::from(secret_key.0).diffie_hellman(&self.ephemeral_pubkey);
pub fn peel_layer(&self, server_key: &SecretKey) -> Result<PeeledOnion, OnionError> {
let shared_secret = StaticSecret::from(server_key.0).diffie_hellman(&self.ephemeral_pubkey);
let mut cipher = new_stream_cipher(&shared_secret)?;
let mut decrypted_bytes = self.enc_payloads[0].clone();
@@ -95,15 +148,6 @@ impl Onion {
})
.collect();
let blinding_factor = calc_blinding_factor(&shared_secret, &self.ephemeral_pubkey)?;
let ephemeral_key = StaticSecret::from(
*blinding_factor
.diffie_hellman(&self.ephemeral_pubkey)
.as_bytes(),
);
let ephemeral_pubkey = xPublicKey::from(&ephemeral_key);
let mut commitment = self.commit.clone();
commitment = secp::add_excess(&commitment, &decrypted_payload.excess)
.map_err(|e| OnionError::CalcCommitError(e))?;
@@ -111,7 +155,7 @@ impl Onion {
.map_err(|e| OnionError::CalcCommitError(e))?;
let peeled_onion = Onion {
ephemeral_pubkey,
ephemeral_pubkey: decrypted_payload.next_ephemeral_pk,
commit: commitment.clone(),
enc_payloads,
};
@@ -122,22 +166,6 @@ impl Onion {
}
}
fn calc_blinding_factor(
shared_secret: &SharedSecret,
ephemeral_pubkey: &xPublicKey,
) -> Result<StaticSecret, OnionError> {
let mut hasher = Sha256::default();
hasher.update(ephemeral_pubkey.as_bytes());
hasher.update(shared_secret.as_bytes());
let hashed: [u8; 32] = hasher
.finalize()
.as_slice()
.try_into()
.map_err(|_| InvalidKeyLength)?;
Ok(StaticSecret::from(hashed))
}
fn new_stream_cipher(shared_secret: &SharedSecret) -> Result<ChaCha20, OnionError> {
let mut mu_hmac = HmacSha256::new_from_slice(b"MWIXNET")?;
mu_hmac.update(shared_secret.as_bytes());
@@ -164,8 +192,7 @@ impl Writeable for Onion {
impl Readable for Onion {
fn read<R: Reader>(reader: &mut R) -> Result<Onion, ser::Error> {
let pubkey_bytes: [u8; 32] =
vec_to_32_byte_arr(reader.read_fixed_bytes(32)?).map_err(|_| ser::Error::CountError)?;
let pubkey_bytes: [u8; 32] = vec_to_array(&reader.read_fixed_bytes(32)?)?;
let ephemeral_pubkey = xPublicKey::from(pubkey_bytes);
let commit = Commitment::read(reader)?;
let mut enc_payloads: Vec<RawBytes> = Vec::new();
@@ -236,9 +263,9 @@ impl<'de> serde::de::Deserialize<'de> for Onion {
let vec =
grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
pubkey =
Some(xPublicKey::from(vec_to_32_byte_arr(vec).map_err(|_| {
serde::de::Error::custom("Invalid length pubkey")
})?));
Some(xPublicKey::from(vec_to_array::<32>(&vec).map_err(
|_| serde::de::Error::custom("Invalid length pubkey"),
)?));
}
Field::Commit => {
let val: String = map.next_value()?;
@@ -303,10 +330,9 @@ impl From<ser::Error> for OnionError {
#[cfg(test)]
pub mod test_util {
use super::{Onion, OnionError, RawBytes};
use super::{Onion, OnionError, Payload, RawBytes};
use crate::crypto::secp::test_util::{rand_commit, rand_proof};
use crate::crypto::secp::{random_secret, Commitment, SecretKey};
use crate::types::Payload;
use chacha20::cipher::StreamCipher;
use grin_core::core::FeeFields;
@@ -317,54 +343,59 @@ pub mod test_util {
#[derive(Clone)]
pub struct Hop {
pub pubkey: xPublicKey,
pub payload: Payload,
pub server_pubkey: xPublicKey,
pub excess: SecretKey,
pub fee: FeeFields,
pub rangeproof: Option<RangeProof>,
}
pub fn new_hop(
server_key: &SecretKey,
hop_excess: &SecretKey,
fee: u64,
fee: u32,
proof: Option<RangeProof>,
) -> Hop {
Hop {
pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
payload: Payload {
excess: hop_excess.clone(),
fee: FeeFields::from(fee as u32),
rangeproof: proof,
},
server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
excess: hop_excess.clone(),
fee: FeeFields::from(fee as u32),
rangeproof: proof,
}
}
/*
Choose random xi for each node ni and create a Payload (Pi) for each containing xi
Build a rangeproof for Cn=Cin+(Σx1...n)*G and include it in payload Pn
Choose random initial ephemeral keypair (r1, R1)
Derive remaining ephemeral keypairs such that ri+1=ri*Sha256(Ri||si) where si=ECDH(Ri, Ki)
For each node ni, use ChaCha20 stream cipher with key=HmacSha256("MWIXNET"||si) and nonce "NONCE1234567" to encrypt payloads Pi...n
*/
/// Create an Onion for the Commitment, encrypting the payload for each hop
pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, OnionError> {
let initial_key = StaticSecret::new(&mut thread_rng());
let mut ephemeral_key = initial_key.clone();
if hops.is_empty() {
return Ok(Onion {
ephemeral_pubkey: xPublicKey::from([0u8; 32]),
commit: commitment.clone(),
enc_payloads: vec![],
});
}
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
let mut enc_payloads: Vec<RawBytes> = Vec::new();
for hop in hops {
let shared_secret = ephemeral_key.diffie_hellman(&hop.pubkey);
let ephemeral_pubkey = xPublicKey::from(&ephemeral_key);
let blinding_factor = super::calc_blinding_factor(&shared_secret, &ephemeral_pubkey)?;
let mut ephemeral_sk = StaticSecret::from(random_secret().0);
let onion_ephemeral_pk = xPublicKey::from(&ephemeral_sk);
for i in 0..hops.len() {
let hop = &hops[i];
let shared_secret = ephemeral_sk.diffie_hellman(&hop.server_pubkey);
shared_secrets.push(shared_secret);
enc_payloads.push(hop.payload.serialize()?);
ephemeral_key = StaticSecret::from(
*ephemeral_key
.diffie_hellman(&xPublicKey::from(&blinding_factor))
.as_bytes(),
);
ephemeral_sk = StaticSecret::from(random_secret().0);
let next_ephemeral_pk = if i < (hops.len() - 1) {
xPublicKey::from(&ephemeral_sk)
} else {
xPublicKey::from([0u8; 32])
};
let payload = Payload {
next_ephemeral_pk,
excess: hop.excess.clone(),
fee: hop.fee.clone(),
rangeproof: hop.rangeproof.clone(),
};
enc_payloads.push(payload.serialize()?);
}
for i in (0..shared_secrets.len()).rev() {
@@ -375,7 +406,7 @@ pub mod test_util {
}
let onion = Onion {
ephemeral_pubkey: xPublicKey::from(&initial_key),
ephemeral_pubkey: onion_ephemeral_pk,
commit: commitment.clone(),
enc_payloads,
};
@@ -387,70 +418,56 @@ pub mod test_util {
let mut hops = Vec::new();
let k = (thread_rng().next_u64() % 5) + 1;
for i in 0..k {
let hop = Hop {
pubkey: xPublicKey::from(random_secret().0),
payload: Payload {
excess: random_secret(),
fee: FeeFields::from(thread_rng().next_u32()),
rangeproof: if i == (k - 1) {
Some(rand_proof())
} else {
None
},
},
let rangeproof = if i == (k - 1) {
Some(rand_proof())
} else {
None
};
let hop = new_hop(
&random_secret(),
&random_secret(),
thread_rng().next_u32(),
rangeproof,
);
hops.push(hop);
}
create_onion(&commit, &hops).unwrap()
}
/// Calculates the expected next ephemeral pubkey after peeling a layer off of the Onion.
pub fn next_ephemeral_pubkey(
onion: &Onion,
server_key: &SecretKey,
) -> Result<xPublicKey, OnionError> {
let shared_secret =
StaticSecret::from(server_key.0.clone()).diffie_hellman(&onion.ephemeral_pubkey);
let blinding_factor = super::calc_blinding_factor(&shared_secret, &onion.ephemeral_pubkey)?;
let mul = blinding_factor.diffie_hellman(&onion.ephemeral_pubkey);
Ok(xPublicKey::from(&StaticSecret::from(*mul.as_bytes())))
}
}
#[cfg(test)]
pub mod tests {
use super::test_util::{self, Hop};
use crate::crypto::secp;
use crate::types::Payload;
use super::test_util::{new_hop, Hop};
use super::*;
use crate::crypto::secp::random_secret;
use grin_core::core::FeeFields;
use x25519_dalek::{PublicKey as xPublicKey, StaticSecret};
/// Test end-to-end Onion creation and unwrapping logic.
#[test]
fn onion() {
let total_fee: u64 = 10;
let fee_per_hop: u64 = 2;
let fee_per_hop: u32 = 2;
let in_value: u64 = 1000;
let out_value: u64 = in_value - total_fee;
let blind = secp::random_secret();
let blind = random_secret();
let commitment = secp::commit(in_value, &blind).unwrap();
let mut hops: Vec<Hop> = Vec::new();
let mut keys: Vec<secp::SecretKey> = Vec::new();
let mut keys: Vec<SecretKey> = Vec::new();
let mut final_commit = secp::commit(out_value, &blind).unwrap();
let mut final_blind = blind.clone();
for i in 0..5 {
keys.push(secp::random_secret());
keys.push(random_secret());
let excess = secp::random_secret();
let excess = random_secret();
let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
final_blind.add_assign(&secp, &excess).unwrap();
final_commit = secp::add_excess(&final_commit, &excess).unwrap();
let proof = if i == 4 {
let n1 = secp::random_secret();
let n1 = random_secret();
let rp = secp.bullet_proof(
out_value,
final_blind.clone(),
@@ -465,20 +482,15 @@ pub mod tests {
None
};
hops.push(Hop {
pubkey: xPublicKey::from(&StaticSecret::from(keys[i].0.clone())),
payload: Payload {
excess,
fee: FeeFields::from(fee_per_hop as u32),
rangeproof: proof,
},
});
let hop = new_hop(&keys[i], &excess, fee_per_hop, proof);
hops.push(hop);
}
let mut onion_packet = test_util::create_onion(&commitment, &hops).unwrap();
let mut payload = Payload {
excess: secp::random_secret(),
next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(),
excess: random_secret(),
fee: FeeFields::from(fee_per_hop as u32),
rangeproof: None,
};
@@ -489,10 +501,7 @@ pub mod tests {
}
assert!(payload.rangeproof.is_some());
assert_eq!(
payload.rangeproof.unwrap(),
hops[4].payload.rangeproof.unwrap()
);
assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap());
assert_eq!(secp::commit(out_value, &final_blind).unwrap(), final_commit);
assert_eq!(payload.fee, FeeFields::from(fee_per_hop as u32));
}
+77 -35
View File
@@ -324,9 +324,12 @@ mod tests {
use crate::crypto::secp::{self, Commitment};
use crate::node::mock::MockGrinNode;
use crate::onion::test_util;
use crate::MixClient;
use crate::{DalekPublicKey, MixClient};
use crate::onion::test_util::Hop;
use ::function_name::named;
use secp256k1zkp::pedersen::RangeProof;
use secp256k1zkp::SecretKey;
use std::collections::HashSet;
use std::sync::Arc;
@@ -341,50 +344,89 @@ mod tests {
}};
}
struct ServerVars {
fee: u32,
sk: SecretKey,
pk: DalekPublicKey,
excess: SecretKey,
}
impl ServerVars {
fn new(fee: u32) -> Self {
let (sk, pk) = dalek::test_util::rand_keypair();
let excess = secp::random_secret();
ServerVars {
fee,
sk,
pk,
excess,
}
}
fn build_hop(&self, proof: Option<RangeProof>) -> Hop {
test_util::new_hop(&self.sk, &self.excess, self.fee, proof)
}
}
/// Tests the happy path for a 3 server setup.
///
/// Servers:
/// * Swap Server - Simulated by test
/// * Mixer 1 - Internal MixServerImpl directly called by test
/// * Mixer 2 - Final MixServerImpl called by Mixer 1
#[test]
#[named]
fn mix_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
let node = Arc::new(MockGrinNode::new_with_utxos(&vec![&input_commit]));
// Setup Input(s)
let input1_value: u64 = 200_000_000;
let input1_blind = secp::random_secret();
let input1_commit = secp::commit(input1_value, &input1_blind)?;
let input_commits = vec![&input1_commit];
let (swap_sk, swap_pk) = dalek::test_util::rand_keypair();
let (mix1_sk, mix1_pk) = dalek::test_util::rand_keypair();
let (mix2_sk, mix2_pk) = dalek::test_util::rand_keypair();
let swap_hop_excess = secp::random_secret();
let swap_hop = test_util::new_hop(&swap_sk, &swap_hop_excess, fee, None);
let mix1_hop_excess = secp::random_secret();
let mix1_hop = test_util::new_hop(&mix1_sk, &mix1_hop_excess, fee, None);
let mix2_hop_excess = secp::random_secret();
let (out_commit, proof) = secp::test_util::proof(
value,
fee * 3,
&blind,
&vec![&swap_hop_excess, &mix1_hop_excess, &mix2_hop_excess],
// Setup Servers
let (swap_vars, mix1_vars, mix2_vars) = (
ServerVars::new(50_000_000),
ServerVars::new(50_000_000),
ServerVars::new(50_000_000),
);
let mix2_hop = test_util::new_hop(&mix2_sk, &mix2_hop_excess, fee, Some(proof));
let onion = test_util::create_onion(&input_commit, &vec![swap_hop, mix1_hop, mix2_hop])?;
let (mixer2_client, mixer2_wallet) =
super::test_util::new_mixer(&mix2_sk, (&mix1_sk, &mix1_pk), &None, &node);
let (mixer1_client, mixer1_wallet) = super::test_util::new_mixer(
&mix1_sk,
(&swap_sk, &swap_pk),
&Some((mix2_pk.clone(), mixer2_client.clone())),
let node = Arc::new(MockGrinNode::new_with_utxos(&input_commits));
let (mixer2_client, mixer2_wallet) = super::test_util::new_mixer(
&mix2_vars.sk,
(&mix1_vars.sk, &mix1_vars.pk),
&None,
&node,
);
// Emulate the swap server peeling the onion and then calling mix1
let mix1_onion = onion.peel_layer(&swap_sk)?;
let (mixer1_client, mixer1_wallet) = super::test_util::new_mixer(
&mix1_vars.sk,
(&swap_vars.sk, &swap_vars.pk),
&Some((mix2_vars.pk.clone(), mixer2_client.clone())),
&node,
);
// Build rangeproof
let (output_commit, proof) = secp::test_util::proof(
input1_value,
swap_vars.fee + mix1_vars.fee + mix2_vars.fee,
&input1_blind,
&vec![&swap_vars.excess, &mix1_vars.excess, &mix2_vars.excess],
);
// Create Onion
let onion = test_util::create_onion(
&input1_commit,
&vec![
swap_vars.build_hop(None),
mix1_vars.build_hop(None),
mix2_vars.build_hop(Some(proof)),
],
)?;
// Simulate the swap server peeling the onion and then calling mix1
let mix1_onion = onion.peel_layer(&swap_vars.sk)?;
let (mixed_indices, mixed_components) =
mixer1_client.mix_outputs(&vec![mix1_onion.onion.clone()])?;
@@ -396,7 +438,7 @@ mod tests {
.iter()
.map(|o| o.identifier.commit.clone())
.collect();
assert!(output_commits.contains(&out_commit));
assert!(output_commits.contains(&output_commit));
assert_eq!(mixer1_wallet.built_outputs().len(), 1);
assert!(output_commits.contains(mixer1_wallet.built_outputs().get(0).unwrap()));
+18 -17
View File
@@ -153,7 +153,7 @@ impl SwapServer for SwapServerImpl {
output_commit: peeled.onion.commit,
rangeproof: peeled.payload.rangeproof,
input,
fee,
fee: fee as u64,
onion: peeled.onion,
status: SwapStatus::Unprocessed,
},
@@ -355,6 +355,7 @@ mod tests {
use grin_core::core::{Committed, Input, Output, OutputFeatures, Transaction, Weighting};
use secp256k1zkp::key::ZERO_KEY;
use std::sync::Arc;
use x25519_dalek::PublicKey as xPublicKey;
macro_rules! assert_error_type {
($result:expr, $error_type:pat) => {
@@ -385,7 +386,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -394,7 +395,7 @@ mod tests {
let (output_commit, proof) = secp::test_util::proof(value, fee, &blind, &vec![&hop_excess]);
let hop = test_util::new_hop(&server_key, &hop_excess, fee, Some(proof));
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
let onion = test_util::create_onion(&input_commit, &vec![hop.clone()])?;
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
let node: Arc<MockGrinNode> = Arc::new(MockGrinNode::new_with_utxos(&vec![&input_commit]));
@@ -407,9 +408,9 @@ mod tests {
output_commit: output_commit.clone(),
rangeproof: Some(proof),
input: Input::new(OutputFeatures::Plain, input_commit.clone()),
fee,
fee: fee as u64,
onion: Onion {
ephemeral_pubkey: test_util::next_ephemeral_pubkey(&onion, &server_key)?,
ephemeral_pubkey: xPublicKey::from([0u8; 32]),
commit: output_commit.clone(),
enc_payloads: vec![],
},
@@ -462,13 +463,13 @@ mod tests {
let node: Arc<MockGrinNode> = Arc::new(MockGrinNode::new_with_utxos(&vec![&input_commit]));
// Swapper data
let swap_fee: u64 = 50_000_000;
let swap_fee: u32 = 50_000_000;
let (swap_sk, _swap_pk) = dalek::test_util::rand_keypair();
let swap_hop_excess = secp::random_secret();
let swap_hop = test_util::new_hop(&swap_sk, &swap_hop_excess, swap_fee, None);
// Mixer data
let mixer_fee: u64 = 30_000_000;
let mixer_fee: u32 = 30_000_000;
let (mixer_sk, mixer_pk) = dalek::test_util::rand_keypair();
let mixer_hop_excess = secp::random_secret();
let (output_commit, proof) = secp::test_util::proof(
@@ -493,7 +494,7 @@ mod tests {
output_commit.clone(),
proof.clone(),
)],
kernels: vec![tx::build_kernel(&mixer_hop_excess, mixer_fee)?],
kernels: vec![tx::build_kernel(&mixer_hop_excess, mixer_fee as u64)?],
};
mock_mixer.set_response(
&vec![mixer_onion.clone()],
@@ -532,7 +533,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -567,7 +568,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -603,7 +604,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -638,7 +639,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -670,7 +671,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -709,7 +710,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -745,7 +746,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 50_000_000;
let fee: u32 = 50_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -777,7 +778,7 @@ mod tests {
let test_dir = init_test!();
let value: u64 = 200_000_000;
let fee: u64 = 1_000_000;
let fee: u32 = 1_000_000;
let blind = secp::random_secret();
let input_commit = secp::commit(value, &blind)?;
@@ -796,7 +797,7 @@ mod tests {
assert_eq!(
Err(SwapError::FeeTooLow {
minimum_fee: 12_500_000,
actual_fee: fee
actual_fee: fee as u64
}),
result
);
-1
View File
@@ -221,7 +221,6 @@ mod tests {
"{{\"jsonrpc\": \"2.0\", \"method\": \"swap\", \"params\": [{}], \"id\": \"1\"}}",
serde_json::json!(swap)
);
println!("Request: {}", req);
let response = make_request(server, req)?;
let expected = "{\"jsonrpc\":\"2.0\",\"result\":\"success\",\"id\":\"1\"}\n";
assert_eq!(response, expected);
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::crypto::secp::{self, Commitment, RangeProof, SecretKey};
use crate::onion::Onion;
use crate::types::{read_optional, write_optional};
use crate::util::{read_optional, write_optional};
use grin_core::core::hash::Hash;
use grin_core::core::Input;
-82
View File
@@ -1,82 +0,0 @@
use crate::crypto::secp::{self, RangeProof, SecretKey};
use grin_core::core::FeeFields;
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use serde::{Deserialize, Serialize};
const CURRENT_VERSION: u8 = 0;
/// Writes an optional value as '1' + value if Some, or '0' if None
pub fn write_optional<O: Writeable, W: Writer>(
writer: &mut W,
o: &Option<O>,
) -> Result<(), ser::Error> {
match &o {
Some(o) => {
writer.write_u8(1)?;
o.write(writer)?;
}
None => writer.write_u8(0)?,
};
Ok(())
}
/// Reads an optional value as '1' + value if Some, or '0' if None
pub fn read_optional<O: Readable, R: Reader>(reader: &mut R) -> Result<Option<O>, ser::Error> {
let o = if reader.read_u8()? == 0 {
None
} else {
Some(O::read(reader)?)
};
Ok(o)
}
// todo: Belongs in Onion
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Payload {
pub excess: SecretKey,
pub fee: FeeFields,
pub rangeproof: Option<RangeProof>,
}
impl Payload {
pub fn deserialize(bytes: &Vec<u8>) -> Result<Payload, ser::Error> {
let payload: Payload = ser::deserialize_default(&mut &bytes[..])?;
Ok(payload)
}
#[cfg(test)]
pub fn serialize(&self) -> Result<Vec<u8>, ser::Error> {
let mut vec = vec![];
ser::serialize_default(&mut vec, &self)?;
Ok(vec)
}
}
impl Readable for Payload {
fn read<R: Reader>(reader: &mut R) -> Result<Payload, ser::Error> {
let version = reader.read_u8()?;
if version != CURRENT_VERSION {
return Err(ser::Error::UnsupportedProtocolVersion);
}
let excess = secp::read_secret_key(reader)?;
let fee = FeeFields::try_from(reader.read_u64()?).map_err(|_| ser::Error::CorruptedData)?;
let rangeproof = read_optional(reader)?;
Ok(Payload {
excess,
fee,
rangeproof,
})
}
}
impl Writeable for Payload {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(CURRENT_VERSION)?;
writer.write_fixed_bytes(&self.excess)?;
writer.write_u64(self.fee.into())?;
write_optional(writer, &self.rangeproof)?;
Ok(())
}
}
+164
View File
@@ -0,0 +1,164 @@
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
/// Writes an optional value as '1' + value if Some, or '0' if None
///
/// This function is used to serialize an optional value into a Writer. If the option
/// contains Some value, it writes '1' followed by the serialized value. If the option
/// is None, it just writes '0'.
///
/// # Arguments
///
/// * `writer` - A Writer instance where the data will be written.
/// * `o` - The Optional value that will be written.
///
/// # Returns
///
/// * If successful, returns Ok with nothing.
/// * If an error occurs during writing, returns Err wrapping the error.
///
/// # Example
///
/// ```
/// let mut writer = vec![];
/// let optional_value: Option<u32> = Some(10);
/// write_optional(&mut writer, &optional_value);
/// assert_eq!(buf, &[1, 0, 0, 0, 10]);
/// ```
pub fn write_optional<O: Writeable, W: Writer>(
writer: &mut W,
o: &Option<O>,
) -> Result<(), ser::Error> {
match &o {
Some(o) => {
writer.write_u8(1)?;
o.write(writer)?;
}
None => writer.write_u8(0)?,
};
Ok(())
}
/// Reads an optional value as '1' + value if Some, or '0' if None
///
/// This function is used to deserialize an optional value from a Reader. If the first byte
/// read is '0', it returns None. If the first byte is '1', it reads the next value and
/// returns Some(value).
///
/// # Arguments
///
/// * `reader` - A Reader instance from where the data will be read.
///
/// # Returns
///
/// * If successful, returns Ok wrapping an optional value. If the first byte read was '0',
/// returns None. If it was '1', returns Some(value).
/// * If an error occurs during reading, returns Err wrapping the error.
///
/// # Example
///
/// ```
/// let mut buf: &[u8] = &[1, 0, 0, 0, 10];
/// let mut reader = BinReader::new(&mut buf, ProtocolVersion::local(), DeserializationMode::default());
/// let optional_value: Option<u32> = read_optional(&mut reader).unwrap();
/// assert_eq!(optional_value, Some(10));
/// ```
pub fn read_optional<O: Readable, R: Reader>(reader: &mut R) -> Result<Option<O>, ser::Error> {
let o = if reader.read_u8()? == 0 {
None
} else {
Some(O::read(reader)?)
};
Ok(o)
}
/// Convert a vector to an array of size `S`.
///
/// # Arguments
///
/// * `vec` - The input vector.
///
/// # Returns
///
/// * If successful, returns an `Ok` wrapping an array of size `S` containing
/// the first `S` bytes of `vec`.
/// * If `vec` is smaller than `S`, returns an `Err` indicating a count error.
///
/// # Example
///
/// ```
/// let v = vec![0, 1, 2, 3, 4, 5];
/// let a = vec_to_array::<4>(&v).unwrap();
/// assert_eq!(a, [0, 1, 2, 3]);
/// ```
pub fn vec_to_array<const S: usize>(vec: &Vec<u8>) -> Result<[u8; S], ser::Error> {
if vec.len() < S {
return Err(ser::Error::CountError);
}
let arr: [u8; S] = vec[0..S].try_into().unwrap();
Ok(arr)
}
#[cfg(test)]
mod tests {
use super::*;
use grin_core::ser::{BinReader, BinWriter, DeserializationMode, ProtocolVersion};
#[test]
fn test_write_optional() {
// Test with Some value
let mut buf: Vec<u8> = vec![];
let val: Option<u32> = Some(10);
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap();
assert_eq!(buf, &[1, 0, 0, 0, 10]); // 1 for Some, then 10 as a little-endian u32
// Test with None value
buf.clear();
let val: Option<u32> = None;
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap();
assert_eq!(buf, &[0]); // 0 for None
}
#[test]
fn test_read_optional() {
// Test with Some value
let mut buf: &[u8] = &[1, 0, 0, 0, 10]; // 1 for Some, then 10 as a little-endian u32
let val: Option<u32> = read_optional(&mut BinReader::new(
&mut buf,
ProtocolVersion::local(),
DeserializationMode::default(),
))
.unwrap();
assert_eq!(val, Some(10));
// Test with None value
buf = &[0]; // 0 for None
let val: Option<u32> = read_optional(&mut BinReader::new(
&mut buf,
ProtocolVersion::local(),
DeserializationMode::default(),
))
.unwrap();
assert_eq!(val, None);
}
#[test]
fn test_vec_to_array_success() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
let a = vec_to_array::<4>(&v).unwrap();
assert_eq!(a, [1, 2, 3, 4]);
}
#[test]
fn test_vec_to_array_too_small() {
let v = vec![1, 2, 3];
let res = vec_to_array::<4>(&v);
assert!(res.is_err());
}
#[test]
fn test_vec_to_array_empty() {
let v = vec![];
let res = vec_to_array::<4>(&v);
assert!(res.is_err());
}
}