@@ -111,3 +111,91 @@ impl ApiClient {
111111 }
112112 }
113113}
114+
115+ #[ cfg( test) ]
116+ mod tests {
117+ use secp256k1:: {
118+ ecdsa:: { RecoverableSignature , RecoveryId } ,
119+ PublicKey ,
120+ } ;
121+ use serde_json:: Value ;
122+ use serde_wormhole:: RawMessage ;
123+ use wormhole_sdk:: { Address , Chain } ;
124+
125+ use super :: * ;
126+
127+ #[ test]
128+ fn test_new_signed_observation ( ) {
129+ let secret_key = SecretKey :: from_byte_array ( [ 1u8 ; 32 ] ) . expect ( "Invalid secret key length" ) ;
130+ let body = Body {
131+ timestamp : 1234567890 ,
132+ nonce : 42 ,
133+ emitter_chain : Chain :: Solana ,
134+ emitter_address : Address ( [ 1u8 ; 32 ] ) ,
135+ sequence : 1000 ,
136+ consistency_level : 1 ,
137+ payload : vec ! [ 1 , 2 , 3 , 4 , 5 ] ,
138+ } ;
139+ let observation =
140+ Observation :: try_new ( body. clone ( ) , secret_key) . expect ( "Failed to create observation" ) ;
141+ assert_eq ! ( observation. version, 1 ) ;
142+ assert_eq ! ( observation. body, body) ;
143+
144+ // Signature verification
145+ let secp = Secp256k1 :: new ( ) ;
146+ let digest = body. digest ( ) . expect ( "Failed to compute digest" ) ;
147+ let message = Message :: from_digest ( digest. secp256k_hash ) ;
148+
149+ let recovery_id: RecoveryId = ( observation. signature [ 64 ] as i32 )
150+ . try_into ( )
151+ . expect ( "Invalid recovery ID" ) ;
152+ let recoverable_sig =
153+ RecoverableSignature :: from_compact ( & observation. signature [ ..64 ] , recovery_id)
154+ . expect ( "Invalid recoverable signature" ) ;
155+
156+ let pubkey = secp
157+ . recover_ecdsa ( message, & recoverable_sig)
158+ . expect ( "Failed to recover pubkey" ) ;
159+
160+ let expected_pubkey = PublicKey :: from_secret_key ( & secp, & secret_key) ;
161+ assert_eq ! ( pubkey, expected_pubkey) ;
162+ }
163+
164+ #[ test]
165+ fn test_observation_serialization ( ) {
166+ let payload = vec ! [ 5 , 1 , 2 , 3 , 4 , 5 ] ;
167+ let observation = Observation {
168+ version : 1 ,
169+ signature : [ 1u8 ; 65 ] ,
170+ body : Body {
171+ timestamp : 1234567890 ,
172+ nonce : 42 ,
173+ emitter_chain : Chain :: Solana ,
174+ emitter_address : Address ( [ 1u8 ; 32 ] ) ,
175+ sequence : 1000 ,
176+ consistency_level : 1 ,
177+ payload : RawMessage :: new ( payload. as_slice ( ) ) ,
178+ } ,
179+ } ;
180+
181+ let serialized =
182+ serde_json:: to_string ( & observation) . expect ( "Failed to serialize observation" ) ;
183+ let parsed: Value = serde_json:: from_str ( & serialized) . expect ( "Failed to parse JSON" ) ;
184+
185+ assert_eq ! ( parsed[ "version" ] , 1 ) ;
186+ let sig = parsed[ "signature" ]
187+ . as_str ( )
188+ . expect ( "Signature should be a string" ) ;
189+ let decoded = hex:: decode ( sig) . expect ( "Should be valid hex" ) ;
190+ assert_eq ! ( decoded, observation. signature) ;
191+
192+ let message = parsed[ "body" ] . as_array ( ) . expect ( "Body should be an array" ) ;
193+ let bytes = message
194+ . iter ( )
195+ . map ( |v| v. as_u64 ( ) . expect ( "Body elements should be u64" ) as u8 )
196+ . collect :: < Vec < u8 > > ( ) ;
197+ let deserialized: Body < & RawMessage > =
198+ serde_wormhole:: from_slice ( & bytes) . expect ( "Failed to deserialize body" ) ;
199+ assert_eq ! ( deserialized, observation. body) ;
200+ }
201+ }
0 commit comments