1
1
use borsh:: BorshDeserialize ;
2
2
use observation:: { Body , SignedBody } ;
3
3
use posted_message:: PostedMessageUnreliableData ;
4
+ use secp256k1:: { ecdsa:: { RecoverableSignature , RecoveryId } , Message , PublicKey , Secp256k1 , SecretKey } ;
5
+ use serde_wormhole:: RawMessage ;
6
+ use sha3:: digest:: crypto_common:: rand_core:: OsRng ;
4
7
use solana_account_decoder:: UiAccountEncoding ;
5
8
use solana_client:: {
6
9
nonblocking:: pubsub_client:: PubsubClient ,
7
10
rpc_config:: { RpcAccountInfoConfig , RpcProgramAccountsConfig } ,
8
11
} ;
9
- use solana_sdk:: { pubkey:: Pubkey , signature:: Keypair } ;
12
+ use solana_sdk:: pubkey:: Pubkey ;
13
+ use wormhole_sdk:: { vaa:: { Body as BodyWormhole , Header , Signature } , Address , Chain , Vaa } ;
10
14
use std:: str:: FromStr ;
11
15
use tokio_stream:: StreamExt ;
16
+ use sha3:: { Digest , Keccak256 } ;
17
+ use sha3:: digest:: crypto_common:: rand_core:: RngCore ;
12
18
13
19
mod posted_message;
14
20
mod observation;
15
21
mod serde_array;
16
22
17
23
const PYTHNET_CHAIN_ID : u16 = 26 ;
18
24
25
+ fn generate_guardian_key ( ) -> ( SecretKey , [ u8 ; 20 ] ) {
26
+ let secp = Secp256k1 :: new ( ) ;
27
+ let mut rng = OsRng ;
28
+
29
+ let mut sk_bytes = [ 0u8 ; 32 ] ;
30
+ rng. fill_bytes ( & mut sk_bytes) ;
31
+ let secret_key = SecretKey :: from_slice ( & sk_bytes) . expect ( "Failed to create secret key" ) ;
32
+
33
+ let public_key = PublicKey :: from_secret_key ( & secp, & secret_key) ;
34
+ let uncompressed = public_key. serialize_uncompressed ( ) ;
35
+
36
+ let hash = Keccak256 :: digest ( & uncompressed[ 1 ..] ) ;
37
+ let address: [ u8 ; 20 ] = hash[ 12 ..] . try_into ( ) . unwrap ( ) ;
38
+
39
+ ( secret_key, address)
40
+ }
41
+
42
+ pub fn verify_vaa ( public_key_original : [ u8 ; 20 ] , signed_body : SignedBody < Vec < u8 > > ) -> bool {
43
+ let mut signature = Signature :: default ( ) ;
44
+ signature. signature = signed_body. signature ;
45
+ let vaa: Vaa < Vec < u8 > > = Vaa {
46
+ version : signed_body. version ,
47
+ guardian_set_index : signed_body. guardian_set_index ,
48
+ signatures : vec ! [ signature] ,
49
+ timestamp : signed_body. body . timestamp ,
50
+ nonce : signed_body. body . nonce ,
51
+ emitter_chain : Chain :: Pythnet ,
52
+ emitter_address : Address ( signed_body. body . emitter_address ) ,
53
+ sequence : signed_body. body . sequence ,
54
+ consistency_level : 1 ,
55
+ payload : signed_body. body . payload ,
56
+ } ;
57
+ let ( _, body) : ( Header , BodyWormhole < Vec < u8 > > ) = vaa. into ( ) ;
58
+ let digest = body. digest ( ) . expect ( "Failed to get digest" ) ;
59
+
60
+ let secp = Secp256k1 :: new ( ) ;
61
+ let signature: [ u8 ; 65 ] = signed_body. signature ;
62
+
63
+ // Recover the public key from an [u8; 65] serialized ECDSA signature in (v, r, s) format
64
+ let recid = RecoveryId :: try_from ( signature[ 64 ] as i32 ) . expect ( "Failed to create recovery ID" ) ;
65
+
66
+ // An address is the last 20 bytes of the Keccak256 hash of the uncompressed public key.
67
+ let pubkey: & [ u8 ; 65 ] = & secp
68
+ . recover_ecdsa (
69
+ Message :: from_digest ( digest. secp256k_hash ) ,
70
+ & RecoverableSignature :: from_compact ( & signature[ ..64 ] , recid) . expect ( "Failed to create recoverable signature" ) ,
71
+ )
72
+ . expect ( "Failed to recover public key" )
73
+ . serialize_uncompressed ( ) ;
74
+
75
+ // The address is the last 20 bytes of the Keccak256 hash of the public key
76
+ let address: [ u8 ; 32 ] = Keccak256 :: new_with_prefix ( & pubkey[ 1 ..] ) . finalize ( ) . into ( ) ;
77
+ let address: [ u8 ; 20 ] = address[ address. len ( ) - 20 ..] . try_into ( )
78
+ . expect ( "Failed to convert address to 20 bytes" ) ;
79
+
80
+ println ! ( "Recovered address: {:?}" , address) ;
81
+ println ! ( "Public key: {:?}" , public_key_original) ;
82
+ // Confirm the recovered address matches an address in the guardian set.
83
+ public_key_original == address
84
+ }
85
+
19
86
#[ tokio:: main]
20
87
async fn main ( ) {
21
88
let ws_url = "wss://api2.pythnet.pyth.network/" ;
22
89
let accumulator_address = Pubkey :: from_str ( "G9LV2mp9ua1znRAfYwZz5cPiJMAbo1T6mbjdQsDZuMJg" ) . unwrap ( ) ; // Replace with actual Wormhole address
23
90
let wormhole_pid = Pubkey :: from_str ( "H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU" ) . unwrap ( ) ; // Replace with actual Wormhole program ID
24
- let secret_key = Keypair :: new ( ) . secret ( ) . to_bytes ( ) ;
91
+ let ( secret_key, pulick_key) = generate_guardian_key ( ) ;
92
+ println ! ( "Generated secret key: {:?}" , secret_key) ;
93
+ println ! ( "Generated public key: {:?}" , pulick_key) ;
25
94
let index = 0 ;
26
95
27
96
let client = PubsubClient :: new ( ws_url) . await . unwrap ( ) ;
@@ -73,7 +142,7 @@ async fn main() {
73
142
consistency_level : unreliable_data. consistency_level ,
74
143
payload : unreliable_data. payload . clone ( ) ,
75
144
} ;
76
- match body. sign ( secret_key) {
145
+ match body. sign ( secret_key. secret_bytes ( ) ) {
77
146
Ok ( signature) => {
78
147
let signed_body = SignedBody {
79
148
version : unreliable_data. vaa_version ,
@@ -83,6 +152,7 @@ async fn main() {
83
152
} ;
84
153
// Post it to server
85
154
println ! ( "message: {:?}" , signed_body) ;
155
+ println ! ( "The message is verified {}" , verify_vaa( pulick_key, signed_body) ) ;
86
156
}
87
157
Err ( e) => tracing:: error!( error = ?e, "Failed to sign body" ) ,
88
158
}
0 commit comments