use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "wallet")]
use super::nut00::PreMintSecrets;
use super::nut00::{BlindSignature, BlindedMessage, Proofs};
use super::ProofsMethods;
use crate::Amount;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
DHKE(#[from] crate::dhke::Error),
#[error(transparent)]
Amount(#[from] crate::amount::Error),
}
#[cfg(feature = "wallet")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PreSwap {
pub pre_mint_secrets: PreMintSecrets,
pub swap_request: SwapRequest,
pub derived_secret_count: u32,
pub fee: Amount,
pub p2bk_secret_keys: Option<Vec<crate::nuts::nut01::SecretKey>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SwapRequest {
inputs: Proofs,
outputs: Vec<BlindedMessage>,
}
impl SwapRequest {
pub fn new(inputs: Proofs, outputs: Vec<BlindedMessage>) -> Self {
Self {
inputs: inputs.without_dleqs(),
outputs,
}
}
pub fn inputs(&self) -> &Proofs {
&self.inputs
}
pub fn inputs_mut(&mut self) -> &mut Proofs {
&mut self.inputs
}
pub fn outputs(&self) -> &Vec<BlindedMessage> {
&self.outputs
}
pub fn outputs_mut(&mut self) -> &mut Vec<BlindedMessage> {
&mut self.outputs
}
pub fn input_amount(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.inputs.iter().map(|proof| proof.amount),
)?)
}
pub fn output_amount(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.outputs.iter().map(|proof| proof.amount),
)?)
}
}
impl super::nut10::SpendingConditionVerification for SwapRequest {
fn inputs(&self) -> &Proofs {
&self.inputs
}
fn sig_all_msg_to_sign(&self) -> String {
let mut msg = String::new();
for proof in &self.inputs {
msg.push_str(&proof.secret.to_string());
msg.push_str(&proof.c.to_hex());
}
for output in &self.outputs {
msg.push_str(&output.amount.to_string());
msg.push_str(&output.blinded_secret.to_hex());
}
msg
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SwapResponse {
pub signatures: Vec<BlindSignature>,
}
impl SwapResponse {
pub fn new(promises: Vec<BlindSignature>) -> Self {
Self {
signatures: promises,
}
}
pub fn promises_amount(&self) -> Result<Amount, Error> {
Ok(Amount::try_sum(
self.signatures
.iter()
.map(|BlindSignature { amount, .. }| *amount),
)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SWAP_REQUEST_JSON: &str = r#"{
"inputs": [
{
"amount": 2,
"id": "00bfa73302d12ffd",
"secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
"C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd",
"witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}"
},
{
"amount": 4,
"id": "00bfa73302d12ffd",
"secret": "[\"P2PK\",{\"nonce\":\"d7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950304\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
"C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd"
}
],
"outputs": [
{
"amount": 2,
"id": "00bfa73302d12ffd",
"B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
}
]
}"#;
#[test]
fn test_swap_request_inputs_outputs_getters() {
let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
let inputs = req.inputs();
assert_eq!(inputs.len(), 2, "expected 2 inputs");
assert_eq!(u64::from(inputs[0].amount), 2);
assert_eq!(u64::from(inputs[1].amount), 4);
let outputs = req.outputs();
assert_eq!(outputs.len(), 1, "expected 1 output");
assert_eq!(u64::from(outputs[0].amount), 2);
}
#[test]
fn test_swap_request_inputs_outputs_getters_via_new() {
let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
let inputs_clone = req.inputs().clone();
let outputs_clone = req.outputs().clone();
let rebuilt = SwapRequest::new(inputs_clone, outputs_clone);
assert_eq!(rebuilt.inputs().len(), 2);
assert_eq!(rebuilt.outputs().len(), 1);
assert!(!rebuilt.inputs().is_empty());
assert!(!rebuilt.outputs().is_empty());
}
#[test]
fn test_swap_request_outputs_mut_updates_outputs() {
let mut req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
let output = req.outputs()[0].clone();
req.outputs_mut().push(output);
assert_eq!(req.outputs().len(), 2);
}
#[test]
fn test_swap_request_amounts() {
let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
assert_eq!(req.input_amount().unwrap(), Amount::from(6));
assert_eq!(req.output_amount().unwrap(), Amount::from(2));
}
#[test]
fn test_swap_response_promises_amount() {
let response: SwapResponse = serde_json::from_str(
r#"{
"signatures": [
{
"amount": 8,
"id": "00bfa73302d12ffd",
"C_": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd"
},
{
"amount": 4,
"id": "00bfa73302d12ffd",
"C_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
}
]
}"#,
)
.unwrap();
assert_eq!(response.promises_amount().unwrap(), Amount::from(12));
}
}