443 lines
17 KiB
Plaintext
443 lines
17 KiB
Plaintext
module wormhole::vaa {
|
|
use std::vector;
|
|
use aptos_std::secp256k1;
|
|
|
|
use wormhole::u16::{U16};
|
|
use wormhole::u32::{U32};
|
|
use wormhole::deserialize;
|
|
use wormhole::cursor;
|
|
use wormhole::guardian_pubkey;
|
|
use wormhole::structs::{
|
|
Guardian,
|
|
GuardianSet,
|
|
Signature,
|
|
create_signature,
|
|
get_guardians,
|
|
unpack_signature,
|
|
get_address,
|
|
};
|
|
use wormhole::state;
|
|
use wormhole::external_address::{Self, ExternalAddress};
|
|
use wormhole::keccak256::keccak256;
|
|
|
|
friend wormhole::guardian_set_upgrade;
|
|
friend wormhole::contract_upgrade;
|
|
|
|
const E_NO_QUORUM: u64 = 0x0;
|
|
const E_TOO_MANY_SIGNATURES: u64 = 0x1;
|
|
const E_INVALID_SIGNATURE: u64 = 0x2;
|
|
const E_GUARDIAN_SET_EXPIRED: u64 = 0x3;
|
|
const E_INVALID_GOVERNANCE_CHAIN: u64 = 0x4;
|
|
const E_INVALID_GOVERNANCE_EMITTER: u64 = 0x5;
|
|
const E_WRONG_VERSION: u64 = 0x6;
|
|
const E_NON_INCREASING_SIGNERS: u64 = 0x7;
|
|
const E_OLD_GUARDIAN_SET_GOVERNANCE: u64 = 0x8;
|
|
|
|
struct VAA {
|
|
/// Header
|
|
guardian_set_index: U32,
|
|
signatures: vector<Signature>,
|
|
|
|
/// Body
|
|
timestamp: U32,
|
|
nonce: U32,
|
|
emitter_chain: U16,
|
|
emitter_address: ExternalAddress,
|
|
sequence: u64,
|
|
consistency_level: u8,
|
|
hash: vector<u8>, // 32 bytes
|
|
payload: vector<u8>, // variable bytes
|
|
}
|
|
|
|
//break
|
|
|
|
#[test_only]
|
|
public fun parse_test(bytes: vector<u8>): VAA {
|
|
parse(bytes)
|
|
}
|
|
|
|
/// Parses a VAA.
|
|
/// Does not do any verification, and is thus private.
|
|
/// This ensures the invariant that if an external module receives a `VAA`
|
|
/// object, its signatures must have been verified, because the only public
|
|
/// function that returns a VAA is `parse_and_verify`
|
|
fun parse(bytes: vector<u8>): VAA {
|
|
let cur = cursor::init(bytes);
|
|
let version = deserialize::deserialize_u8(&mut cur);
|
|
assert!(version == 1, E_WRONG_VERSION);
|
|
let guardian_set_index = deserialize::deserialize_u32(&mut cur);
|
|
|
|
let signatures_len = deserialize::deserialize_u8(&mut cur);
|
|
let signatures = vector::empty<Signature>();
|
|
|
|
while (signatures_len > 0) {
|
|
let guardian_index = deserialize::deserialize_u8(&mut cur);
|
|
let sig = deserialize::deserialize_vector(&mut cur, 64);
|
|
let recovery_id = deserialize::deserialize_u8(&mut cur);
|
|
let sig: secp256k1::ECDSASignature = secp256k1::ecdsa_signature_from_bytes(sig);
|
|
vector::push_back(&mut signatures, create_signature(sig, recovery_id, guardian_index));
|
|
signatures_len = signatures_len - 1;
|
|
};
|
|
|
|
let body = cursor::rest(cur);
|
|
let hash = keccak256(keccak256(body));
|
|
|
|
let cur = cursor::init(body);
|
|
|
|
let timestamp = deserialize::deserialize_u32(&mut cur);
|
|
let nonce = deserialize::deserialize_u32(&mut cur);
|
|
let emitter_chain = deserialize::deserialize_u16(&mut cur);
|
|
let emitter_address = external_address::deserialize(&mut cur);
|
|
let sequence = deserialize::deserialize_u64(&mut cur);
|
|
let consistency_level = deserialize::deserialize_u8(&mut cur);
|
|
|
|
let payload = cursor::rest(cur);
|
|
|
|
VAA {
|
|
guardian_set_index,
|
|
signatures,
|
|
timestamp,
|
|
nonce,
|
|
emitter_chain,
|
|
emitter_address,
|
|
sequence,
|
|
consistency_level,
|
|
hash,
|
|
payload,
|
|
}
|
|
}
|
|
|
|
public fun get_guardian_set_index(vaa: &VAA): U32 {
|
|
vaa.guardian_set_index
|
|
}
|
|
|
|
public fun get_timestamp(vaa: &VAA): U32 {
|
|
vaa.timestamp
|
|
}
|
|
|
|
public fun get_payload(vaa: &VAA): vector<u8> {
|
|
vaa.payload
|
|
}
|
|
|
|
public fun get_hash(vaa: &VAA): vector<u8> {
|
|
vaa.hash
|
|
}
|
|
|
|
public fun get_emitter_chain(vaa: &VAA): U16 {
|
|
vaa.emitter_chain
|
|
}
|
|
|
|
public fun get_emitter_address(vaa: &VAA): ExternalAddress {
|
|
vaa.emitter_address
|
|
}
|
|
|
|
public fun get_sequence(vaa: &VAA): u64 {
|
|
vaa.sequence
|
|
}
|
|
|
|
public fun get_consistency_level(vaa: &VAA): u8 {
|
|
vaa.consistency_level
|
|
}
|
|
|
|
// break
|
|
|
|
public fun destroy(vaa: VAA): vector<u8> {
|
|
let VAA {
|
|
guardian_set_index: _,
|
|
signatures: _,
|
|
timestamp: _,
|
|
nonce: _,
|
|
emitter_chain: _,
|
|
emitter_address: _,
|
|
sequence: _,
|
|
consistency_level: _,
|
|
hash: _,
|
|
payload,
|
|
} = vaa;
|
|
payload
|
|
}
|
|
|
|
/// Verifies the signatures of a VAA.
|
|
/// It's private, because there's no point calling it externally, since VAAs
|
|
/// external to this module have already been verified (by construction).
|
|
fun verify(vaa: &VAA, guardian_set: &GuardianSet) {
|
|
assert!(state::guardian_set_is_active(guardian_set), E_GUARDIAN_SET_EXPIRED);
|
|
|
|
let guardians = get_guardians(guardian_set);
|
|
let hash = vaa.hash;
|
|
let sigs_len = vector::length<Signature>(&vaa.signatures);
|
|
let guardians_len = vector::length<Guardian>(&guardians);
|
|
|
|
assert!(sigs_len >= quorum(guardians_len), E_NO_QUORUM);
|
|
|
|
let sig_i = 0;
|
|
let last_index = 0;
|
|
while (sig_i < sigs_len) {
|
|
let (sig, recovery_id, guardian_index) = unpack_signature(vector::borrow(&vaa.signatures, sig_i));
|
|
|
|
// Ensure that the provided signatures are strictly increasing.
|
|
// This check makes sure that no duplicate signers occur. The
|
|
// increasing order is guaranteed by the guardians, or can always be
|
|
// reordered by the client.
|
|
assert!(sig_i == 0 || guardian_index > last_index, E_NON_INCREASING_SIGNERS);
|
|
last_index = guardian_index;
|
|
|
|
let address = guardian_pubkey::from_signature(hash, recovery_id, &sig);
|
|
|
|
let cur_guardian = vector::borrow<Guardian>(&guardians, (guardian_index as u64));
|
|
let cur_address = get_address(cur_guardian);
|
|
|
|
assert!(address == cur_address, E_INVALID_SIGNATURE);
|
|
|
|
sig_i = sig_i + 1;
|
|
};
|
|
}
|
|
|
|
/// Parses and verifies the signatures of a VAA.
|
|
/// NOTE: this is the only public function that returns a VAA, and it should
|
|
/// be kept that way. This ensures that if an external module receives a
|
|
/// `VAA`, it has been verified.
|
|
public fun parse_and_verify(bytes: vector<u8>): VAA {
|
|
let vaa = parse(bytes);
|
|
let guardian_set = state::get_guardian_set(vaa.guardian_set_index);
|
|
verify(&vaa, &guardian_set);
|
|
vaa
|
|
}
|
|
|
|
/// Aborts if the VAA is not governance (i.e. sent from the governance
|
|
/// emitter on the governance chain)
|
|
public fun assert_governance(vaa: &VAA) {
|
|
let latest_guardian_set_index = state::get_current_guardian_set_index();
|
|
assert!(vaa.guardian_set_index == latest_guardian_set_index, E_OLD_GUARDIAN_SET_GOVERNANCE);
|
|
assert!(vaa.emitter_chain == state::get_governance_chain(), E_INVALID_GOVERNANCE_CHAIN);
|
|
assert!(vaa.emitter_address == state::get_governance_contract(), E_INVALID_GOVERNANCE_EMITTER);
|
|
}
|
|
|
|
/// Aborts if the VAA has already been consumed. Marks the VAA as consumed
|
|
/// the first time around.
|
|
/// Only to be used for core bridge messages. Protocols should implement
|
|
/// their own replay protection.
|
|
public(friend) fun replay_protect(vaa: &VAA) {
|
|
// this calls table::add which aborts if the key already exists
|
|
state::set_governance_action_consumed(vaa.hash);
|
|
}
|
|
|
|
/// Returns the minimum number of signatures required for a VAA to be valid.
|
|
public fun quorum(num_guardians: u64): u64 {
|
|
(num_guardians * 2) / 3 + 1
|
|
}
|
|
|
|
}
|
|
|
|
#[test_only]
|
|
module wormhole::vaa_test {
|
|
use wormhole::guardian_set_upgrade;
|
|
use wormhole::wormhole;
|
|
use wormhole::vaa;
|
|
use wormhole::structs::{create_guardian};
|
|
use wormhole::u32;
|
|
|
|
/// A test VAA signed by the first guardian set (index 0) containing guardian a single
|
|
/// guardian beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
|
|
/// It's a governance VAA (contract upgrade), so we can test all sorts of
|
|
/// properties
|
|
const GOV_VAA: vector<u8> = x"010000000001000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
|
|
|
|
/// Identical VAA except it's signed by guardian set 1, and double signed by
|
|
/// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
|
|
/// Used to test that a single guardian can't supply multiple signatures
|
|
const GOV_VAA_DOUBLE_SIGNED: vector<u8> = x"010000000102000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a301000da16466429ee8ffb09b90ca90db8326d20cfeeae0542da9dcaaad641a5aca2d6c1fe33a5970ca84fd0ff5e6d29ef9e40404eb1a8892b509f085fc725b9e23a30100000001000000010001000000000000000000000000000000000000000000000000000000000000000400000000020b10360000000000000000000000000000000000000000000000000000000000436f7265010016d8f30e4a345ea0fa5df11daac4e1866ee368d253209cf9eda012d915a2db09e6";
|
|
|
|
/// A test VAA signed by the second guardian set (index 1) with the following two guardians:
|
|
/// 0: beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
|
|
/// 1: 90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
|
|
const GOV_VAA_2: vector<u8> = x"0100000001020052da07c7ba7d58661e22922a1130e75732f454e81086330f9a5337797ee7ee9d703fd55aabc257c4d53d8ab1e471e4eb1f2767bf37cc6d3d6774e2ca3ab429eb00018c9859f14027c2a62563028a2a9bbb30464ce5b86d13728b02fb85b34761d258154bb59bad87908c9b09342efa9045d4420d289bb0144729eb368ec50c45e719010000000100000001000100000000000000000000000000000000000000000000000000000000000000040000000004cdedc90000000000000000000000000000000000000000000000000000000000436f72650100167759324e86f870265b8648ef8d5ef505b2ae99840a616081eb7adc13995204a4";
|
|
|
|
/// Set up wormhole with the initial guardian
|
|
/// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe
|
|
fun setup() {
|
|
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
|
|
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
|
|
let _wormhole = wormhole::init_test(
|
|
22,
|
|
1,
|
|
x"0000000000000000000000000000000000000000000000000000000000000004",
|
|
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// Ensures that the GOV_VAA can still be verified after the guardian set
|
|
/// upgrade before expiry
|
|
public fun test_guardian_set_not_expired() {
|
|
setup();
|
|
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
|
|
|
|
// fast forward time before expiration
|
|
std::timestamp::fast_forward_seconds(80000);
|
|
|
|
// we still expect this to verify
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA));
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 3, location = wormhole::vaa)] // E_GUARDIAN_SET_EXPIRED
|
|
/// Ensures that the GOV_VAA can no longer be verified after the guardian set
|
|
/// upgrade after expiry
|
|
public fun test_guardian_set_expired() {
|
|
setup();
|
|
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
|
|
|
|
// fast forward time beyond expiration
|
|
std::timestamp::fast_forward_seconds(90000);
|
|
|
|
// we expect this to fail because the guardian set has expired
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA));
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 8, location = wormhole::vaa)] // E_OLD_GUARDIAN_SET_GOVERNANCE
|
|
/// Ensures that governance GOV_VAAs can only be verified by the latest guardian
|
|
/// set, even if the signer hasn't expired yet
|
|
public fun test_governance_guardian_set_latest() {
|
|
setup();
|
|
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[create_guardian(x"71aa1be1d36cafe3867910f99c09e347899c19c3")]);
|
|
|
|
// fast forward time before expiration
|
|
std::timestamp::fast_forward_seconds(80000);
|
|
|
|
// we still expect this to verify
|
|
let vaa = vaa::parse_and_verify(GOV_VAA);
|
|
|
|
// but fail here
|
|
vaa::assert_governance(&vaa);
|
|
vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 5, location = wormhole::vaa)] // E_INVALID_GOVERNANCE_EMITTER
|
|
/// Ensures that governance GOV_VAAs can only be sent from the correct governance emitter
|
|
public fun test_invalid_governance_emitter() {
|
|
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
|
|
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
|
|
let _wormhole = wormhole::init_test(
|
|
22,
|
|
1,
|
|
// Note the different governance emitter address here
|
|
x"0000000000000000000000000000000000000000000000000000000000000003",
|
|
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
|
|
0
|
|
);
|
|
|
|
// we still expect this to verify
|
|
let vaa = vaa::parse_and_verify(GOV_VAA);
|
|
|
|
// but fail here
|
|
vaa::assert_governance(&vaa);
|
|
vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 4, location = wormhole::vaa)] // E_INVALID_GOVERNANCE_CHAIN
|
|
/// Ensures that governance GOV_VAAs can only be sent from the correct governance chain
|
|
public fun test_invalid_governance_chain() {
|
|
let aptos_framework = std::account::create_account_for_test(@aptos_framework);
|
|
std::timestamp::set_time_has_started_for_testing(&aptos_framework);
|
|
let _wormhole = wormhole::init_test(
|
|
22,
|
|
// Note the different governance chain here
|
|
2,
|
|
x"0000000000000000000000000000000000000000000000000000000000000004",
|
|
x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe",
|
|
0
|
|
);
|
|
|
|
// we still expect this to verify
|
|
let vaa = vaa::parse_and_verify(GOV_VAA);
|
|
|
|
// but fail here
|
|
vaa::assert_governance(&vaa);
|
|
vaa::destroy(vaa);
|
|
}
|
|
|
|
#[test]
|
|
public fun test_quorum() {
|
|
setup();
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[
|
|
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
|
|
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
|
|
]);
|
|
|
|
// we expect this to succeed because both guardians signed in the correct order
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 0, location = wormhole::vaa)] // NO_QUORUM
|
|
public fun test_no_quorum() {
|
|
setup();
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[
|
|
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
|
|
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
|
|
create_guardian(x"5e1487f35515d02a92753504a8d75471b9f49edb")
|
|
]);
|
|
|
|
// we expect this to fail because we don't have enough signatures
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 7, location = wormhole::vaa)] // E_NON_INCREASING_SIGNERS
|
|
public fun test_double_signed() {
|
|
setup();
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[
|
|
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
|
|
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
|
|
]);
|
|
|
|
// we expect this to fail because
|
|
// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe signed this twice
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA_DOUBLE_SIGNED));
|
|
}
|
|
|
|
#[test]
|
|
#[expected_failure(abort_code = 2, location = wormhole::vaa)] // E_INVALID_SIGNATURE
|
|
public fun test_out_of_order_signers() {
|
|
setup();
|
|
// do an upgrade
|
|
guardian_set_upgrade::do_upgrade_test(
|
|
u32::from_u64(1),
|
|
vector[
|
|
// note that the guardians are set up in the other way arond now
|
|
create_guardian(x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"),
|
|
create_guardian(x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"),
|
|
]);
|
|
|
|
// we expect this to fail because
|
|
// beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe signed this twice
|
|
vaa::destroy(vaa::parse_and_verify(GOV_VAA_2));
|
|
}
|
|
}
|