mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-21 03:21:09 +03:00
add test for every swap failure scenario
This commit is contained in:
parent
2e02d67dbc
commit
6f626ac11e
3 changed files with 312 additions and 87 deletions
15
src/onion.rs
15
src/onion.rs
|
@ -214,20 +214,19 @@ pub mod test_util {
|
||||||
use crate::secp::{Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret};
|
use crate::secp::{Commitment, PublicKey, Secp256k1, SecretKey, SharedSecret};
|
||||||
use crate::types::Payload;
|
use crate::types::Payload;
|
||||||
|
|
||||||
|
use crate::secp;
|
||||||
use chacha20::cipher::StreamCipher;
|
use chacha20::cipher::StreamCipher;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Hop {
|
pub struct Hop {
|
||||||
pub pubkey: PublicKey,
|
pub pubkey: PublicKey,
|
||||||
pub payload: Payload,
|
pub payload: Payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an Onion for the Commitment, encrypting the payload for each hop
|
/// Create an Onion for the Commitment, encrypting the payload for each hop
|
||||||
pub fn create_onion(
|
pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion> {
|
||||||
commitment: &Commitment,
|
|
||||||
session_key: &SecretKey,
|
|
||||||
hops: &Vec<Hop>,
|
|
||||||
) -> Result<Onion> {
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
let session_key = secp::random_secret();
|
||||||
let mut ephemeral_key = session_key.clone();
|
let mut ephemeral_key = session_key.clone();
|
||||||
|
|
||||||
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
|
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
|
||||||
|
@ -251,7 +250,7 @@ pub mod test_util {
|
||||||
}
|
}
|
||||||
|
|
||||||
let onion = Onion {
|
let onion = Onion {
|
||||||
ephemeral_pubkey: PublicKey::from_secret_key(&secp, session_key)?,
|
ephemeral_pubkey: PublicKey::from_secret_key(&secp, &session_key)?,
|
||||||
commit: commitment.clone(),
|
commit: commitment.clone(),
|
||||||
enc_payloads,
|
enc_payloads,
|
||||||
};
|
};
|
||||||
|
@ -287,9 +286,7 @@ pub mod tests {
|
||||||
let blind = secp::random_secret();
|
let blind = secp::random_secret();
|
||||||
let commitment = secp::commit(in_value, &blind).unwrap();
|
let commitment = secp::commit(in_value, &blind).unwrap();
|
||||||
|
|
||||||
let session_key = secp::random_secret();
|
|
||||||
let mut hops: Vec<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();
|
||||||
let mut final_blind = blind.clone();
|
let mut final_blind = blind.clone();
|
||||||
|
@ -327,7 +324,7 @@ pub mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut onion_packet = test_util::create_onion(&commitment, &session_key, &hops).unwrap();
|
let mut onion_packet = test_util::create_onion(&commitment, &hops).unwrap();
|
||||||
|
|
||||||
let mut payload = Payload {
|
let mut payload = Payload {
|
||||||
excess: secp::random_secret(),
|
excess: secp::random_secret(),
|
||||||
|
|
|
@ -201,7 +201,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn swap_success() -> Result<(), Box<dyn std::error::Error>> {
|
fn swap_success() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let commitment = secp::commit(1234, &secp::random_secret())?;
|
let commitment = secp::commit(1234, &secp::random_secret())?;
|
||||||
let onion = test_util::create_onion(&commitment, &secp::random_secret(), &vec![])?;
|
let onion = test_util::create_onion(&commitment, &vec![])?;
|
||||||
let comsig = ComSignature::sign(1234, &secp::random_secret(), &onion.serialize()?)?;
|
let comsig = ComSignature::sign(1234, &secp::random_secret(), &onion.serialize()?)?;
|
||||||
let swap = SwapReq {
|
let swap = SwapReq {
|
||||||
onion: onion.clone(),
|
onion: onion.clone(),
|
||||||
|
@ -241,7 +241,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error>> {
|
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let commitment = secp::commit(1234, &secp::random_secret())?;
|
let commitment = secp::commit(1234, &secp::random_secret())?;
|
||||||
let onion = test_util::create_onion(&commitment, &secp::random_secret(), &vec![])?;
|
let onion = test_util::create_onion(&commitment, &vec![])?;
|
||||||
let comsig = ComSignature::sign(1234, &secp::random_secret(), &onion.serialize()?)?;
|
let comsig = ComSignature::sign(1234, &secp::random_secret(), &onion.serialize()?)?;
|
||||||
let swap = SwapReq {
|
let swap = SwapReq {
|
||||||
onion: onion.clone(),
|
onion: onion.clone(),
|
||||||
|
|
380
src/server.rs
380
src/server.rs
|
@ -35,8 +35,10 @@ pub enum SwapError {
|
||||||
InvalidPayloadLength { expected: usize, found: usize },
|
InvalidPayloadLength { expected: usize, found: usize },
|
||||||
#[error("Commitment Signature is invalid")]
|
#[error("Commitment Signature is invalid")]
|
||||||
InvalidComSignature,
|
InvalidComSignature,
|
||||||
#[error("Rangeproof is missing or invalid")]
|
#[error("Rangeproof is invalid")]
|
||||||
InvalidRangeproof,
|
InvalidRangeproof,
|
||||||
|
#[error("Rangeproof is required but was not supplied")]
|
||||||
|
MissingRangeproof,
|
||||||
#[error("Output {commit:?} does not exist, or is already spent.")]
|
#[error("Output {commit:?} does not exist, or is already spent.")]
|
||||||
CoinNotFound { commit: Commitment },
|
CoinNotFound { commit: Commitment },
|
||||||
#[error("Output {commit:?} is already in the swap list.")]
|
#[error("Output {commit:?} is already in the swap list.")]
|
||||||
|
@ -49,12 +51,19 @@ pub enum SwapError {
|
||||||
UnknownError(String),
|
UnknownError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A MWixnet server
|
||||||
pub trait Server: Send + Sync {
|
pub trait Server: Send + Sync {
|
||||||
|
/// Submit a new output to be swapped.
|
||||||
fn swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError>;
|
fn swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError>;
|
||||||
|
|
||||||
|
/// Iterate through all saved submissions, filter out any inputs that are no longer spendable,
|
||||||
|
/// and assemble the coinswap transaction, posting the transaction to the configured node.
|
||||||
|
///
|
||||||
|
/// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes.
|
||||||
fn execute_round(&self) -> crate::error::Result<()>;
|
fn execute_round(&self) -> crate::error::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The standard MWixnet server implementation
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerImpl {
|
pub struct ServerImpl {
|
||||||
server_config: ServerConfig,
|
server_config: ServerConfig,
|
||||||
|
@ -91,7 +100,6 @@ impl ServerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server for ServerImpl {
|
impl Server for ServerImpl {
|
||||||
/// Submit a new output to be swapped.
|
|
||||||
fn swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError> {
|
fn swap(&self, onion: &Onion, comsig: &ComSignature) -> Result<(), SwapError> {
|
||||||
// milestone 3: check that enc_payloads length matches number of configured servers
|
// milestone 3: check that enc_payloads length matches number of configured servers
|
||||||
if onion.enc_payloads.len() != 1 {
|
if onion.enc_payloads.len() != 1 {
|
||||||
|
@ -142,7 +150,7 @@ impl Server for ServerImpl {
|
||||||
.map_err(|_| SwapError::InvalidRangeproof)?;
|
.map_err(|_| SwapError::InvalidRangeproof)?;
|
||||||
} else {
|
} else {
|
||||||
// milestone 3: only the last hop will have a rangeproof
|
// milestone 3: only the last hop will have a rangeproof
|
||||||
return Err(SwapError::InvalidRangeproof);
|
return Err(SwapError::MissingRangeproof);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut locked = self.submissions.lock().unwrap();
|
let mut locked = self.submissions.lock().unwrap();
|
||||||
|
@ -166,10 +174,6 @@ impl Server for ServerImpl {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate through all saved submissions, filter out any inputs that are no longer spendable,
|
|
||||||
/// and assemble the coinswap transaction, posting the transaction to the configured node.
|
|
||||||
///
|
|
||||||
/// Currently only a single mix node is used. Milestone 3 will include support for multiple mix nodes.
|
|
||||||
fn execute_round(&self) -> crate::error::Result<()> {
|
fn execute_round(&self) -> crate::error::Result<()> {
|
||||||
let mut locked_state = self.submissions.lock().unwrap();
|
let mut locked_state = self.submissions.lock().unwrap();
|
||||||
let next_block_height = self.node.get_chain_height()? + 1;
|
let next_block_height = self.node.get_chain_height()? + 1;
|
||||||
|
@ -272,17 +276,34 @@ mod tests {
|
||||||
use crate::node::mock::MockGrinNode;
|
use crate::node::mock::MockGrinNode;
|
||||||
use crate::onion::test_util::{self, Hop};
|
use crate::onion::test_util::{self, Hop};
|
||||||
use crate::onion::Onion;
|
use crate::onion::Onion;
|
||||||
use crate::secp::{self, ComSignature, PublicKey, Secp256k1, SecretKey};
|
use crate::secp::{
|
||||||
|
self, ComSignature, Commitment, PublicKey, RangeProof, Secp256k1, SecretKey,
|
||||||
|
};
|
||||||
use crate::server::{Server, ServerImpl, Submission, SwapError};
|
use crate::server::{Server, ServerImpl, Submission, SwapError};
|
||||||
use crate::types::Payload;
|
use crate::types::Payload;
|
||||||
use crate::wallet::mock::MockWallet;
|
use crate::wallet::mock::MockWallet;
|
||||||
|
|
||||||
use grin_core::core::{Committed, FeeFields, Input, OutputFeatures, Transaction};
|
use grin_core::core::{Committed, FeeFields, Input, OutputFeatures, Transaction, Weighting};
|
||||||
|
use grin_core::global::{self, ChainTypes};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn new_config(server_key: &SecretKey) -> ServerConfig {
|
macro_rules! assert_error_type {
|
||||||
ServerConfig {
|
($result:expr, $error_type:pat) => {
|
||||||
|
assert!($result.is_err());
|
||||||
|
assert!(if let $error_type = $result.unwrap_err() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_server(
|
||||||
|
server_key: &SecretKey,
|
||||||
|
utxos: &Vec<&Commitment>,
|
||||||
|
) -> (ServerImpl, Arc<MockGrinNode>) {
|
||||||
|
let config = ServerConfig {
|
||||||
key: server_key.clone(),
|
key: server_key.clone(),
|
||||||
interval_s: 1,
|
interval_s: 1,
|
||||||
addr: TcpListener::bind("127.0.0.1:0")
|
addr: TcpListener::bind("127.0.0.1:0")
|
||||||
|
@ -293,53 +314,70 @@ mod tests {
|
||||||
grin_node_secret_path: None,
|
grin_node_secret_path: None,
|
||||||
wallet_owner_url: "127.0.0.1:3420".parse().unwrap(),
|
wallet_owner_url: "127.0.0.1:3420".parse().unwrap(),
|
||||||
wallet_owner_secret_path: None,
|
wallet_owner_secret_path: None,
|
||||||
|
};
|
||||||
|
let wallet = Arc::new(MockWallet {});
|
||||||
|
let mut mut_node = MockGrinNode::new();
|
||||||
|
for utxo in utxos {
|
||||||
|
mut_node.add_default_utxo(&utxo);
|
||||||
|
}
|
||||||
|
let node = Arc::new(mut_node);
|
||||||
|
|
||||||
|
let server = ServerImpl::new(config, wallet.clone(), node.clone());
|
||||||
|
(server, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof(value: u64, fee: u64, input_blind: &SecretKey, hop_excess: &SecretKey) -> RangeProof {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
let nonce = secp::random_secret();
|
||||||
|
|
||||||
|
let mut blind = input_blind.clone();
|
||||||
|
blind.add_assign(&secp, &hop_excess).unwrap();
|
||||||
|
|
||||||
|
secp.bullet_proof(
|
||||||
|
value - fee,
|
||||||
|
blind.clone(),
|
||||||
|
nonce.clone(),
|
||||||
|
nonce.clone(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_hop(
|
||||||
|
server_key: &SecretKey,
|
||||||
|
hop_excess: &SecretKey,
|
||||||
|
fee: u64,
|
||||||
|
proof: Option<RangeProof>,
|
||||||
|
) -> Hop {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
Hop {
|
||||||
|
pubkey: PublicKey::from_secret_key(&secp, &server_key).unwrap(),
|
||||||
|
payload: Payload {
|
||||||
|
excess: hop_excess.clone(),
|
||||||
|
fee: FeeFields::from(fee as u32),
|
||||||
|
rangeproof: proof,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single hop to demonstrate request validation and onion unwrapping.
|
/// Single hop to demonstrate request validation and onion unwrapping.
|
||||||
#[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 value: u64 = 200_000_000;
|
let value: u64 = 200_000_000;
|
||||||
let fee: u64 = 50_000_000;
|
let fee: u64 = 50_000_000;
|
||||||
let blind = secp::random_secret();
|
let blind = secp::random_secret();
|
||||||
let input_commit = secp::commit(value, &blind)?;
|
let input_commit = secp::commit(value, &blind)?;
|
||||||
|
|
||||||
|
let server_key = secp::random_secret();
|
||||||
let hop_excess = secp::random_secret();
|
let hop_excess = secp::random_secret();
|
||||||
let nonce = secp::random_secret();
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
let mut final_blind = blind.clone();
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
final_blind.add_assign(&secp, &hop_excess).unwrap();
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
let proof = secp.bullet_proof(
|
|
||||||
value - fee,
|
|
||||||
final_blind.clone(),
|
|
||||||
nonce.clone(),
|
|
||||||
nonce.clone(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let hop = Hop {
|
let (server, node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
pubkey: PublicKey::from_secret_key(&secp, &server_key)?,
|
server.swap(&onion, &comsig)?;
|
||||||
payload: Payload {
|
|
||||||
excess: hop_excess.clone(),
|
|
||||||
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(&input_commit, &session_key, &hops)?;
|
|
||||||
let comsig = ComSignature::sign(value, &blind, &onion_packet.serialize()?)?;
|
|
||||||
|
|
||||||
let wallet = Arc::new(MockWallet {});
|
|
||||||
let mut mut_node = MockGrinNode::new();
|
|
||||||
mut_node.add_default_utxo(&input_commit);
|
|
||||||
let node = Arc::new(mut_node);
|
|
||||||
|
|
||||||
let server = ServerImpl::new(new_config(&server_key), wallet.clone(), node.clone());
|
|
||||||
server.swap(&onion_packet, &comsig)?;
|
|
||||||
|
|
||||||
// Make sure entry is added to server.
|
// Make sure entry is added to server.
|
||||||
let output_commit = secp::add_excess(&input_commit, &hop_excess)?;
|
let output_commit = secp::add_excess(&input_commit, &hop_excess)?;
|
||||||
|
@ -352,7 +390,7 @@ mod tests {
|
||||||
input: Input::new(OutputFeatures::Plain, input_commit.clone()),
|
input: Input::new(OutputFeatures::Plain, input_commit.clone()),
|
||||||
fee,
|
fee,
|
||||||
onion: Onion {
|
onion: Onion {
|
||||||
ephemeral_pubkey: test_util::next_ephemeral_pubkey(&onion_packet, &server_key)?,
|
ephemeral_pubkey: test_util::next_ephemeral_pubkey(&onion, &server_key)?,
|
||||||
commit: output_commit.clone(),
|
commit: output_commit.clone(),
|
||||||
enc_payloads: vec![],
|
enc_payloads: vec![],
|
||||||
},
|
},
|
||||||
|
@ -374,47 +412,41 @@ mod tests {
|
||||||
let posted_txns = node.get_posted_txns();
|
let posted_txns = node.get_posted_txns();
|
||||||
assert_eq!(posted_txns.len(), 1);
|
assert_eq!(posted_txns.len(), 1);
|
||||||
let posted_txn: Transaction = posted_txns.into_iter().next().unwrap();
|
let posted_txn: Transaction = posted_txns.into_iter().next().unwrap();
|
||||||
let posted_input = posted_txn.inputs_committed().into_iter().next().unwrap();
|
assert!(posted_txn.inputs_committed().contains(&input_commit));
|
||||||
assert_eq!(input_commit, posted_input);
|
assert!(posted_txn.outputs_committed().contains(&output_commit));
|
||||||
|
// todo: check that outputs also contain the commitment generated by our wallet
|
||||||
|
|
||||||
|
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||||
|
posted_txn.validate(Weighting::AsTransaction)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns "Commitment not found" when there's no matching output in the UTXO set.
|
/// Returns InvalidPayloadLength when too many payloads are provided.
|
||||||
#[test]
|
#[test]
|
||||||
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error>> {
|
fn swap_too_many_payloads() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let secp = Secp256k1::new();
|
|
||||||
let server_key = secp::random_secret();
|
|
||||||
|
|
||||||
let value: u64 = 200_000_000;
|
let value: u64 = 200_000_000;
|
||||||
let fee: u64 = 50_000_000;
|
let fee: u64 = 50_000_000;
|
||||||
let blind = secp::random_secret();
|
let blind = secp::random_secret();
|
||||||
let commitment = secp::commit(value, &blind)?;
|
let input_commit = secp::commit(value, &blind)?;
|
||||||
|
|
||||||
let hop = Hop {
|
let server_key = secp::random_secret();
|
||||||
pubkey: PublicKey::from_secret_key(&secp, &server_key)?,
|
let hop_excess = secp::random_secret();
|
||||||
payload: Payload {
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
excess: secp::random_secret(),
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
fee: FeeFields::from(fee as u32),
|
|
||||||
rangeproof: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
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 wallet = Arc::new(MockWallet {});
|
let hops: Vec<Hop> = vec![hop.clone(), hop.clone()]; // Multiple payloads
|
||||||
let node = Arc::new(MockGrinNode::new());
|
let onion = test_util::create_onion(&input_commit, &hops)?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
let server = ServerImpl::new(new_config(&server_key), wallet.clone(), node.clone());
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
let result = server.swap(&onion_packet, &comsig);
|
let result = server.swap(&onion, &comsig);
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SwapError::CoinNotFound {
|
Err(SwapError::InvalidPayloadLength {
|
||||||
commit: commitment.clone()
|
expected: 1,
|
||||||
},
|
found: 2
|
||||||
result.unwrap_err()
|
}),
|
||||||
|
result
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure no entry is added to server.submissions
|
// Make sure no entry is added to server.submissions
|
||||||
|
@ -423,5 +455,201 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test bulletproof verification and test minimum fee
|
/// Returns InvalidComSignature when ComSignature fails to verify.
|
||||||
|
#[test]
|
||||||
|
fn swap_invalid_com_signature() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
|
||||||
|
let wrong_blind = secp::random_secret();
|
||||||
|
let comsig = ComSignature::sign(value, &wrong_blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(Err(SwapError::InvalidComSignature), result);
|
||||||
|
|
||||||
|
// Make sure no entry is added to server.submissions
|
||||||
|
assert!(server.submissions.lock().unwrap().is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns InvalidRangeProof when the rangeproof fails to verify for the commitment.
|
||||||
|
#[test]
|
||||||
|
fn swap_invalid_rangeproof() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let wrong_value = value + 10_000_000;
|
||||||
|
let proof = proof(wrong_value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(Err(SwapError::InvalidRangeproof), result);
|
||||||
|
|
||||||
|
// Make sure no entry is added to server.submissions
|
||||||
|
assert!(server.submissions.lock().unwrap().is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns MissingRangeproof when no rangeproof is provided.
|
||||||
|
#[test]
|
||||||
|
fn swap_missing_rangeproof() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, None);
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(Err(SwapError::MissingRangeproof), result);
|
||||||
|
|
||||||
|
// Make sure no entry is added to server.submissions
|
||||||
|
assert!(server.submissions.lock().unwrap().is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns CoinNotFound when there's no matching output in the UTXO set.
|
||||||
|
#[test]
|
||||||
|
fn swap_utxo_missing() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(
|
||||||
|
Err(SwapError::CoinNotFound {
|
||||||
|
commit: input_commit.clone()
|
||||||
|
}),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure no entry is added to server.submissions
|
||||||
|
assert!(server.submissions.lock().unwrap().is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns AlreadySwapped when trying to swap the same commitment multiple times.
|
||||||
|
#[test]
|
||||||
|
fn swap_already_swapped() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
server.swap(&onion, &comsig)?;
|
||||||
|
|
||||||
|
// Call swap a second time
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(
|
||||||
|
Err(SwapError::AlreadySwapped {
|
||||||
|
commit: input_commit.clone()
|
||||||
|
}),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns PeelOnionFailure when a failure occurs trying to decrypt the onion payload.
|
||||||
|
#[test]
|
||||||
|
fn swap_peel_onion_failure() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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 server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
|
||||||
|
let wrong_server_key = secp::random_secret();
|
||||||
|
let hop = new_hop(&wrong_server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_error_type!(result, SwapError::PeelOnionFailure(_));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns FeeTooLow when the minimum fee is not met.
|
||||||
|
#[test]
|
||||||
|
fn swap_fee_too_low() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let value: u64 = 200_000_000;
|
||||||
|
let fee: u64 = 1_000_000;
|
||||||
|
let blind = secp::random_secret();
|
||||||
|
let input_commit = secp::commit(value, &blind)?;
|
||||||
|
|
||||||
|
let server_key = secp::random_secret();
|
||||||
|
let hop_excess = secp::random_secret();
|
||||||
|
let proof = proof(value, fee, &blind, &hop_excess);
|
||||||
|
let hop = new_hop(&server_key, &hop_excess, fee, Some(proof));
|
||||||
|
|
||||||
|
let onion = test_util::create_onion(&input_commit, &vec![hop])?;
|
||||||
|
let comsig = ComSignature::sign(value, &blind, &onion.serialize()?)?;
|
||||||
|
|
||||||
|
let (server, _node) = new_server(&server_key, &vec![&input_commit]);
|
||||||
|
let result = server.swap(&onion, &comsig);
|
||||||
|
assert_eq!(
|
||||||
|
Err(SwapError::FeeTooLow {
|
||||||
|
minimum_fee: 12_500_000,
|
||||||
|
actual_fee: fee
|
||||||
|
}),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue