Skip to content

Commit 21d3814

Browse files
committed
Add verification to signature
1 parent cd3f213 commit 21d3814

File tree

4 files changed

+150
-10
lines changed

4 files changed

+150
-10
lines changed

Cargo.lock

Lines changed: 67 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ solana-sdk = "2.2.2"
1616
tokio = "1.45.1"
1717
tokio-stream = "0.1.17"
1818
tracing = "0.1.41"
19+
wormhole-sdk = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.17.1" }

src/main.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,96 @@
11
use borsh::BorshDeserialize;
22
use observation::{Body, SignedBody};
33
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;
47
use solana_account_decoder::UiAccountEncoding;
58
use solana_client::{
69
nonblocking::pubsub_client::PubsubClient,
710
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
811
};
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};
1014
use std::str::FromStr;
1115
use tokio_stream::StreamExt;
16+
use sha3::{Digest, Keccak256};
17+
use sha3::digest::crypto_common::rand_core::RngCore;
1218

1319
mod posted_message;
1420
mod observation;
1521
mod serde_array;
1622

1723
const PYTHNET_CHAIN_ID: u16 = 26;
1824

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+
1986
#[tokio::main]
2087
async fn main() {
2188
let ws_url = "wss://api2.pythnet.pyth.network/";
2289
let accumulator_address = Pubkey::from_str("G9LV2mp9ua1znRAfYwZz5cPiJMAbo1T6mbjdQsDZuMJg").unwrap(); // Replace with actual Wormhole address
2390
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);
2594
let index = 0;
2695

2796
let client = PubsubClient::new(ws_url).await.unwrap();
@@ -73,7 +142,7 @@ async fn main() {
73142
consistency_level: unreliable_data.consistency_level,
74143
payload: unreliable_data.payload.clone(),
75144
};
76-
match body.sign(secret_key) {
145+
match body.sign(secret_key.secret_bytes()) {
77146
Ok(signature) => {
78147
let signed_body = SignedBody {
79148
version: unreliable_data.vaa_version,
@@ -83,6 +152,7 @@ async fn main() {
83152
};
84153
// Post it to server
85154
println!("message: {:?}", signed_body);
155+
println!("The message is verified {}", verify_vaa(pulick_key, signed_body));
86156
}
87157
Err(e) => tracing::error!(error = ?e, "Failed to sign body"),
88158
}

src/observation.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::io::Write;
22
use serde::{Deserialize, Serialize};
3+
use serde_wormhole::RawMessage;
34
use sha3::Digest as Sha3Digest;
45
use secp256k1::{Secp256k1, SecretKey, Message};
6+
use wormhole_sdk::{Address, Chain};
57

68
/// The body for a VAA.
7-
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8-
pub struct Body {
9+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
10+
pub struct Body<P> {
911
/// The timestamp of the block this message was published in.
1012
/// Seconds since UNIX epoch
1113
pub timestamp: u32,
@@ -14,7 +16,7 @@ pub struct Body {
1416
pub emitter_address: [u8; 32],
1517
pub sequence: u64,
1618
pub consistency_level: u8,
17-
pub payload: Vec<u8>,
19+
pub payload: P,
1820
}
1921

2022
#[derive(Debug)]
@@ -25,7 +27,7 @@ pub enum Error {
2527
InvalidSecretKey(secp256k1::Error),
2628
}
2729

28-
impl Body {
30+
impl<P: Serialize> Body<P> {
2931
/// Body Digest Components.
3032
///
3133
/// A VAA is distinguished by the unique 256bit Keccak256 hash of its body. This hash is
@@ -72,13 +74,13 @@ impl Body {
7274
}
7375
}
7476

75-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76-
pub struct SignedBody {
77+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
78+
pub struct SignedBody<P> {
7779
pub version: u8,
7880
pub guardian_set_index: u32,
7981
#[serde(with = "crate::serde_array")]
8082
pub signature: [u8; 65],
8183

8284
#[serde(flatten)]
83-
pub body: Body,
85+
pub body: Body<P>,
8486
}

0 commit comments

Comments
 (0)