Merge pull request #731 from thesimplekid/fix_p2pk_multi_sig

Fix p2pk multi sig
This commit is contained in:
thesimplekid
2025-04-22 12:48:46 +01:00
committed by GitHub
2 changed files with 41 additions and 11 deletions

View File

@@ -60,6 +60,9 @@ pub enum Error {
/// Witness Signatures not provided
#[error("Witness signatures not provided")]
SignaturesNotProvided,
/// Duplicate signature from same pubkey
#[error("Duplicate signature from the same pubkey detected")]
DuplicateSignature,
/// Parse Url Error
#[error(transparent)]
UrlParseError(#[from] url::ParseError),
@@ -128,7 +131,7 @@ impl Proof {
secret.secret_data.tags.unwrap_or_default().try_into()?;
let msg: &[u8] = self.secret.as_bytes();
let mut valid_sigs = 0;
let mut verified_pubkeys = HashSet::new();
let witness_signatures = match &self.witness {
Some(witness) => witness.signatures(),
@@ -148,7 +151,10 @@ impl Proof {
let sig = Signature::from_str(signature)?;
if v.verify(msg, &sig).is_ok() {
valid_sigs += 1;
// If the pubkey is already verified, return a duplicate signature error
if !verified_pubkeys.insert(*v) {
return Err(Error::DuplicateSignature);
}
} else {
tracing::debug!(
"Could not verify signature: {sig} on message: {}",
@@ -158,6 +164,8 @@ impl Proof {
}
}
let valid_sigs = verified_pubkeys.len() as u64;
if valid_sigs >= spending_conditions.num_sigs.unwrap_or(1) {
return Ok(());
}
@@ -185,19 +193,27 @@ impl Proof {
}
}
/// Returns count of valid signatures
pub fn valid_signatures(msg: &[u8], pubkeys: &[PublicKey], signatures: &[Signature]) -> u64 {
let mut count = 0;
/// Returns count of valid signatures (each public key is only counted once)
/// Returns error if the same pubkey has multiple valid signatures
pub fn valid_signatures(
msg: &[u8],
pubkeys: &[PublicKey],
signatures: &[Signature],
) -> Result<u64, Error> {
let mut verified_pubkeys = HashSet::new();
for pubkey in pubkeys {
for signature in signatures {
if pubkey.verify(msg, signature).is_ok() {
count += 1;
// If the pubkey is already verified, return a duplicate signature error
if !verified_pubkeys.insert(*pubkey) {
return Err(Error::DuplicateSignature);
}
}
}
}
count
Ok(verified_pubkeys.len() as u64)
}
impl BlindedMessage {
@@ -224,7 +240,7 @@ impl BlindedMessage {
/// Verify P2PK conditions on [BlindedMessage]
pub fn verify_p2pk(&self, pubkeys: &Vec<PublicKey>, required_sigs: u64) -> Result<(), Error> {
let mut valid_sigs = 0;
let mut verified_pubkeys = HashSet::new();
if let Some(witness) = &self.witness {
for signature in witness
.signatures()
@@ -236,7 +252,10 @@ impl BlindedMessage {
let sig = Signature::from_str(signature)?;
if v.verify(msg, &sig).is_ok() {
valid_sigs += 1;
// If the pubkey is already verified, return a duplicate signature error
if !verified_pubkeys.insert(*v) {
return Err(Error::DuplicateSignature);
}
} else {
tracing::debug!(
"Could not verify signature: {sig} on message: {}",
@@ -247,6 +266,8 @@ impl BlindedMessage {
}
}
let valid_sigs = verified_pubkeys.len() as u64;
if valid_sigs.ge(&required_sigs) {
Ok(())
} else {
@@ -928,4 +949,13 @@ mod tests {
assert!(invalid_proof.verify_p2pk().is_err());
}
#[test]
fn test_duplicate_signatures_counting() {
let proof: Proof = serde_json::from_str(
r#"{"amount":1,"id":"009a1f293253e41e","secret":"[\"P2PK\",{\"nonce\":\"e434a9efbc5f65d144a620e368c9a6dc12c719d0ebc57e0c74f7341864dc449a\",\"data\":\"02a60c27104cf6023581e790970fc33994a320abe36e7ceed16771b0f8d76f0666\",\"tags\":[[\"pubkeys\",\"039c6a20a6ba354b7bb92eb9750716c1098063006362a1fa2afca7421f262d45c5\",\"0203eb2f7cd72a4f725d3327216365d2df18bb4bbc810522fd973c9af987e9b05b\"],[\"locktime\",\"1744876528\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","witness":"{\"signatures\":[\"3e9ff9e55c9eccb9e5aa0b6c62d54500b40d0eebadb06efcc8e76f3ce38e0923f956ec1bccb9080db96a17c1e98a1b857abfd1a56bb25670037cea3db1f73d81\",\"c5e29c38e60c4db720cf3f78e590358cf1291a06b9eadf77c1108ae84d533520c2707ffda224eb6a63fddaee9abd5ecf8f2cd263d2556950550e3061a5511f65\"]}"}"#,
).unwrap();
assert!(proof.verify_p2pk().is_err());
}
}

View File

@@ -94,7 +94,7 @@ impl Proof {
.collect::<Result<Vec<Signature>, _>>()?;
// If secret includes refund keys check that there is a valid signature
if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures).ge(&1) {
if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures)?.ge(&1) {
return Ok(());
}
}
@@ -113,7 +113,7 @@ impl Proof {
.map(|s| Signature::from_str(s))
.collect::<Result<Vec<Signature>, _>>()?;
let valid_sigs = valid_signatures(self.secret.as_bytes(), &pubkey, &signatures);
let valid_sigs = valid_signatures(self.secret.as_bytes(), &pubkey, &signatures)?;
ensure_cdk!(valid_sigs >= req_sigs, Error::IncorrectSecretKind);
}
}