@@ -17,6 +17,7 @@ pub use builder::Builder;
1717use catalyst_types:: problem_report:: ProblemReport ;
1818pub use content:: Content ;
1919use coset:: { CborSerializable , Header } ;
20+ use ed25519_dalek:: VerifyingKey ;
2021use error:: CatalystSignedDocError ;
2122pub use metadata:: { DocumentRef , ExtraFields , Metadata , UuidV4 , UuidV7 } ;
2223pub use minicbor:: { decode, encode, Decode , Decoder , Encode } ;
@@ -105,6 +106,64 @@ impl CatalystSignedDocument {
105106 pub fn signatures ( & self ) -> & Signatures {
106107 & self . inner . signatures
107108 }
109+
110+ /// Verify document signatures and `UUID`s.
111+ ///
112+ /// # Errors
113+ ///
114+ /// Returns a report of verification failures and the source error.
115+ #[ allow( clippy:: indexing_slicing) ]
116+ pub fn verify < P > ( & self , pk_getter : P ) -> Result < ( ) , CatalystSignedDocError >
117+ where P : Fn ( & KidUri ) -> VerifyingKey {
118+ let error_report = ProblemReport :: new ( "Catalyst Signed Document Verification" ) ;
119+
120+ match coset:: CoseSign :: try_from ( self ) {
121+ Ok ( cose_sign) => {
122+ let signatures = self . signatures ( ) . cose_signatures ( ) ;
123+ for ( idx, kid) in self . signatures ( ) . kids ( ) . iter ( ) . enumerate ( ) {
124+ let pk = pk_getter ( kid) ;
125+ let signature = & signatures[ idx] ;
126+ let tbs_data = cose_sign. tbs_data ( & [ ] , signature) ;
127+ match signature. signature . as_slice ( ) . try_into ( ) {
128+ Ok ( signature_bytes) => {
129+ let signature = ed25519_dalek:: Signature :: from_bytes ( signature_bytes) ;
130+ if let Err ( e) = pk. verify_strict ( & tbs_data, & signature) {
131+ error_report. functional_validation (
132+ & format ! (
133+ "Verification failed for signature with Key ID {kid}: {e}"
134+ ) ,
135+ "During signature validation with verifying key" ,
136+ ) ;
137+ }
138+ } ,
139+ Err ( _) => {
140+ error_report. invalid_value (
141+ "cose signature" ,
142+ & format ! ( "{}" , signature. signature. len( ) ) ,
143+ & format ! ( "must be {}" , ed25519_dalek:: Signature :: BYTE_SIZE ) ,
144+ "During encoding cose signature to bytes" ,
145+ ) ;
146+ } ,
147+ }
148+ }
149+ } ,
150+ Err ( e) => {
151+ error_report. other (
152+ & format ! ( "{e}" ) ,
153+ "During encoding signed document as COSE SIGN" ,
154+ ) ;
155+ } ,
156+ }
157+
158+ if error_report. is_problematic ( ) {
159+ return Err ( CatalystSignedDocError :: new (
160+ error_report,
161+ anyhow:: anyhow!( "Verification failed for Catalyst Signed Document" ) ,
162+ ) ) ;
163+ }
164+
165+ Ok ( ( ) )
166+ }
108167}
109168
110169impl TryFrom < & [ u8 ] > for CatalystSignedDocument {
@@ -242,6 +301,17 @@ impl Encode<()> for CatalystSignedDocument {
242301 }
243302}
244303
304+ impl TryFrom < & CatalystSignedDocument > for coset:: CoseSign {
305+ type Error = anyhow:: Error ;
306+
307+ fn try_from ( signed_doc : & CatalystSignedDocument ) -> Result < Self , Self :: Error > {
308+ let mut cose_bytes: Vec < u8 > = Vec :: new ( ) ;
309+ minicbor:: encode ( signed_doc, & mut cose_bytes) ?;
310+ coset:: CoseSign :: from_slice ( & cose_bytes)
311+ . map_err ( |e| anyhow:: anyhow!( "encoding COSE SIGN failed: {e}" ) )
312+ }
313+ }
314+
245315#[ cfg( test) ]
246316mod tests {
247317 use metadata:: { ContentEncoding , ContentType } ;
0 commit comments