mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-23 07:35:03 +01:00
feat: refund multi sig
This commit is contained in:
@@ -178,15 +178,24 @@ impl Proof {
|
|||||||
spending_conditions.locktime,
|
spending_conditions.locktime,
|
||||||
spending_conditions.refund_keys,
|
spending_conditions.refund_keys,
|
||||||
) {
|
) {
|
||||||
|
let needed_refund_sigs = spending_conditions.num_sigs_refund.unwrap_or(1) as usize;
|
||||||
|
|
||||||
|
let mut valid_pubkeys = HashSet::new();
|
||||||
|
|
||||||
// If lock time has passed check if refund witness signature is valid
|
// If lock time has passed check if refund witness signature is valid
|
||||||
if locktime.lt(&unix_time()) {
|
if locktime.lt(&unix_time()) {
|
||||||
for s in witness_signatures.iter() {
|
for s in witness_signatures.iter() {
|
||||||
for v in &refund_keys {
|
for v in &refund_keys {
|
||||||
let sig = Signature::from_str(s).map_err(|_| Error::InvalidSignature)?;
|
let sig = Signature::from_str(s).map_err(|_| Error::InvalidSignature)?;
|
||||||
|
|
||||||
// As long as there is one valid refund signature it can be spent
|
|
||||||
if v.verify(msg, &sig).is_ok() {
|
if v.verify(msg, &sig).is_ok() {
|
||||||
return Ok(());
|
if !valid_pubkeys.insert(v) {
|
||||||
|
return Err(Error::DuplicateSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid_pubkeys.len() >= needed_refund_sigs {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,7 +452,7 @@ pub struct Conditions {
|
|||||||
/// Refund keys
|
/// Refund keys
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub refund_keys: Option<Vec<PublicKey>>,
|
pub refund_keys: Option<Vec<PublicKey>>,
|
||||||
/// Numbedr of signatures required
|
/// Number of signatures required
|
||||||
///
|
///
|
||||||
/// Default is 1
|
/// Default is 1
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -452,6 +461,11 @@ pub struct Conditions {
|
|||||||
///
|
///
|
||||||
/// Default [`SigFlag::SigInputs`]
|
/// Default [`SigFlag::SigInputs`]
|
||||||
pub sig_flag: SigFlag,
|
pub sig_flag: SigFlag,
|
||||||
|
/// Number of refund signatures required
|
||||||
|
///
|
||||||
|
/// Default is 1
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub num_sigs_refund: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Conditions {
|
impl Conditions {
|
||||||
@@ -462,6 +476,7 @@ impl Conditions {
|
|||||||
refund_keys: Option<Vec<PublicKey>>,
|
refund_keys: Option<Vec<PublicKey>>,
|
||||||
num_sigs: Option<u64>,
|
num_sigs: Option<u64>,
|
||||||
sig_flag: Option<SigFlag>,
|
sig_flag: Option<SigFlag>,
|
||||||
|
num_sigs_refund: Option<u64>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
if let Some(locktime) = locktime {
|
if let Some(locktime) = locktime {
|
||||||
ensure_cdk!(locktime.ge(&unix_time()), Error::LocktimeInPast);
|
ensure_cdk!(locktime.ge(&unix_time()), Error::LocktimeInPast);
|
||||||
@@ -473,6 +488,7 @@ impl Conditions {
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
num_sigs,
|
num_sigs,
|
||||||
sig_flag: sig_flag.unwrap_or_default(),
|
sig_flag: sig_flag.unwrap_or_default(),
|
||||||
|
num_sigs_refund,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,6 +500,7 @@ impl From<Conditions> for Vec<Vec<String>> {
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
num_sigs,
|
num_sigs,
|
||||||
sig_flag,
|
sig_flag,
|
||||||
|
num_sigs_refund: _,
|
||||||
} = conditions;
|
} = conditions;
|
||||||
|
|
||||||
let mut tags = Vec::new();
|
let mut tags = Vec::new();
|
||||||
@@ -564,6 +581,7 @@ impl TryFrom<Vec<Vec<String>>> for Conditions {
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
num_sigs,
|
num_sigs,
|
||||||
sig_flag,
|
sig_flag,
|
||||||
|
num_sigs_refund: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,6 +601,9 @@ pub enum TagKind {
|
|||||||
Refund,
|
Refund,
|
||||||
/// Pubkey
|
/// Pubkey
|
||||||
Pubkeys,
|
Pubkeys,
|
||||||
|
/// Number signatures required
|
||||||
|
#[serde(rename = "n_sigs_refund")]
|
||||||
|
NSigsRefund,
|
||||||
/// Custom tag kind
|
/// Custom tag kind
|
||||||
Custom(String),
|
Custom(String),
|
||||||
}
|
}
|
||||||
@@ -595,6 +616,7 @@ impl fmt::Display for TagKind {
|
|||||||
Self::Locktime => write!(f, "locktime"),
|
Self::Locktime => write!(f, "locktime"),
|
||||||
Self::Refund => write!(f, "refund"),
|
Self::Refund => write!(f, "refund"),
|
||||||
Self::Pubkeys => write!(f, "pubkeys"),
|
Self::Pubkeys => write!(f, "pubkeys"),
|
||||||
|
Self::NSigsRefund => write!(f, "n_sigs_refund"),
|
||||||
Self::Custom(kind) => write!(f, "{kind}"),
|
Self::Custom(kind) => write!(f, "{kind}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -857,6 +879,7 @@ mod tests {
|
|||||||
.unwrap()]),
|
.unwrap()]),
|
||||||
num_sigs: Some(2),
|
num_sigs: Some(2),
|
||||||
sig_flag: SigFlag::SigAll,
|
sig_flag: SigFlag::SigAll,
|
||||||
|
num_sigs_refund: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let secret: Nut10Secret = Nut10Secret::new(Kind::P2PK, data.to_string(), Some(conditions));
|
let secret: Nut10Secret = Nut10Secret::new(Kind::P2PK, data.to_string(), Some(conditions));
|
||||||
@@ -891,6 +914,7 @@ mod tests {
|
|||||||
refund_keys: Some(vec![v_key]),
|
refund_keys: Some(vec![v_key]),
|
||||||
num_sigs: Some(2),
|
num_sigs: Some(2),
|
||||||
sig_flag: SigFlag::SigInputs,
|
sig_flag: SigFlag::SigInputs,
|
||||||
|
num_sigs_refund: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let secret: Secret = Nut10Secret::new(Kind::P2PK, v_key.to_string(), Some(conditions))
|
let secret: Secret = Nut10Secret::new(Kind::P2PK, v_key.to_string(), Some(conditions))
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ pub async fn create_request(
|
|||||||
refund_keys: None,
|
refund_keys: None,
|
||||||
num_sigs: Some(num_sigs),
|
num_sigs: Some(num_sigs),
|
||||||
sig_flag: SigFlag::SigInputs,
|
sig_flag: SigFlag::SigInputs,
|
||||||
|
num_sigs_refund: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to parse the hash
|
// Try to parse the hash
|
||||||
@@ -186,6 +187,7 @@ pub async fn create_request(
|
|||||||
refund_keys: None,
|
refund_keys: None,
|
||||||
num_sigs: Some(num_sigs),
|
num_sigs: Some(num_sigs),
|
||||||
sig_flag: SigFlag::SigInputs,
|
sig_flag: SigFlag::SigInputs,
|
||||||
|
num_sigs_refund: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create HTLC conditions with the hash and pubkeys in conditions
|
// Create HTLC conditions with the hash and pubkeys in conditions
|
||||||
@@ -203,6 +205,7 @@ pub async fn create_request(
|
|||||||
refund_keys: None,
|
refund_keys: None,
|
||||||
num_sigs: Some(num_sigs),
|
num_sigs: Some(num_sigs),
|
||||||
sig_flag: SigFlag::SigInputs,
|
sig_flag: SigFlag::SigInputs,
|
||||||
|
num_sigs_refund: None,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ pub async fn send(
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
sub_command_args.required_sigs,
|
sub_command_args.required_sigs,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -155,8 +156,8 @@ pub async fn send(
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
sub_command_args.required_sigs,
|
sub_command_args.required_sigs,
|
||||||
None,
|
None,
|
||||||
)
|
None,
|
||||||
.unwrap();
|
)?;
|
||||||
|
|
||||||
Some(SpendingConditions::new_htlc_hash(hash, Some(conditions))?)
|
Some(SpendingConditions::new_htlc_hash(hash, Some(conditions))?)
|
||||||
}
|
}
|
||||||
@@ -187,8 +188,8 @@ pub async fn send(
|
|||||||
refund_keys,
|
refund_keys,
|
||||||
sub_command_args.required_sigs,
|
sub_command_args.required_sigs,
|
||||||
None,
|
None,
|
||||||
)
|
None,
|
||||||
.unwrap();
|
)?;
|
||||||
|
|
||||||
Some(SpendingConditions::P2PKConditions {
|
Some(SpendingConditions::P2PKConditions {
|
||||||
data: data_pubkey,
|
data: data_pubkey,
|
||||||
|
|||||||
Reference in New Issue
Block a user