mirror of
https://github.com/jwinterm/mwixnet.git
synced 2026-10-19 15:18:32 +02:00
generate random pubkey for each payload (https://github.com/mimblewimble/mwixnet/issues/19)
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user