diff --git a/.changeset/fast-numbers-battle.md b/.changeset/fast-numbers-battle.md new file mode 100644 index 00000000..46bc112a --- /dev/null +++ b/.changeset/fast-numbers-battle.md @@ -0,0 +1,5 @@ +--- +'@axelar-network/axelar-cgp-sui': patch +--- + +refactor: auth module diff --git a/move/axelar_gateway/Move.lock b/move/axelar_gateway/Move.lock index e008d0ac..d08b8503 100644 --- a/move/axelar_gateway/Move.lock +++ b/move/axelar_gateway/Move.lock @@ -30,6 +30,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.32.2" +compiler-version = "1.32.0" edition = "2024.beta" flavor = "sui" diff --git a/move/axelar_gateway/sources/auth.move b/move/axelar_gateway/sources/auth.move index 5e1bdf7d..e9fe2d03 100644 --- a/move/axelar_gateway/sources/auth.move +++ b/move/axelar_gateway/sources/auth.move @@ -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; @@ -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 @@ -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 } @@ -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); @@ -152,69 +139,6 @@ public(package) fun rotate_signers( // Internal Functions // ------------------ -fun validate_signatures( - message: vector, - signers: &WeightedSigners, - signatures: &vector, -) { - 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(¤t_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, diff --git a/move/axelar_gateway/sources/types/proof.move b/move/axelar_gateway/sources/types/proof.move index 2e3575fd..ab0c4c37 100644 --- a/move/axelar_gateway/sources/types/proof.move +++ b/move/axelar_gateway/sources/types/proof.move @@ -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 @@ -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) { + 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, +): (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(); diff --git a/move/axelar_gateway/sources/types/weighted_signer.move b/move/axelar_gateway/sources/types/weighted_signer.move index 18c0f35a..9b6f6824 100644 --- a/move/axelar_gateway/sources/types/weighted_signer.move +++ b/move/axelar_gateway/sources/types/weighted_signer.move @@ -31,6 +31,7 @@ public fun weight(self: &WeightedSigner): u128 { // ------ const EInvalidPubKeyLength: u64 = 0; +const EInvalidWeight: u64 = 1; // ----------------- // Package Functions @@ -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; diff --git a/move/axelar_gateway/sources/types/weighted_signers.move b/move/axelar_gateway/sources/types/weighted_signers.move index bffa5079..d3ac18bd 100644 --- a/move/axelar_gateway/sources/types/weighted_signers.move +++ b/move/axelar_gateway/sources/types/weighted_signers.move @@ -16,10 +16,16 @@ 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); @@ -27,16 +33,25 @@ public(package) fun peel(bcs: &mut BCS): WeightedSigners { 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 { - self.signers +public(package) fun signers(self: &WeightedSigners): &vector { + &self.signers } public(package) fun threshold(self: &WeightedSigners): u128 { @@ -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!( + 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,