radix_transactions/validation/
signature_validator.rs

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
119/// This can assume that the signature counts are within checked limits,
120/// so calculations cannot overflow.
121pub 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                    &notary_public_key,
261                    &notary_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        // Arrange
370        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(&notary)
394            .notarize(&notary)
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(&notary)
402            .notarize(&notary)
403            .build_minimal_no_validate();
404
405        // Act & Assert - Transaction V1 permits using notary as signatory and also having it sign
406        // It was deemed that we didn't want to start failing V1 transactions for this at Cuttlefish
407        // as we didn't want existing integrations to break.
408        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        // Act & Assert - Transaction V2 does not permit duplicating a notary is signatory as a signatory
416        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            // Test Secp256k1
519            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, &notary),
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), &notary)
530                {
531                    // Coincidentally, between V1 and V2 we hit both cases below
532                    Ok(signer_keys) => {
533                        // NOTE: Because we recover our Secp256k1 public keys, by mutating the signature
534                        // we might have stumbled on a valid signature for a different key - but that's okay.
535                        // As long as we can't fake the signature of a particular key, that's okay.
536                        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(&notary)),
549                    Err(TransactionValidationError::SignatureValidationError(
550                        TransactionValidationErrorLocation::RootTransactionIntent(_),
551                        SignatureValidationError::InvalidNotarySignature
552                    ))
553                );
554            }
555        }
556
557        {
558            // Test Ed25519
559            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, &notary),
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), &notary),
571                    Err(TransactionValidationError::SignatureValidationError(
572                        TransactionValidationErrorLocation::RootTransactionIntent(_),
573                        SignatureValidationError::InvalidIntentSignature
574                    ))
575                );
576                assert_matches!(
577                    validate_transaction(&validator, version, &signer, &FakeSigner::new(&notary)),
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, // 70 from subintent, 1 from transaction intent, 1 from notarization
637                    limit: 64
638                },
639            ))
640        );
641    }
642
643    #[test]
644    fn test_incorrect_number_of_subintent_signature_batches() {
645        // CASE 1: Too fee signatures
646        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        // Remove one signature batch
657        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        // CASE 2: Too many signature batches
673        let mut transaction = TransactionV2Builder::new_with_test_defaults()
674            .add_trivial_manifest()
675            .default_notarize()
676            .build_minimal_no_validate();
677
678        // Add an extra signature batch
679        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}