diff --git a/move/sources/multisig.move b/move/sources/multisig.move index c00beb5..6e9c183 100644 --- a/move/sources/multisig.move +++ b/move/sources/multisig.move @@ -9,6 +9,7 @@ module multisig::multisig { use sui::hash::blake2b256; use sui::tx_context::{Self, TxContext}; use std::vector; + use multisig::utils; /// Error code indicating that the lengths of public keys and weights are not equal. const ELengthsOfPksAndWeightsAreNotEqual: u64 = 0; @@ -16,6 +17,9 @@ module multisig::multisig { /// Error code indicating that the threshold is positive and not greater than the sum of weights. const EThresholdIsPositiveAndNotGreaterThanTheSumOfWeights: u64 = 1; + /// Error code indicating that no permutation matches the expected multisig address. + const ENoPermutationMatchesTheExpectedAddress: u64 = 2; + /// Event emitted when a multisig address is created. struct MultisigAddressEvent has copy, drop { pks: vector>, @@ -162,4 +166,43 @@ module multisig::multisig { address::from_bytes(sui::hash::blake2b256(pk)) } + /// This function orders the public keys (pks) in all possible permutations and checks if the derived multisig address matches the expected multisig address. + /// It takes the expected multisig address, a vector of public keys (pks), a vector of weights corresponding to the public keys, and a threshold value as input. + /// The function returns the ordered public keys (pks) if a permutation matches the expected multisig address. + /// If no permutation matches the expected multisig address, it aborts with an error. + /// + /// Parameters: + /// - expected_ms_address: The expected multisig address to match. + /// - pks: A vector of vectors containing the public keys. + /// - weights: A vector of weights corresponding to the public keys. + /// - threshold: The threshold value for multisig. + /// + /// Returns: + /// - A vector of vectors containing the ordered public keys (pks) if a permutation matches the expected multisig address. + /// + /// Abort: + /// - ENoPermutationMatchesTheExpectedAddress: If no permutation matches the expected multisig address. + public fun order_pks( + expected_ms_address: address, + pks: vector>, + weights: vector, + threshold: u16, + ): vector> { + // loop through all the permutations of the pks vector + let perms = utils::permutations(&mut pks); + let n = vector::length(&perms); + let i = 0; + while (i < n) { + let perm: vector> = *vector::borrow(&perms, i); + let ms_address = derive_multisig_address_quiet(perm, weights, threshold); + // check if the ms_address matches the expected one + if (ms_address == expected_ms_address) { + return perm + }; + i = i + 1; + }; + + abort ENoPermutationMatchesTheExpectedAddress + } + } diff --git a/move/sources/utils.move b/move/sources/utils.move new file mode 100644 index 0000000..54196f9 --- /dev/null +++ b/move/sources/utils.move @@ -0,0 +1,90 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// This module contains the permutations functions for generating all possible permutations of a vector of vectors. +module multisig::utils { + use std::vector; + + /// Generates all possible permutations of a vector of vectors. + /// + /// This function takes a vector of vectors `pks` as input and generates all possible permutations of the vectors in `pks`. + /// The function uses a modified version of the Heap's algorithm to generate the permutations. + /// It initializes an empty vector `perms` to store the permutations and a vector `c` to encode the stack state. + /// The function iterates through the vectors in `pks` and swaps elements based on the parity of the iteration index. + /// It outputs each new permutation and increments the stack state accordingly. + /// Finally, it returns the vector `perms` containing all the generated permutations. + /// + /// # Arguments + /// + /// * `pks` - A vector of vectors representing the input vectors to generate permutations for. + /// + /// # Returns + /// + /// A vector of vectors representing all possible permutations of the input vectors. + /// + /// # Examples + /// + /// ``` + /// let pks = vector[ + /// vector[1, 2, 3], + /// vector[4, 5], + /// vector[6, 7, 8, 9] + /// ]; + /// + /// let perms = permutations(pks); + /// + /// assert!(vector::length(&perms) == 6, 0); + /// assert!(*vector::borrow(&perms, 0) == vector[1, 2, 3], 0); + /// assert!(*vector::borrow(&perms, 1) == vector[4, 5], 0); + /// assert!(*vector::borrow(&perms, 2) == vector[6, 7, 8, 9], 0); + /// assert!(*vector::borrow(&perms, 3) == vector[2, 1, 3], 0); + /// assert!(*vector::borrow(&perms, 4) == vector[5, 4], 0); + /// assert!(*vector::borrow(&perms, 5) == vector[7, 6, 8, 9], 0); + /// ``` + public fun permutations( + pks: &mut vector, + ): vector> { + // initialize an empty vector to store the permutations + let perms = vector::empty>(); + // get the length of the pks vector + let n = vector::length(pks); + // c is an encoding of the stack state. c[k] encodes the for-loop counter for when permutations(k - 1, pks) is called + let c = vector::empty(); + // initialize c with zeros + let i = 0; + while (i < n) { + vector::push_back(&mut c, 0); + i = i + 1 + }; + // output the first permutation + vector::push_back(&mut perms, *pks); + // i acts similarly to a stack pointer + i = 1; + // loop until i is equal to n + while (i < n) { + // check if c[i] is less than i + if (*vector::borrow(&c, i) < i) { + // swap elements depending on the parity of i + if (i % 2 == 0) { + vector::swap(pks, 0, i); + } else { + vector::swap(pks, *vector::borrow(&c, (i as u64)), i); + }; + // output the new permutation + vector::push_back(&mut perms, *pks); + // increment c[i] by 1 + *vector::borrow_mut(&mut c, i) = *vector::borrow(&c, i) + 1; + // reset i to 1 + i = 1; + } else { + // reset c[i] to 0, c[i] = 0; + *vector::borrow_mut(&mut c, i) = 0; + // increment i by 1 + i = i + 1; + }; + }; + // return the perms vector + perms + } + +} diff --git a/move/tests/multisig_tests.move b/move/tests/multisig_tests.move index 8c05d61..ab230cb 100644 --- a/move/tests/multisig_tests.move +++ b/move/tests/multisig_tests.move @@ -8,21 +8,21 @@ module multisig::multisig_unit_tests { #[test] fun test_derive_multisig_address() { // ED25519 - let address_ED25519: address = @0x73a6b3c33e2d63383de5c6786cbaca231ff789f4c853af6d54cb883d8780adc0; - let key_ED25519: vector = vector[0, 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117]; - assert!(ms::ed25519_key_to_address(&key_ED25519) == address_ED25519, 0); + let ed25519_address_: address = @0x73a6b3c33e2d63383de5c6786cbaca231ff789f4c853af6d54cb883d8780adc0; + let ed25519_key: vector = vector[0, 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117]; + assert!(ms::ed25519_key_to_address(&ed25519_key) == ed25519_address_, 0); // Secp256k1 - let address_Secp256k1: address = @0xd9607cd03428c904949572b51471e7a9f60019aeb9a3d7ee5e72921cab8e8be7; - let key_Secp256k1: vector = vector[1, 2, 14, 23, 205, 89, 57, 228, 107, 25, 102, 65, 150, 140, 215, 89, 145, 11, 162, 87, 126, 39, 250, 115, 253, 227, 135, 109, 185, 190, 197, 188, 235, 43]; - assert!(ms::secp256k1_key_to_address(&key_Secp256k1) == address_Secp256k1, 0); + let secp256k1_address_: address = @0xd9607cd03428c904949572b51471e7a9f60019aeb9a3d7ee5e72921cab8e8be7; + let secp256k1_key: vector = vector[1, 2, 14, 23, 205, 89, 57, 228, 107, 25, 102, 65, 150, 140, 215, 89, 145, 11, 162, 87, 126, 39, 250, 115, 253, 227, 135, 109, 185, 190, 197, 188, 235, 43]; + assert!(ms::secp256k1_key_to_address(&secp256k1_key) == secp256k1_address_, 0); // Secp256r1 - let address_Secp256r1: address = @0x600b1081644fe46f76da3bdc19f8743b9f04458516364374c7d82959e790c19e; - let key_Secp256r1: vector = vector[2, 3, 71, 251, 175, 35, 240, 56, 171, 196, 195, 8, 162, 113, 17, 122, 42, 76, 255, 174, 221, 188, 95, 248, 28, 117, 23, 188, 108, 116, 167, 237, 180, 48]; - assert!(ms::secp256r1_key_to_address(&key_Secp256r1) == address_Secp256r1, 0); + let secp256r1_address: address = @0x600b1081644fe46f76da3bdc19f8743b9f04458516364374c7d82959e790c19e; + let secp256r1_key: vector = vector[2, 3, 71, 251, 175, 35, 240, 56, 171, 196, 195, 8, 162, 113, 17, 122, 42, 76, 255, 174, 221, 188, 95, 248, 28, 117, 23, 188, 108, 116, 167, 237, 180, 48]; + assert!(ms::secp256r1_key_to_address(&secp256r1_key) == secp256r1_address, 0); - let pks: vector> = vector[key_ED25519, key_Secp256k1, key_Secp256r1]; + let pks: vector> = vector[ed25519_key, secp256k1_key, secp256r1_key]; let weights: vector = vector[1, 1, 1]; let threshold: u16 = 2; @@ -31,4 +31,55 @@ module multisig::multisig_unit_tests { assert!(derived_multisig_address == expected_multisig_address, 0); } + #[test] + fun test_order_pks() { + let ed25519_key: vector = vector[0, 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117]; + let secp256k1_key: vector = vector[1, 2, 14, 23, 205, 89, 57, 228, 107, 25, 102, 65, 150, 140, 215, 89, 145, 11, 162, 87, 126, 39, 250, 115, 253, 227, 135, 109, 185, 190, 197, 188, 235, 43]; + let secp256r1_key: vector = vector[2, 3, 71, 251, 175, 35, 240, 56, 171, 196, 195, 8, 162, 113, 17, 122, 42, 76, 255, 174, 221, 188, 95, 248, 28, 117, 23, 188, 108, 116, 167, 237, 180, 48]; + + let pks0: vector> = vector[ed25519_key, secp256k1_key, secp256r1_key]; + let pks1: vector> = vector[secp256k1_key, ed25519_key, secp256r1_key]; + let pks2: vector> = vector[secp256r1_key, ed25519_key, secp256k1_key]; + let pks3: vector> = vector[ed25519_key, secp256r1_key, secp256k1_key]; + let pks4: vector> = vector[secp256k1_key, secp256r1_key, ed25519_key]; + let pks5: vector> = vector[secp256r1_key, secp256k1_key, ed25519_key]; + + let ordered_pks: vector> = vector[ed25519_key, secp256k1_key, secp256r1_key]; + let weights: vector = vector[1, 1, 1]; + let threshold: u16 = 2; + let multisig_address: address = @0x1c4dac7fb4c01a0c608db993711c451ad655a38b7f0a9571ff099f70090263a8; + + assert!(ms::order_pks(multisig_address, pks0, weights, threshold) == ordered_pks, 0); + assert!(ms::order_pks(multisig_address, pks1, weights, threshold) == ordered_pks, 0); + assert!(ms::order_pks(multisig_address, pks2, weights, threshold) == ordered_pks, 0); + assert!(ms::order_pks(multisig_address, pks3, weights, threshold) == ordered_pks, 0); + assert!(ms::order_pks(multisig_address, pks4, weights, threshold) == ordered_pks, 0); + assert!(ms::order_pks(multisig_address, pks5, weights, threshold) == ordered_pks, 0); + } + + #[test] + #[expected_failure(abort_code = ms::ENoPermutationMatchesTheExpectedAddress)] + fun test_failure_order_pks() { + let ed25519_key: vector = vector[0, 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117]; + let secp256k1_key: vector = vector[1, 2, 14, 23, 205, 89, 57, 228, 107, 25, 102, 65, 150, 140, 215, 89, 145, 11, 162, 87, 126, 39, 250, 115, 253, 227, 135, 109, 185, 190, 197, 188, 235, 43]; + let secp256r1_key: vector = vector[2, 3, 71, 251, 175, 35, 240, 56, 171, 196, 195, 8, 162, 113, 17, 122, 42, 76, 255, 174, 221, 188, 95, 248, 28, 117, 23, 188, 108, 116, 167, 237, 180, 48]; + + let pks0: vector> = vector[ed25519_key, secp256k1_key, secp256r1_key]; + let pks1: vector> = vector[secp256k1_key, ed25519_key, secp256r1_key]; + let pks2: vector> = vector[secp256r1_key, ed25519_key, secp256k1_key]; + let pks3: vector> = vector[ed25519_key, secp256r1_key, secp256k1_key]; + let pks4: vector> = vector[secp256k1_key, secp256r1_key, ed25519_key]; + let pks5: vector> = vector[secp256r1_key, secp256k1_key, ed25519_key]; + + let weights: vector = vector[1, 1, 1]; + let threshold: u16 = 2; + let multisig_address: address = @0xCAFE; + + ms::order_pks(multisig_address, pks0, weights, threshold); + ms::order_pks(multisig_address, pks1, weights, threshold); + ms::order_pks(multisig_address, pks2, weights, threshold); + ms::order_pks(multisig_address, pks3, weights, threshold); + ms::order_pks(multisig_address, pks4, weights, threshold); + ms::order_pks(multisig_address, pks5, weights, threshold); + } } diff --git a/move/tests/utils_test.move b/move/tests/utils_test.move new file mode 100644 index 0000000..cd41ad1 --- /dev/null +++ b/move/tests/utils_test.move @@ -0,0 +1,26 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module multisig::utils_unit_tests { + use std::vector; + use multisig::utils; + + #[test] + fun test_permutations() { + let ed25519_key: vector = vector[0, 13, 125, 171, 53, 140, 141, 173, 170, 78, 250, 0, 73, 167, 91, 7, 67, 101, 85, 177, 10, 54, 130, 25, 187, 104, 15, 112, 87, 19, 73, 215, 117]; + let secp256k1_key: vector = vector[1, 2, 14, 23, 205, 89, 57, 228, 107, 25, 102, 65, 150, 140, 215, 89, 145, 11, 162, 87, 126, 39, 250, 115, 253, 227, 135, 109, 185, 190, 197, 188, 235, 43]; + let secp256r1_key: vector = vector[2, 3, 71, 251, 175, 35, 240, 56, 171, 196, 195, 8, 162, 113, 17, 122, 42, 76, 255, 174, 221, 188, 95, 248, 28, 117, 23, 188, 108, 116, 167, 237, 180, 48]; + + let pks: vector> = vector[ed25519_key, secp256k1_key, secp256r1_key]; + let permutations: vector>> = utils::permutations(&mut pks); + + assert!(vector::length(&permutations) == 6, 0); + assert!(*vector::borrow(&permutations, 0) == vector[ed25519_key, secp256k1_key, secp256r1_key], 0); + assert!(*vector::borrow(&permutations, 1) == vector[secp256k1_key, ed25519_key, secp256r1_key], 0); + assert!(*vector::borrow(&permutations, 2) == vector[secp256r1_key, ed25519_key, secp256k1_key], 0); + assert!(*vector::borrow(&permutations, 3) == vector[ed25519_key, secp256r1_key, secp256k1_key], 0); + assert!(*vector::borrow(&permutations, 4) == vector[secp256k1_key, secp256r1_key, ed25519_key], 0); + assert!(*vector::borrow(&permutations, 5) == vector[secp256r1_key, secp256k1_key, ed25519_key], 0); + } +} \ No newline at end of file