1use crate::internal_prelude::*;
2
3pub fn verify_and_recover(
4 signed_hash: &Hash,
5 signature: &SignatureWithPublicKeyV1,
6) -> Option<PublicKey> {
7 match signature {
8 SignatureWithPublicKeyV1::Secp256k1 { signature } => {
9 verify_and_recover_secp256k1(signed_hash, signature).map(Into::into)
10 }
11 SignatureWithPublicKeyV1::Ed25519 {
12 public_key,
13 signature,
14 } => {
15 if verify_ed25519(signed_hash, public_key, signature) {
16 Some((*public_key).into())
17 } else {
18 None
19 }
20 }
21 }
22}
23
24pub fn verify(signed_hash: &Hash, public_key: &PublicKey, signature: &SignatureV1) -> bool {
25 match (public_key, signature) {
26 (PublicKey::Secp256k1(public_key), SignatureV1::Secp256k1(signature)) => {
27 verify_secp256k1(signed_hash, public_key, signature)
28 }
29 (PublicKey::Ed25519(public_key), SignatureV1::Ed25519(signature)) => {
30 verify_ed25519(signed_hash, public_key, signature)
31 }
32 _ => false,
33 }
34}
35
36pub trait SignedIntentTreeStructure {
37 type IntentTree: IntentTreeStructure;
38 fn root_signatures(&self) -> PendingIntentSignatureValidations<'_>;
39 fn non_root_subintent_signatures(
40 &self,
41 ) -> impl ExactSizeIterator<Item = PendingSubintentSignatureValidations<'_>>;
42 fn intent_tree(&self) -> &Self::IntentTree;
43 fn transaction_version(&self) -> TransactionVersion;
44
45 fn construct_pending_signature_validations<'a>(
46 &'a self,
47 config: &'a TransactionValidationConfig,
48 ) -> Result<AllPendingSignatureValidations<'a>, TransactionValidationError> {
49 let mut pending_signatures = AllPendingSignatureValidations::new_with_root(
50 self.transaction_version(),
51 config,
52 self.intent_tree().root().intent_hash(),
53 self.root_signatures(),
54 )?;
55
56 let non_root_subintents = self.intent_tree().non_root_subintents();
57 let non_root_subintent_signatures = self.non_root_subintent_signatures();
58 if non_root_subintents.len() != non_root_subintent_signatures.len() {
59 return Err(
60 SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches
61 .located(TransactionValidationErrorLocation::AcrossTransaction),
62 );
63 }
64 for (index, (subintent, signatures)) in non_root_subintents
65 .zip(non_root_subintent_signatures)
66 .enumerate()
67 {
68 pending_signatures.add_non_root(
69 SubintentIndex(index),
70 subintent.subintent_hash(),
71 signatures.for_subintent(subintent.subintent_hash()),
72 )?;
73 }
74
75 Ok(pending_signatures)
76 }
77}
78
79#[must_use]
80pub struct AllPendingSignatureValidations<'a> {
81 transaction_version: TransactionVersion,
82 config: &'a TransactionValidationConfig,
83 root: (
84 PendingIntentSignatureValidations<'a>,
85 TransactionValidationErrorLocation,
86 ),
87 non_roots: Vec<(
88 PendingIntentSignatureValidations<'a>,
89 TransactionValidationErrorLocation,
90 )>,
91 total_signature_validations: usize,
92}
93
94pub enum PendingSubintentSignatureValidations<'a> {
95 Subintent {
96 intent_signatures: &'a [IntentSignatureV1],
97 },
98 PreviewSubintent {
99 intent_public_keys: &'a [PublicKey],
100 },
101}
102
103impl<'a> PendingSubintentSignatureValidations<'a> {
104 fn for_subintent(self, signed_hash: SubintentHash) -> PendingIntentSignatureValidations<'a> {
105 match self {
106 PendingSubintentSignatureValidations::Subintent { intent_signatures } => {
107 PendingIntentSignatureValidations::Subintent {
108 intent_signatures,
109 signed_hash,
110 }
111 }
112 PendingSubintentSignatureValidations::PreviewSubintent { intent_public_keys } => {
113 PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys }
114 }
115 }
116 }
117}
118
119pub enum PendingIntentSignatureValidations<'a> {
122 TransactionIntent {
123 notary_is_signatory: bool,
124 notary_public_key: PublicKey,
125 notary_signature: SignatureV1,
126 notarized_hash: SignedTransactionIntentHash,
127 intent_signatures: &'a [IntentSignatureV1],
128 signed_hash: TransactionIntentHash,
129 },
130 PreviewTransactionIntent {
131 notary_is_signatory: bool,
132 notary_public_key: PublicKey,
133 intent_public_keys: &'a [PublicKey],
134 },
135 Subintent {
136 intent_signatures: &'a [IntentSignatureV1],
137 signed_hash: SubintentHash,
138 },
139 PreviewSubintent {
140 intent_public_keys: &'a [PublicKey],
141 },
142}
143
144impl<'a> AllPendingSignatureValidations<'a> {
145 pub(crate) fn new_with_root(
146 transaction_version: TransactionVersion,
147 config: &'a TransactionValidationConfig,
148 root_intent_hash: IntentHash,
149 signatures: PendingIntentSignatureValidations<'a>,
150 ) -> Result<Self, TransactionValidationError> {
151 let intent_signature_validations = signatures.intent_signature_validations();
152 let error_location = TransactionValidationErrorLocation::for_root(root_intent_hash);
153 if intent_signature_validations > config.max_signer_signatures_per_intent {
154 return Err(TransactionValidationError::SignatureValidationError(
155 error_location,
156 SignatureValidationError::TooManySignatures {
157 total: intent_signature_validations,
158 limit: config.max_signer_signatures_per_intent,
159 },
160 ));
161 }
162 let notary_signature_validations = signatures.notary_signature_validations();
163
164 Ok(Self {
165 transaction_version,
166 config,
167 root: (signatures, error_location),
168 non_roots: Default::default(),
169 total_signature_validations: intent_signature_validations
170 + notary_signature_validations,
171 })
172 }
173
174 fn add_non_root(
175 &mut self,
176 subintent_index: SubintentIndex,
177 subintent_hash: SubintentHash,
178 signatures: PendingIntentSignatureValidations<'a>,
179 ) -> Result<(), TransactionValidationError> {
180 let intent_signature_validations = signatures.intent_signature_validations();
181 let error_location =
182 TransactionValidationErrorLocation::NonRootSubintent(subintent_index, subintent_hash);
183 if intent_signature_validations > self.config.max_signer_signatures_per_intent {
184 return Err(TransactionValidationError::SignatureValidationError(
185 error_location,
186 SignatureValidationError::TooManySignatures {
187 total: intent_signature_validations,
188 limit: self.config.max_signer_signatures_per_intent,
189 },
190 ));
191 }
192
193 self.non_roots.push((signatures, error_location));
194 self.total_signature_validations += intent_signature_validations;
195 Ok(())
196 }
197
198 pub(crate) fn validate_all(
199 self,
200 ) -> Result<SignatureValidationSummary, TransactionValidationError> {
201 if self.total_signature_validations > self.config.max_total_signature_validations {
202 return Err(TransactionValidationError::SignatureValidationError(
203 TransactionValidationErrorLocation::AcrossTransaction,
204 SignatureValidationError::TooManySignatures {
205 total: self.total_signature_validations,
206 limit: self.config.max_total_signature_validations,
207 },
208 ));
209 }
210 let config = self.config;
211 let transaction_version = self.transaction_version;
212 let root_signer_keys = Self::validate_signatures(self.root.0, config, transaction_version)
213 .map_err(|err| {
214 TransactionValidationError::SignatureValidationError(self.root.1, err)
215 })?;
216
217 let non_root_signer_keys = self
218 .non_roots
219 .into_iter()
220 .map(|non_root| {
221 Self::validate_signatures(non_root.0, config, transaction_version).map_err(|err| {
222 TransactionValidationError::SignatureValidationError(non_root.1, err)
223 })
224 })
225 .collect::<Result<_, _>>()?;
226
227 Ok(SignatureValidationSummary {
228 root_signer_keys,
229 non_root_signer_keys,
230 total_signature_validations: self.total_signature_validations,
231 })
232 }
233
234 fn validate_signatures(
235 signatures: PendingIntentSignatureValidations,
236 config: &TransactionValidationConfig,
237 transaction_version: TransactionVersion,
238 ) -> Result<IndexSet<PublicKey>, SignatureValidationError> {
239 let public_keys = match signatures {
240 PendingIntentSignatureValidations::TransactionIntent {
241 notary_is_signatory,
242 notary_public_key,
243 notary_signature,
244 notarized_hash,
245 intent_signatures,
246 signed_hash,
247 } => {
248 let mut intent_public_keys: IndexSet<PublicKey> = Default::default();
249 for signature in intent_signatures {
250 let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0)
251 .ok_or(SignatureValidationError::InvalidIntentSignature)?;
252
253 if !intent_public_keys.insert(public_key) {
254 return Err(SignatureValidationError::DuplicateSigner);
255 }
256 }
257
258 if !verify(
259 notarized_hash.as_hash(),
260 ¬ary_public_key,
261 ¬ary_signature,
262 ) {
263 return Err(SignatureValidationError::InvalidNotarySignature);
264 }
265
266 if notary_is_signatory
267 && !intent_public_keys.insert(notary_public_key)
268 && !config.allow_notary_to_duplicate_signer(transaction_version)
269 {
270 return Err(
271 SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner,
272 );
273 }
274
275 intent_public_keys
276 }
277 PendingIntentSignatureValidations::PreviewTransactionIntent {
278 notary_is_signatory,
279 notary_public_key,
280 intent_public_keys,
281 } => {
282 let mut checked_intent_public_keys: IndexSet<PublicKey> = Default::default();
283 for key in intent_public_keys {
284 if !checked_intent_public_keys.insert(*key) {
285 return Err(SignatureValidationError::DuplicateSigner);
286 }
287 }
288 if notary_is_signatory
289 && !checked_intent_public_keys.insert(notary_public_key)
290 && !config.allow_notary_to_duplicate_signer(transaction_version)
291 {
292 return Err(
293 SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner,
294 );
295 }
296 checked_intent_public_keys
297 }
298 PendingIntentSignatureValidations::Subintent {
299 intent_signatures,
300 signed_hash,
301 } => {
302 let mut intent_public_keys: IndexSet<PublicKey> = Default::default();
303 for signature in intent_signatures {
304 let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0)
305 .ok_or(SignatureValidationError::InvalidIntentSignature)?;
306
307 if !intent_public_keys.insert(public_key) {
308 return Err(SignatureValidationError::DuplicateSigner);
309 }
310 }
311 intent_public_keys
312 }
313 PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => {
314 let mut checked_intent_public_keys: IndexSet<PublicKey> = Default::default();
315 for key in intent_public_keys {
316 if !checked_intent_public_keys.insert(*key) {
317 return Err(SignatureValidationError::DuplicateSigner);
318 }
319 }
320 checked_intent_public_keys
321 }
322 };
323
324 Ok(public_keys)
325 }
326}
327
328pub(crate) struct SignatureValidationSummary {
329 pub root_signer_keys: IndexSet<PublicKey>,
330 pub non_root_signer_keys: Vec<IndexSet<PublicKey>>,
331 pub total_signature_validations: usize,
332}
333
334impl<'a> PendingIntentSignatureValidations<'a> {
335 fn intent_signature_validations(&self) -> usize {
336 match self {
337 PendingIntentSignatureValidations::TransactionIntent {
338 intent_signatures, ..
339 } => intent_signatures.len(),
340 PendingIntentSignatureValidations::PreviewTransactionIntent {
341 intent_public_keys,
342 ..
343 } => intent_public_keys.len(),
344 PendingIntentSignatureValidations::Subintent {
345 intent_signatures, ..
346 } => intent_signatures.len(),
347 PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => {
348 intent_public_keys.len()
349 }
350 }
351 }
352
353 fn notary_signature_validations(&self) -> usize {
354 match self {
355 PendingIntentSignatureValidations::TransactionIntent { .. }
356 | PendingIntentSignatureValidations::PreviewTransactionIntent { .. } => 1,
357 PendingIntentSignatureValidations::Subintent { .. }
358 | PendingIntentSignatureValidations::PreviewSubintent { .. } => 0,
359 }
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use crate::internal_prelude::*;
366
367 #[test]
368 fn test_demonstrate_behaviour_with_notary_duplicating_signer() {
369 let network = NetworkDefinition::simulator();
371 let notary = Secp256k1PrivateKey::from_u64(1).unwrap();
372
373 let babylon_validator = TransactionValidator::new_with_static_config(
374 TransactionValidationConfig::babylon(),
375 network.id,
376 );
377 let latest_validator = TransactionValidator::new_with_static_config(
378 TransactionValidationConfig::latest(),
379 network.id,
380 );
381
382 let transaction_v1 = TransactionBuilder::new()
383 .header(TransactionHeaderV1 {
384 network_id: network.id,
385 start_epoch_inclusive: Epoch::of(1),
386 end_epoch_exclusive: Epoch::of(10),
387 nonce: 0,
388 notary_public_key: notary.public_key().into(),
389 notary_is_signatory: true,
390 tip_percentage: 0,
391 })
392 .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build())
393 .sign(¬ary)
394 .notarize(¬ary)
395 .build();
396
397 let transaction_v2 = TransactionV2Builder::new_with_test_defaults()
398 .notary_is_signatory(true)
399 .notary_public_key(notary.public_key())
400 .manifest(ManifestBuilder::new_v2().drop_auth_zone_proofs().build())
401 .sign(¬ary)
402 .notarize(¬ary)
403 .build_minimal_no_validate();
404
405 assert!(transaction_v1
409 .prepare_and_validate(&babylon_validator)
410 .is_ok());
411 assert!(transaction_v1
412 .prepare_and_validate(&latest_validator)
413 .is_ok());
414
415 assert_matches!(
417 transaction_v2.prepare_and_validate(&babylon_validator),
418 Err(TransactionValidationError::PrepareError(
419 PrepareError::TransactionTypeNotSupported
420 ))
421 );
422 assert_matches!(
423 transaction_v2.prepare_and_validate(&latest_validator),
424 Err(TransactionValidationError::SignatureValidationError(
425 TransactionValidationErrorLocation::RootTransactionIntent(_),
426 SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner
427 )),
428 );
429 }
430
431 struct FakeSigner<'a, S: Signer> {
432 signer: &'a S,
433 }
434
435 impl<'a, S: Signer> FakeSigner<'a, S> {
436 fn new(signer: &'a S) -> Self {
437 Self { signer }
438 }
439 }
440
441 impl<'a, S: Signer> Signer for FakeSigner<'a, S> {
442 fn public_key(&self) -> PublicKey {
443 self.signer.public_key()
444 }
445
446 fn sign_without_public_key(&self, message_hash: &impl IsHash) -> SignatureV1 {
447 let mut signature = self.signer.sign_without_public_key(message_hash);
448 match &mut signature {
449 SignatureV1::Secp256k1(inner_signature) => {
450 inner_signature.0[5] += 1;
451 }
452 SignatureV1::Ed25519(inner_signature) => {
453 inner_signature.0[5] += 1;
454 }
455 }
456 signature
457 }
458
459 fn sign_with_public_key(&self, message_hash: &impl IsHash) -> SignatureWithPublicKeyV1 {
460 let mut signature = self.signer.sign_with_public_key(message_hash);
461 match &mut signature {
462 SignatureWithPublicKeyV1::Secp256k1 { signature } => {
463 signature.0[5] += 1;
464 }
465 SignatureWithPublicKeyV1::Ed25519 {
466 signature,
467 public_key: _,
468 } => {
469 signature.0[5] += 1;
470 }
471 }
472 signature
473 }
474 }
475
476 #[test]
477 fn test_invalid_signatures() {
478 let network = NetworkDefinition::simulator();
479
480 let validator = TransactionValidator::new_with_static_config(
481 TransactionValidationConfig::latest(),
482 network.id,
483 );
484
485 let versions_to_test = [TransactionVersion::V1, TransactionVersion::V2];
486
487 fn validate_transaction(
488 validator: &TransactionValidator,
489 version: TransactionVersion,
490 signer: &impl Signer,
491 notary: &impl Signer,
492 ) -> Result<IndexSet<PublicKey>, TransactionValidationError> {
493 let signer_keys = match version {
494 TransactionVersion::V1 => {
495 unsigned_v1_builder(notary.public_key())
496 .sign(signer)
497 .notarize(notary)
498 .build()
499 .prepare_and_validate(validator)?
500 .signer_keys
501 }
502 TransactionVersion::V2 => {
503 TransactionV2Builder::new_with_test_defaults()
504 .add_trivial_manifest()
505 .notary_public_key(notary.public_key())
506 .sign(signer)
507 .notarize(notary)
508 .build_minimal_no_validate()
509 .prepare_and_validate(validator)?
510 .transaction_intent_info
511 .signer_keys
512 }
513 };
514 Ok(signer_keys)
515 }
516
517 {
518 let notary = Secp256k1PrivateKey::from_u64(1).unwrap();
520 let signer = Secp256k1PrivateKey::from_u64(13).unwrap();
521 for version in versions_to_test {
522 assert_matches!(
523 validate_transaction(&validator, version, &signer, ¬ary),
524 Ok(signer_keys) => {
525 assert_eq!(signer_keys.len(), 1);
526 assert_eq!(signer_keys[0], signer.public_key().into());
527 }
528 );
529 match validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary)
530 {
531 Ok(signer_keys) => {
533 assert_eq!(signer_keys.len(), 1);
537 assert_ne!(signer_keys[0], signer.public_key().into());
538 }
539 Err(TransactionValidationError::SignatureValidationError(
540 TransactionValidationErrorLocation::RootTransactionIntent(_),
541 SignatureValidationError::InvalidIntentSignature,
542 )) => {}
543 other_result => {
544 panic!("Unexpected result: {other_result:?}");
545 }
546 }
547 assert_matches!(
548 validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)),
549 Err(TransactionValidationError::SignatureValidationError(
550 TransactionValidationErrorLocation::RootTransactionIntent(_),
551 SignatureValidationError::InvalidNotarySignature
552 ))
553 );
554 }
555 }
556
557 {
558 let notary = Ed25519PrivateKey::from_u64(1).unwrap();
560 let signer = Ed25519PrivateKey::from_u64(13).unwrap();
561 for version in versions_to_test {
562 assert_matches!(
563 validate_transaction(&validator, version, &signer, ¬ary),
564 Ok(signer_keys) => {
565 assert_eq!(signer_keys.len(), 1);
566 assert_eq!(signer_keys[0], signer.public_key().into());
567 }
568 );
569 assert_matches!(
570 validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary),
571 Err(TransactionValidationError::SignatureValidationError(
572 TransactionValidationErrorLocation::RootTransactionIntent(_),
573 SignatureValidationError::InvalidIntentSignature
574 ))
575 );
576 assert_matches!(
577 validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)),
578 Err(TransactionValidationError::SignatureValidationError(
579 TransactionValidationErrorLocation::RootTransactionIntent(_),
580 SignatureValidationError::InvalidNotarySignature
581 ))
582 );
583 }
584 }
585 }
586
587 #[test]
588 fn too_many_signatures_should_be_rejected() {
589 fn validate_transaction(
590 root_signature_count: usize,
591 signature_counts: Vec<usize>,
592 ) -> Result<ValidatedNotarizedTransactionV2, TransactionValidationError> {
593 TransactionV2Builder::new_with_test_defaults()
594 .add_children(
595 signature_counts
596 .iter()
597 .enumerate()
598 .map(|(i, signature_count)| {
599 create_leaf_partial_transaction(i as u64, *signature_count)
600 }),
601 )
602 .add_manifest_calling_each_child_once()
603 .multi_sign(
604 (0..root_signature_count)
605 .map(|i| Secp256k1PrivateKey::from_u64((100 + i) as u64).unwrap()),
606 )
607 .default_notarize_and_validate()
608 }
609
610 assert_matches!(validate_transaction(1, vec![10]), Ok(_));
611 assert_matches!(
612 validate_transaction(1, vec![10, 20]),
613 Err(TransactionValidationError::SignatureValidationError(
614 TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), _),
615 SignatureValidationError::TooManySignatures {
616 total: 20,
617 limit: 16,
618 },
619 ))
620 );
621 assert_matches!(
622 validate_transaction(17, vec![0, 3]),
623 Err(TransactionValidationError::SignatureValidationError(
624 TransactionValidationErrorLocation::RootTransactionIntent(_),
625 SignatureValidationError::TooManySignatures {
626 total: 17,
627 limit: 16,
628 },
629 ))
630 );
631 assert_matches!(
632 validate_transaction(1, vec![10, 10, 10, 10, 10, 10, 10]),
633 Err(TransactionValidationError::SignatureValidationError(
634 TransactionValidationErrorLocation::AcrossTransaction,
635 SignatureValidationError::TooManySignatures {
636 total: 72, limit: 64
638 },
639 ))
640 );
641 }
642
643 #[test]
644 fn test_incorrect_number_of_subintent_signature_batches() {
645 let validator = TransactionValidator::new_for_latest_simulator();
647
648 let mut transaction = TransactionV2Builder::new_with_test_defaults()
649 .add_children(vec![PartialTransactionV2Builder::new_with_test_defaults()
650 .add_trivial_manifest()
651 .build()])
652 .add_manifest_calling_each_child_once()
653 .default_notarize()
654 .build_minimal_no_validate();
655
656 let removed_signature_batch = transaction
658 .signed_transaction_intent
659 .non_root_subintent_signatures
660 .by_subintent
661 .pop()
662 .unwrap();
663
664 assert_matches!(
665 transaction.prepare_and_validate(&validator),
666 Err(TransactionValidationError::SignatureValidationError(
667 TransactionValidationErrorLocation::AcrossTransaction,
668 SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches
669 ))
670 );
671
672 let mut transaction = TransactionV2Builder::new_with_test_defaults()
674 .add_trivial_manifest()
675 .default_notarize()
676 .build_minimal_no_validate();
677
678 transaction
680 .signed_transaction_intent
681 .non_root_subintent_signatures
682 .by_subintent
683 .push(removed_signature_batch);
684
685 assert_matches!(
686 transaction.prepare_and_validate(&validator),
687 Err(TransactionValidationError::SignatureValidationError(
688 TransactionValidationErrorLocation::AcrossTransaction,
689 SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches
690 ))
691 );
692 }
693}