Skip to content

Commit

Permalink
refactor(auth): extract validation to weight signers and proof (#140)
Browse files Browse the repository at this point in the history
Co-authored-by: Milap Sheth <milap@interoplabs.io>
  • Loading branch information
npty and milapsheth authored Sep 17, 2024
1 parent 48ea98d commit 9716da3
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 86 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-numbers-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@axelar-network/axelar-cgp-sui': patch
---

refactor: auth module
2 changes: 1 addition & 1 deletion move/axelar_gateway/Move.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ dependencies = [
]

[move.toolchain-version]
compiler-version = "1.32.2"
compiler-version = "1.32.0"
edition = "2024.beta"
flavor = "sui"
88 changes: 6 additions & 82 deletions move/axelar_gateway/sources/auth.move
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
module axelar_gateway::auth;

use axelar_gateway::bytes32::{Self, Bytes32};
use axelar_gateway::proof::{Proof, Signature};
use axelar_gateway::weighted_signer;
use axelar_gateway::proof::{Proof};
use axelar_gateway::weighted_signers::WeightedSigners;
use sui::bcs;
use sui::clock::Clock;
Expand All @@ -12,15 +11,9 @@ use sui::table::{Self, Table};
// ------
// Errors
// ------
const EInvalidWeights: u64 = 0;
const EInvalidThreshold: u64 = 1;
/// For when operators have changed, and proof is no longer valid.
const EInvalidOperators: u64 = 2;
const EInsufficientRotationDelay: u64 = 3;
const EInsufficientRotationDelay: u64 = 0;
/// For when number of signatures for the call approvals is below the threshold.
const ELowSignaturesWeight: u64 = 4;
const EMalformedSigners: u64 = 5;
const EInvalidEpoch: u64 = 6;
const EInvalidEpoch: u64 = 1;

// -----
// Types
Expand Down Expand Up @@ -109,17 +102,11 @@ public(package) fun validate_proof(
EInvalidEpoch,
);

let message = MessageToSign {
proof.validate(bcs::to_bytes(&MessageToSign {
domain_separator: self.domain_separator,
signers_hash,
data_hash,
};

validate_signatures(
bcs::to_bytes(&message),
signers,
proof.signatures(),
);
}));

is_latest_signers
}
Expand All @@ -130,7 +117,7 @@ public(package) fun rotate_signers(
new_signers: WeightedSigners,
enforce_rotation_delay: bool,
) {
validate_signers(&new_signers);
new_signers.validate();

self.update_rotation_timestamp(clock, enforce_rotation_delay);

Expand All @@ -152,69 +139,6 @@ public(package) fun rotate_signers(
// Internal Functions
// ------------------

fun validate_signatures(
message: vector<u8>,
signers: &WeightedSigners,
signatures: &vector<Signature>,
) {
let signers_length = signers.signers().length();
let signatures_length = signatures.length();
assert!(signatures_length != 0, ELowSignaturesWeight);

let threshold = signers.threshold();
let mut signer_index = 0;
let mut total_weight = 0;
let mut i = 0;

while (i < signatures_length) {
let pub_key = signatures[i].recover_pub_key(&message);

while (
signer_index < signers_length &&
signers.signers()[signer_index].pub_key() != pub_key
) {
signer_index = signer_index + 1;
};

assert!(signer_index < signers_length, EMalformedSigners);

total_weight = total_weight + signers.signers()[signer_index].weight();

if (total_weight >= threshold) {
return
};

signer_index = signer_index + 1;
i = i + 1;
};

abort ELowSignaturesWeight
}

fun validate_signers(signers: &WeightedSigners) {
let signers_length = signers.signers().length();
assert!(signers_length != 0, EInvalidOperators);

let mut total_weight = 0;
let mut i = 0;
let mut previous_signer = weighted_signer::default();

while (i < signers_length) {
let current_signer = signers.signers()[i];
assert!(previous_signer.lt(&current_signer), EInvalidOperators);

let weight = current_signer.weight();
assert!(weight != 0, EInvalidWeights);

total_weight = total_weight + weight;
i = i + 1;
previous_signer = current_signer;
};

let threshold = signers.threshold();
assert!(threshold != 0 && total_weight >= threshold, EInvalidThreshold);
}

fun update_rotation_timestamp(
self: &mut AxelarSigners,
clock: &Clock,
Expand Down
57 changes: 57 additions & 0 deletions move/axelar_gateway/sources/types/proof.move
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const SIGNATURE_LENGTH: u64 = 65;
// ------
/// Invalid length of the bytes
const EInvalidLength: u64 = 0;
const ELowSignaturesWeight: u64 = 1;
const EMalformedSigners: u64 = 2;

// ----------------
// Public Functions
Expand Down Expand Up @@ -60,6 +62,61 @@ public(package) fun recover_pub_key(
ecdsa::secp256k1_ecrecover(&self.bytes, message, 0)
}

/// Validates the signatures of a message against the signers.
/// The total weight of the signatures must be greater than or equal to the threshold.
/// Otherwise, the error `ELowSignaturesWeight` is raised.
public(package) fun validate(self: &Proof, message: vector<u8>) {
let signers = &self.signers;
let signatures = &self.signatures;
assert!(signatures.length() != 0, ELowSignaturesWeight);

let threshold = signers.threshold();
let signatures_length = signatures.length();
let mut total_weight: u128 = 0;
let mut signer_index = 0;
let mut i = 0;

while (i < signatures_length) {
let pub_key = signatures[i].recover_pub_key(&message);

let (weight, index) = find_weight_by_pub_key_from(
signers,
signer_index,
&pub_key,
);

total_weight = total_weight + weight;

if (total_weight >= threshold) return;

i = i + 1;
signer_index = index + 1;
};

abort ELowSignaturesWeight
}

/// Finds the weight of a signer in the weighted signers by its public key.
fun find_weight_by_pub_key_from(
signers: &WeightedSigners,
signer_index: u64,
pub_key: &vector<u8>,
): (u128, u64) {
let signers = signers.signers();
let length = signers.length();
let mut index = signer_index;

// Find the first signer that satisfies the predicate
while (index < length && signers[index].pub_key() != pub_key) {
index = index + 1;
};

// If no signer satisfies the predicate, return an error
assert!(index < length, EMalformedSigners);

(signers[index].weight(), index)
}

public(package) fun peel_signature(bcs: &mut BCS): Signature {
let bytes = bcs.peel_vec_u8();

Expand Down
5 changes: 5 additions & 0 deletions move/axelar_gateway/sources/types/weighted_signer.move
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public fun weight(self: &WeightedSigner): u128 {
// ------

const EInvalidPubKeyLength: u64 = 0;
const EInvalidWeight: u64 = 1;

// -----------------
// Package Functions
Expand Down Expand Up @@ -60,6 +61,10 @@ public(package) fun peel(bcs: &mut BCS): WeightedSigner {
new(pub_key, weight)
}

public(package) fun validate(self: &WeightedSigner) {
assert!(self.weight != 0, EInvalidWeight);
}

/// Check if self.signer is less than other.signer as bytes
public(package) fun lt(self: &WeightedSigner, other: &WeightedSigner): bool {
let mut i = 0;
Expand Down
55 changes: 52 additions & 3 deletions move/axelar_gateway/sources/types/weighted_signers.move
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,42 @@ public struct WeightedSigners has copy, drop, store {
/// ------
/// Invalid length of the bytes
const EInvalidLength: u64 = 0;
const EInvalidThreshold: u64 = 1;
/// Invalid signer weights or threshold
const EInvalidSigners: u64 = 2;
const EInvalidSignerOrder: u64 = 3;

/// -----------------
/// Package Functions
/// -----------------

/// Decode a `WeightedSigners` from the BCS encoded bytes.
public(package) fun peel(bcs: &mut BCS): WeightedSigners {
let len = bcs.peel_vec_length();
assert!(len > 0, EInvalidLength);

WeightedSigners {
signers: vector::tabulate!(len, |_| weighted_signer::peel(bcs)),
threshold: bcs.peel_u128(),
nonce: bytes32::peel(bcs)
nonce: bytes32::peel(bcs),
}
}

/// Validates the weighted signers. The following must be true:
/// 1. The signers are in ascending order by their public key.
/// 2. The threshold is greater than zero.
/// 3. The threshold is less than or equal to the total weight of the signers.
public(package) fun validate(self: &WeightedSigners) {
self.validate_signers();
self.validate_threshold();
}

public(package) fun hash(self: &WeightedSigners): Bytes32 {
bytes32::from_bytes(hash::keccak256(&bcs::to_bytes(self)))
}

public(package) fun signers(self: &WeightedSigners): vector<WeightedSigner> {
self.signers
public(package) fun signers(self: &WeightedSigners): &vector<WeightedSigner> {
&self.signers
}

public(package) fun threshold(self: &WeightedSigners): u128 {
Expand All @@ -47,6 +62,40 @@ public(package) fun nonce(self: &WeightedSigners): Bytes32 {
self.nonce
}

/// -----
/// Internal Functions
/// -----

/// Validates the order of the signers and the length of the signers.
/// The signers must be in ascending order by their public key.
/// Otherwise, the error `EInvalidSigners` is raised.
fun validate_signers(self: &WeightedSigners) {
assert!(!self.signers.is_empty(), EInvalidSigners);
let mut previous = &weighted_signer::default();
self.signers.do_ref!(
|signer| {
signer.validate();
assert!(previous.lt(signer), EInvalidSignerOrder);
previous = signer;
},
);
}

/// Calculates the total weight of the signers.
fun total_weight(self: &WeightedSigners): u128 {
self.signers.fold!<WeightedSigner, u128>(
0,
|acc, signer| acc + signer.weight(),
)
}

/// Validates the threshold.
/// The threshold must be greater than zero and less than or equal to the total weight of the signers.
/// Otherwise, the error `EInvalidThreshold` is raised.
fun validate_threshold(self: &WeightedSigners) {
assert!(self.threshold != 0 && self.total_weight() >= self.threshold, EInvalidThreshold);
}

#[test_only]
public fun create_for_testing(
signers: vector<WeightedSigner>,
Expand Down

0 comments on commit 9716da3

Please sign in to comment.