Skip to content

Commit

Permalink
feat: new gateway: initialize signing session + verify signature ixs …
Browse files Browse the repository at this point in the history
…(#495)

* chore: implement init sig session instruction

* chore: remove execute_data_buffer module

* chore: deref VerifierSetLeafNode + getset

* refactor: signature verification

* refactor: signature verification session full flow

* tests: more tests for the signature verification session

* chore: rename "execute data" to "signature verification"

* feat: initialize payload verification session instruction

* chore: wrapper type for VerifierSetLeafNode

* feat: implement signature verification instruction

* fix: typo

* fix: typos

* clippy: lint

* test: use a test case builder

* test: refactor instruction inputs generation

* test: full payload verification

* tests: configurable signer set size

* fix: set bytemuck derive flag

* fix: remove uses of `Result::inspect_err`

* fix: reverse toolchain changes

didn't mean to commit that

* fix: remove unused dep from old gateway

* chore: use a forcibly aligned array for bytemuck

* chore: use result-based assertions

* chore: remove getter methods from VerifierSetElement

* chore: remove fn init_pda_with_dynamic_size

* fix: unresolved imports

* fix: unresolved imports

* fix: remove unused dependencies

* fix: use payload merkle root during signature verification

* chore: check verifier set tracker epoch
  • Loading branch information
tilacog authored Oct 30, 2024
1 parent b9518ca commit 628b64e
Show file tree
Hide file tree
Showing 26 changed files with 1,423 additions and 1,578 deletions.
5 changes: 3 additions & 2 deletions solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ bitvec = "1.0.1"
bnum = { version = "0.10", features = ["serde"] }
borsh = { version = "1", features = ["derive"] }
bs58 = "0.5.1"
bytemuck = "1"
bytemuck = { version = "1", features = ["derive"] }
cosmwasm-schema = "2"
cosmwasm-std = "1.5"
ed25519-dalek = "2"
Expand Down Expand Up @@ -95,11 +95,12 @@ solana-program = "2.0.1"
solana-program-test = "2.0.1"
solana-sdk = "2.0.1"
solana-test-validator = "2.0.1"
spl-associated-token-account = "5.0.1"
spl-associated-token-account = "5.0.1"
spl-pod = "0.4.0"
spl-token = "6.0.0"
spl-token-2022 = "5.0.2"
spl-token-metadata-interface = "0.5.1"
static_assertions = "1"
test-log = { version = "0.2", features = ["trace"], default-features = false }
thiserror = "1"
tokio = { version = "1", features = ["full"] }
Expand Down
21 changes: 15 additions & 6 deletions solana/crates/axelar-rkyv-encoding/src/test_fixtures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,9 @@ pub fn random_message_with_destination_and_payload(
message
}

pub fn random_valid_verifier_set() -> VerifierSet {
pub fn random_valid_verifier_set_fixed_size(num_signers: usize) -> VerifierSet {
let mut signers = BTreeMap::new();
let mut total_weight = BnumU128::ZERO;

let num_signers = OsRng.gen_range(1..10);
for _ in 0..num_signers {
let pubkey = random_public_key();
let weight = random_weight();
Expand All @@ -80,6 +78,11 @@ pub fn random_valid_verifier_set() -> VerifierSet {
VerifierSet::new(OsRng.gen(), signers, total_weight.into(), random_bytes())
}

pub fn random_valid_verifier_set() -> VerifierSet {
let num_signers = OsRng.gen_range(1..10);
random_valid_verifier_set_fixed_size(num_signers)
}

pub fn random_proof(message: &[u8]) -> Proof {
let num_signatures = OsRng.gen_range(1..10);
let signatures = (0..num_signatures)
Expand Down Expand Up @@ -191,14 +194,13 @@ pub fn random_execute_data() -> ExecuteData {
ExecuteData::new(payload, proof)
}

fn random_verifier_set_and_signing_keys(
pub fn random_verifier_set_and_signing_keys_fixed_size(
num_signers: usize,
domain_separator: [u8; 32],
) -> (VerifierSet, BTreeMap<PublicKey, TestSigningKey>) {
let mut signers = BTreeMap::new();
let mut signing_keys = BTreeMap::new();
let mut total_weight = BnumU128::ZERO;

let num_signers = OsRng.gen_range(1..10);
for _ in 0..num_signers {
let (signing_key, public_key) = random_keypair();
let weight = random_weight();
Expand All @@ -211,6 +213,13 @@ fn random_verifier_set_and_signing_keys(
(verifier_set, signing_keys)
}

pub fn random_verifier_set_and_signing_keys(
domain_separator: [u8; 32],
) -> (VerifierSet, BTreeMap<PublicKey, TestSigningKey>) {
let num_signers = OsRng.gen_range(1..10);
random_verifier_set_and_signing_keys_fixed_size(num_signers, domain_separator)
}

pub fn random_valid_execute_data_and_verifier_set_for_payload(
domain_separator: [u8; 32],
payload: Payload,
Expand Down
39 changes: 33 additions & 6 deletions solana/crates/axelar-rkyv-encoding/src/types/verifier_set.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::error::Error;
use std::marker::PhantomData;
use std::ops::Deref;

use rkyv::bytecheck::{self, CheckBytes, StructCheckError};
use rkyv::validation::validators::DefaultValidatorError;
Expand Down Expand Up @@ -88,6 +89,7 @@ impl VerifierSet {
signer_pubkey: *signer_pubkey,
signer_weight: (*signer_weight).into(),
position: position as u16,
set_size: self.signers.len() as u16,
},
)
}
Expand Down Expand Up @@ -137,21 +139,34 @@ impl ArchivedVerifierSet {
}

/// A `VerifierSet` element.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct VerifierSetElement {
created_at: u64,
quorum: u128,
signer_pubkey: PublicKey,
signer_weight: u128,
domain_separator: [u8; 32],
position: u16,
pub created_at: u64,
pub quorum: u128,
pub signer_pubkey: PublicKey,
pub signer_weight: u128,
pub domain_separator: [u8; 32],
pub position: u16,
pub set_size: u16,
}

/// Wraps a [`VerifierSetElement`], is generic over the hashing context.
///
/// This type is the leaf node of a [`VerifierSet`]'s Merkle tree.
#[derive(Clone, Copy)]
pub struct VerifierSetLeafNode<T: rs_merkle::Hasher<Hash = [u8; 32]>> {
element: VerifierSetElement,
hasher: PhantomData<T>,
}

impl<T: rs_merkle::Hasher<Hash = [u8; 32]>> Deref for VerifierSetLeafNode<T> {
type Target = VerifierSetElement;

fn deref(&self) -> &Self::Target {
&self.element
}
}

impl<'a, T> VerifierSetLeafNode<T>
where
T: rs_merkle::Hasher<Hash = [u8; 32]>,
Expand All @@ -178,6 +193,18 @@ where
}
}

impl<T> From<VerifierSetElement> for VerifierSetLeafNode<T>
where
T: rs_merkle::Hasher<Hash = [u8; 32]>,
{
fn from(element: VerifierSetElement) -> Self {
Self {
element,
hasher: PhantomData,
}
}
}

impl From<VerifierSetLeafNode<SolanaSyscallHasher>> for [u8; 32] {
fn from(leaf_node: VerifierSetLeafNode<SolanaSyscallHasher>) -> Self {
leaf_node.leaf_hash::<SolanaKeccak256Hasher>()
Expand Down
14 changes: 14 additions & 0 deletions solana/helpers/axelar-message-primitives/src/u256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ impl U256 {
}
}

impl PartialOrd for U256 {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for U256 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let lhs = bnum::types::U256::from_digits(self.0);
let rhs = bnum::types::U256::from_digits(other.0);
lhs.cmp(&rhs)
}
}

impl From<u8> for U256 {
fn from(value: u8) -> Self {
U256(bnum::types::U256::from(value).into())
Expand Down
5 changes: 3 additions & 2 deletions solana/programs/axelar-solana-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ no-entrypoint = []
test-sbf = []

[dependencies]
arrayref.workspace = true
axelar-message-primitives.workspace = true
axelar-rkyv-encoding.workspace = true
base64.workspace = true
bincode.workspace = true
bitflags.workspace = true
bitvec.workspace = true
borsh.workspace = true
bytemuck.workspace = true
hex.workspace = true
itertools.workspace = true
libsecp256k1.workspace = true
Expand All @@ -34,6 +33,7 @@ num-traits.workspace = true
program-utils.workspace = true
rkyv.workspace = true
solana-program.workspace = true
static_assertions.workspace = true
thiserror.workspace = true

[dev-dependencies]
Expand All @@ -44,5 +44,6 @@ solana-client.workspace = true
solana-logger.workspace = true
solana-program-test.workspace = true
solana-sdk.workspace = true
rand.workspace = true
test-fixtures.workspace = true
typed-builder.workspace = true
10 changes: 10 additions & 0 deletions solana/programs/axelar-solana-gateway/src/axelar_auth_weighted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ impl AxelarAuthWeighted {
pub fn current_epoch(&self) -> U256 {
self.current_epoch
}

/// Returns `true` if the current epoch is still considered valid given the
/// signer retention policies.
pub fn is_epoch_valid(&self, epoch: U256) -> Result<bool, AxelarAuthWeightedError> {
let earliest_valid_epoch = self
.current_epoch
.checked_sub(self.previous_signers_retention)
.ok_or(AxelarAuthWeightedError::EpochCalculationOverflow)?;
Ok(epoch >= earliest_valid_epoch)
}
}

fn validate_proof_for_message(
Expand Down
86 changes: 75 additions & 11 deletions solana/programs/axelar-solana-gateway/src/instructions.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
//! Instruction types
mod proxy_types;

use std::fmt::Debug;

use axelar_rkyv_encoding::hasher::merkle_tree::{MerkleProof, SolanaSyscallHasher};
use axelar_rkyv_encoding::types::{Signature, VerifierSetLeafNode};
use borsh::{to_vec, BorshDeserialize, BorshSerialize};
use itertools::Itertools;
use solana_program::instruction::{AccountMeta, Instruction};
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;

use self::proxy_types::{ProxySignature, ProxyVerifierSetLeafNode};
use crate::axelar_auth_weighted::{RotationDelaySecs, SignerSetEpoch};
use crate::state::verifier_set_tracker::VerifierSetHash;

Expand Down Expand Up @@ -80,23 +85,21 @@ pub enum GatewayInstruction {
///
/// Accounts expected by this instruction:
/// 0. [] Gateway Root Config PDA account
/// 1. [WRITE] Execute Data PDA buffer account
/// 1. [WRITE] Verification session PDA buffer account
/// 2. [] Verifier Setr Tracker PDA account (the one that signed the
/// Payload's Merkle root)
VerifySignature {
/// The Merkle root for the Payload being verified.
payload_merkle_root: [u8; 32],
/// Contains all the required information for the
verifier_set_leaf_node: GatewayVerifierSetLeafNode,

verifier_set_leaf_node: ProxyVerifierSetLeafNode,
/// The signer's proof of inclusion in the verifier set Merkle tree.
signer_merkle_proof: Vec<u8>,
verifier_merkle_proof: Vec<u8>,
/// The signer's digital signature over `payload_merkle_root`
signature: ProxySignature,
},
}

/// Wrapper around `VerifierSetLeafNode<_>` which implement borsh traits.
#[derive(BorshSerialize, BorshDeserialize, Eq, PartialEq, Debug)]
pub struct GatewayVerifierSetLeafNode {
// TODO: implement this.
}

/// Configuration parameters for initializing the axelar-solana gateway
#[derive(Debug, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct InitializeConfig<T> {
Expand Down Expand Up @@ -274,6 +277,67 @@ pub fn initialize_config(
})
}

/// Creates a [`GatewayInstruction::InitializePayloadVerificationSession`]
/// instruction.
pub fn initialize_payload_verification_session(
payer: Pubkey,
gateway_config_pda: Pubkey,
payload_merkle_root: [u8; 32],
) -> Result<Instruction, ProgramError> {
let (verification_session_pda, bump_seed) =
crate::get_signature_verification_pda(&gateway_config_pda, &payload_merkle_root);

let accounts = vec![
AccountMeta::new(payer, true),
AccountMeta::new_readonly(gateway_config_pda, false),
AccountMeta::new(verification_session_pda, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
];

let data = to_vec(&GatewayInstruction::InitializePayloadVerificationSession {
payload_merkle_root,
bump_seed,
})?;

Ok(Instruction {
program_id: crate::id(),
accounts,
data,
})
}

/// Creates a [`GatewayInstruction::VerifySignature`] instruction.
pub fn verify_signature(
gateway_config_pda: Pubkey,
verifier_set_tracker_pda: Pubkey,
payload_merkle_root: [u8; 32],
verifier_set_leaf_node: VerifierSetLeafNode<SolanaSyscallHasher>,
verifier_merkle_proof: MerkleProof<SolanaSyscallHasher>,
signature: Signature,
) -> Result<Instruction, ProgramError> {
let (verification_session_pda, _bump) =
crate::get_signature_verification_pda(&gateway_config_pda, &payload_merkle_root);

let accounts = vec![
AccountMeta::new_readonly(gateway_config_pda, false),
AccountMeta::new(verification_session_pda, false),
AccountMeta::new_readonly(verifier_set_tracker_pda, false),
];

let data = to_vec(&GatewayInstruction::VerifySignature {
payload_merkle_root,
verifier_set_leaf_node: verifier_set_leaf_node.into(),
verifier_merkle_proof: verifier_merkle_proof.to_bytes(),
signature: signature.into(),
})?;

Ok(Instruction {
program_id: crate::id(),
accounts,
data,
})
}

#[cfg(test)]
pub mod tests {

Expand Down
Loading

0 comments on commit 628b64e

Please sign in to comment.