Skip to content

Commit 281b2e7

Browse files
authored
Address review comments (#14)
1 parent 4735780 commit 281b2e7

File tree

5 files changed

+178
-18
lines changed

5 files changed

+178
-18
lines changed

Cargo.lock

Lines changed: 71 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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ wormhole-vaas-serde = "0.1.0"
3333
[dev-dependencies]
3434
serde_json = "1.0.140"
3535
base64 = "0.22.1"
36+
mockall = "0.13.1"
37+
38+
[profile.release]
39+
overflow-checks = true

src/main.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const INVALID_UNRELIABLE_DATA_FORMAT: &str = "Invalid unreliable data format";
6161
const INVALID_PDA_MESSAGE: &str = "Invalid PDA message";
6262
const INVALID_EMITTER_CHAIN: &str = "Invalid emitter chain";
6363
const INVALID_ACCUMULATOR_ADDRESS: &str = "Invalid accumulator address";
64+
const INVALID_VAA_VERSION: &str = "Invalid VAA version";
6465

6566
fn decode_and_verify_update(
6667
wormhole_pid: &Pubkey,
@@ -87,6 +88,14 @@ fn decode_and_verify_update(
8788
anyhow::anyhow!(format!("{}: {}", INVALID_UNRELIABLE_DATA_FORMAT, e))
8889
})?;
8990

91+
if unreliable_data.message.vaa_version != 1 {
92+
tracing::error!(
93+
vaa_version = unreliable_data.message.vaa_version,
94+
"Unsupported VAA version"
95+
);
96+
return Err(anyhow::anyhow!(INVALID_VAA_VERSION));
97+
}
98+
9099
if Chain::Pythnet != unreliable_data.emitter_chain.into() {
91100
tracing::error!(
92101
emitter_chain = unreliable_data.emitter_chain,
@@ -320,7 +329,7 @@ mod tests {
320329
use secp256k1::SecretKey;
321330
use solana_account_decoder::{UiAccount, UiAccountData};
322331

323-
use crate::posted_message::MessageData;
332+
use crate::{posted_message::MessageData, signer::MockSigner};
324333

325334
fn get_wormhole_pid() -> Pubkey {
326335
Pubkey::from_str("H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU").unwrap()
@@ -488,6 +497,18 @@ mod tests {
488497
assert_eq!(result.unwrap_err().to_string(), INVALID_EMITTER_CHAIN);
489498
}
490499

500+
#[test]
501+
fn test_decode_and_verify_update_invalid_vaa_version() {
502+
let mut unreliable_data = get_unreliable_data();
503+
unreliable_data.message.vaa_version = 2;
504+
let result = decode_and_verify_update(
505+
&get_wormhole_pid(),
506+
&get_accumulator_address(),
507+
get_update(unreliable_data),
508+
);
509+
assert_eq!(result.unwrap_err().to_string(), INVALID_VAA_VERSION);
510+
}
511+
491512
#[test]
492513
fn test_decode_and_verify_update_invalid_emitter_address() {
493514
let mut unreliable_data = get_unreliable_data();
@@ -530,4 +551,21 @@ mod tests {
530551
"30e41be3f10d3ac813f91e49e189bbb948d030be",
531552
);
532553
}
554+
555+
#[tokio::test]
556+
async fn test_sign_error() {
557+
let mut mock_signer = MockSigner::new();
558+
mock_signer
559+
.expect_sign()
560+
.return_once(|_| Err(anyhow::anyhow!("Mock signing error")));
561+
let unreliable_data = get_unreliable_data();
562+
let body = message_data_to_body(&unreliable_data);
563+
let err = Observation::try_new(body, Arc::new(mock_signer))
564+
.await
565+
.unwrap_err();
566+
assert_eq!(
567+
err.to_string(),
568+
"Failed to sign observation: Mock signing error"
569+
);
570+
}
533571
}

src/posted_message.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,55 @@ mod tests {
127127
let decoded = PostedMessageUnreliableData::try_from_slice(encoded.as_slice()).unwrap();
128128
assert_eq!(decoded, post_message_unreliable_data);
129129
}
130+
131+
#[test]
132+
fn test_invalid_magic() {
133+
let post_message_unreliable_data = PostedMessageUnreliableData {
134+
message: MessageData {
135+
vaa_version: 1,
136+
consistency_level: 2,
137+
vaa_time: 3,
138+
vaa_signature_account: [4u8; 32],
139+
submission_time: 5,
140+
nonce: 6,
141+
sequence: 7,
142+
emitter_chain: 8,
143+
emitter_address: [9u8; 32],
144+
payload: vec![10u8; 32],
145+
},
146+
};
147+
148+
let mut encoded = borsh::to_vec(&post_message_unreliable_data).unwrap();
149+
encoded[0..3].copy_from_slice(b"foo"); // Invalid magic
150+
151+
let err = PostedMessageUnreliableData::try_from_slice(encoded.as_slice()).unwrap_err();
152+
assert_eq!(
153+
err.to_string(),
154+
"Magic mismatch. Expected [109, 115, 117] but got [102, 111, 111]"
155+
);
156+
}
157+
158+
#[test]
159+
fn test_invalid_data_length() {
160+
let post_message_unreliable_data = PostedMessageUnreliableData {
161+
message: MessageData {
162+
vaa_version: 1,
163+
consistency_level: 2,
164+
vaa_time: 3,
165+
vaa_signature_account: [4u8; 32],
166+
submission_time: 5,
167+
nonce: 6,
168+
sequence: 7,
169+
emitter_chain: 8,
170+
emitter_address: [9u8; 32],
171+
payload: vec![10u8; 32],
172+
},
173+
};
174+
175+
let mut encoded = borsh::to_vec(&post_message_unreliable_data).unwrap();
176+
encoded = encoded[0..encoded.len() - 1].to_vec();
177+
178+
let err = PostedMessageUnreliableData::try_from_slice(encoded.as_slice()).unwrap_err();
179+
assert_eq!(err.to_string(), "Unexpected length of input");
180+
}
130181
}

src/signer.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use der::{
2-
asn1::{AnyRef, BitStringRef, UintRef},
2+
asn1::{AnyRef, BitStringRef},
33
oid::ObjectIdentifier,
44
Decode, Sequence,
55
};
@@ -13,12 +13,13 @@ use std::{
1313
use async_trait::async_trait;
1414
use prost::Message as ProstMessage;
1515
use secp256k1::{
16-
ecdsa::{RecoverableSignature, RecoveryId},
16+
ecdsa::{RecoverableSignature, RecoveryId, Signature},
1717
Message, PublicKey, Secp256k1, SecretKey,
1818
};
1919
use sequoia_openpgp::armor::{Kind, Reader, ReaderMode};
2020
use sha3::{Digest, Keccak256};
2121

22+
#[cfg_attr(test, mockall::automock)]
2223
#[async_trait]
2324
pub trait Signer: Send + Sync {
2425
async fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]>;
@@ -181,12 +182,6 @@ pub struct SubjectPublicKeyInfo<'a> {
181182
pub subject_public_key: BitStringRef<'a>,
182183
}
183184

184-
#[derive(Sequence)]
185-
struct EcdsaSignature<'a> {
186-
r: UintRef<'a>,
187-
s: UintRef<'a>,
188-
}
189-
190185
#[async_trait]
191186
impl Signer for KMSSigner {
192187
async fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]> {
@@ -204,14 +199,13 @@ impl Signer for KMSSigner {
204199
.signature
205200
.ok_or_else(|| anyhow::anyhow!("KMS did not return a signature"))?;
206201

207-
let decoded_signature = EcdsaSignature::from_der(kms_signature.as_ref())
208-
.map_err(|e| anyhow::anyhow!("Failed to decode SubjectPublicKeyInfo: {}", e))?;
209-
210-
let r_bytes = decoded_signature.r.as_bytes();
211-
let s_bytes = decoded_signature.s.as_bytes();
212-
let mut signature = [0u8; 65];
213-
signature[(32 - r_bytes.len())..32].copy_from_slice(r_bytes);
214-
signature[(64 - s_bytes.len())..64].copy_from_slice(decoded_signature.s.as_bytes());
202+
let mut signature = Signature::from_der(kms_signature.as_ref())
203+
.map_err(|e| anyhow::anyhow!("Failed to decode signature from KMS: {}", e))?;
204+
// NOTE: AWS KMS does not guarantee that the ECDSA signature is normalized.
205+
// Therefore, we must normalize it ourselves to prevent malleability,
206+
// so that it can be successfully verified later using the secp256k1 standard libraries.
207+
signature.normalize_s();
208+
let signature_bytes = signature.serialize_compact();
215209

216210
let public_key = self.get_public_key()?;
217211
for raw_id in 0..4 {
@@ -220,10 +214,12 @@ impl Signer for KMSSigner {
220214
.map_err(|e| anyhow::anyhow!("Failed to create RecoveryId: {}", e))?;
221215
if let Ok(recovered_public_key) = secp.recover_ecdsa(
222216
&Message::from_digest(data),
223-
&RecoverableSignature::from_compact(&signature[..64], recid)
217+
&RecoverableSignature::from_compact(&signature_bytes, recid)
224218
.map_err(|e| anyhow::anyhow!("Failed to create RecoverableSignature: {}", e))?,
225219
) {
226220
if recovered_public_key == public_key.0 {
221+
let mut signature = [0u8; 65];
222+
signature[..64].copy_from_slice(&signature_bytes);
227223
signature[64] = raw_id as u8;
228224
return Ok(signature);
229225
}

0 commit comments

Comments
 (0)