use std::str::FromStr;
use serde::{Deserialize, Serialize};
use super::nut01::PublicKey;
use crate::{nut11, nut14};
pub mod spending_conditions;
pub use spending_conditions::{Conditions, SpendingConditions};
pub mod secret;
pub use secret::Secret;
pub mod error;
pub use error::Error;
pub mod tag;
pub use tag::{Tag, TagKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RefundPath {
pub pubkeys: Vec<PublicKey>,
pub required_sigs: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SpendingRequirements {
pub preimage_needed: bool,
pub pubkeys: Vec<PublicKey>,
pub required_sigs: u64,
pub refund_path: Option<RefundPath>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Kind {
P2PK,
HTLC,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SecretData {
nonce: String,
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<Vec<Vec<String>>>,
}
impl SecretData {
pub fn new<S, V>(data: S, tags: Option<V>) -> Self
where
S: Into<String>,
V: Into<Vec<Vec<String>>>,
{
let nonce = crate::secret::Secret::generate().to_string();
Self {
nonce,
data: data.into(),
tags: tags.map(Into::into),
}
}
pub fn nonce(&self) -> &str {
&self.nonce
}
pub fn data(&self) -> &str {
&self.data
}
pub fn tags(&self) -> Option<&Vec<Vec<String>>> {
self.tags.as_ref()
}
}
pub(crate) fn get_pubkeys_and_required_sigs(
secret: &Secret,
current_time: u64,
) -> Result<SpendingRequirements, Error> {
debug_assert!(
secret.kind() == Kind::P2PK || secret.kind() == Kind::HTLC,
"get_pubkeys_and_required_sigs called with invalid kind - this is a bug"
);
let conditions: Conditions = secret
.secret_data()
.tags()
.cloned()
.unwrap_or_default()
.try_into()?;
let locktime_passed = conditions
.locktime
.map(|locktime| locktime < current_time)
.unwrap_or(false);
match secret.kind() {
Kind::P2PK => {
let mut primary_keys = vec![];
let data_pubkey = PublicKey::from_str(secret.secret_data().data())?;
primary_keys.push(data_pubkey);
if let Some(additional_keys) = &conditions.pubkeys {
primary_keys.extend(additional_keys.clone());
}
let primary_num_sigs_required = conditions.num_sigs.unwrap_or(1);
let refund_path = if locktime_passed {
if let Some(refund_keys) = &conditions.refund_keys {
Some(RefundPath {
pubkeys: refund_keys.clone(),
required_sigs: conditions.num_sigs_refund.unwrap_or(1),
})
} else {
Some(RefundPath {
pubkeys: vec![],
required_sigs: 0,
})
}
} else {
None
};
Ok(SpendingRequirements {
preimage_needed: false,
pubkeys: primary_keys,
required_sigs: primary_num_sigs_required,
refund_path,
})
}
Kind::HTLC => {
let pubkeys = conditions.pubkeys.clone().unwrap_or_default();
let required_sigs = if pubkeys.is_empty() {
0
} else {
conditions.num_sigs.unwrap_or(1)
};
let refund_path = if locktime_passed {
if let Some(refund_keys) = &conditions.refund_keys {
Some(RefundPath {
pubkeys: refund_keys.clone(),
required_sigs: conditions.num_sigs_refund.unwrap_or(1),
})
} else {
Some(RefundPath {
pubkeys: vec![],
required_sigs: 0,
})
}
} else {
None
};
Ok(SpendingRequirements {
preimage_needed: true,
pubkeys,
required_sigs,
refund_path,
})
}
}
}
use super::Proofs;
pub trait SpendingConditionVerification {
fn inputs(&self) -> &Proofs;
fn sig_all_msg_to_sign(&self) -> String;
fn has_at_least_one_sig_all(&self) -> Result<bool, Error> {
for proof in self.inputs() {
if let Ok(spending_conditions) = super::SpendingConditions::try_from(&proof.secret) {
let has_sig_all = match spending_conditions {
super::SpendingConditions::P2PKConditions { conditions, .. } => conditions
.map(|c| c.sig_flag == super::SigFlag::SigAll)
.unwrap_or(false),
super::SpendingConditions::HTLCConditions { conditions, .. } => conditions
.map(|c| c.sig_flag == super::SigFlag::SigAll)
.unwrap_or(false),
};
if has_sig_all {
return Ok(true);
}
} else if proof.witness.is_some() {
return Err(Error::NUT11(nut11::Error::IncorrectWitnessKind));
}
}
Ok(false)
}
fn verify_all_inputs_match_for_sig_all(&self) -> Result<(), Error> {
let inputs = self.inputs();
let first_input = inputs.first().ok_or(Error::SpendConditionsNotMet)?;
let first_secret = Secret::try_from(&first_input.secret)?;
let first_kind = first_secret.kind();
let first_data = first_secret.secret_data().data();
let first_tags = first_secret.secret_data().tags();
let first_conditions =
super::Conditions::try_from(first_tags.cloned().unwrap_or_default())?;
if first_conditions.sig_flag != super::SigFlag::SigAll {
return Err(Error::SpendConditionsNotMet);
}
for proof in inputs.iter().skip(1) {
let secret = Secret::try_from(&proof.secret)?;
if secret.kind() != first_kind {
return Err(Error::SpendConditionsNotMet);
}
if secret.secret_data().data() != first_data {
return Err(Error::SpendConditionsNotMet);
}
if secret.secret_data().tags() != first_tags {
return Err(Error::SpendConditionsNotMet);
}
}
Ok(())
}
fn verify_spending_conditions(&self) -> Result<(), Error> {
if self.has_at_least_one_sig_all()? {
self.verify_full_sig_all_check()
} else {
self.verify_inputs_individually()
}
}
fn verify_full_sig_all_check(&self) -> Result<(), Error> {
debug_assert!(
self.has_at_least_one_sig_all()?,
"verify_full_sig_all_check() called on proofs without SIG_ALL. This shouldn't happen"
);
self.verify_all_inputs_match_for_sig_all()?;
let first_input = self.inputs().first().ok_or(Error::SpendConditionsNotMet)?;
let first_secret =
Secret::try_from(&first_input.secret).map_err(|_| Error::IncorrectSecretKind)?;
match first_secret.kind() {
Kind::P2PK => {
nut11::verify_sig_all_p2pk(first_input, self.sig_all_msg_to_sign())?;
}
Kind::HTLC => {
nut14::verify_sig_all_htlc(first_input, self.sig_all_msg_to_sign())?;
}
}
Ok(())
}
fn verify_inputs_individually(&self) -> Result<(), Error> {
debug_assert!(
!(self.has_at_least_one_sig_all()?),
"verify_inputs_individually() called on SIG_ALL. This shouldn't happen"
);
for proof in self.inputs() {
if let Ok(secret) = Secret::try_from(&proof.secret) {
if let Ok(conditions) = super::Conditions::try_from(
secret.secret_data().tags().cloned().unwrap_or_default(),
) {
debug_assert!(
conditions.sig_flag != super::SigFlag::SigAll,
"verify_inputs_individually called with SIG_ALL proof - this is a bug"
);
}
match secret.kind() {
Kind::P2PK => {
proof.verify_p2pk()?;
}
Kind::HTLC => {
proof.verify_htlc()?;
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::assert_eq;
use std::str::FromStr;
use super::*;
#[test]
fn test_secret_serialize() {
let secret_data = SecretData::new(
"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198".to_string(),
Some(vec![vec![
"key".to_string(),
"value1".to_string(),
"value2".to_string(),
]]),
);
let secret = Secret::new(Kind::P2PK, secret_data.clone());
let secret_str = format!(
r#"["P2PK",{{"nonce":"{}","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}}]"#,
secret_data.nonce(),
);
assert_eq!(serde_json::to_string(&secret).unwrap(), secret_str);
}
#[test]
fn test_secret_round_trip_serialization() {
let original_secret = Secret::new(
Kind::P2PK,
SecretData::new(
"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198".to_string(),
None::<Vec<Vec<String>>>,
),
);
let serialized = serde_json::to_string(&original_secret).unwrap();
let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();
assert_eq!(original_secret, deserialized_secret);
let cashu_secret = crate::secret::Secret::from_str(&serialized).unwrap();
let deserialized_from_cashu: Secret = TryFrom::try_from(&cashu_secret).unwrap();
assert_eq!(original_secret, deserialized_from_cashu);
}
#[test]
fn test_htlc_secret_round_trip() {
let payment_hash = "5c23fc3aec9d985bd5fc88ca8bceaccc52cf892715dd94b42b84f1b43350751e";
let secret_data = SecretData::new(payment_hash.to_string(), None::<Vec<Vec<String>>>);
let original_secret = Secret::new(Kind::HTLC, secret_data.clone());
let serialized = serde_json::to_string(&original_secret).unwrap();
let expected_json = format!(
r#"["HTLC",{{"nonce":"{}","data":"{}"}}]"#,
secret_data.nonce(),
payment_hash
);
assert_eq!(serialized, expected_json);
let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();
assert_eq!(original_secret, deserialized_secret);
assert_eq!(deserialized_secret.kind(), Kind::HTLC);
assert_eq!(deserialized_secret.secret_data().data, payment_hash);
}
}