diff --git a/contracts/sui/multisig/Move.lock b/contracts/sui/multisig/Move.lock new file mode 100644 index 00000000..940ee6ce --- /dev/null +++ b/contracts/sui/multisig/Move.lock @@ -0,0 +1,26 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 2 +manifest_digest = "0BAC026A0C518E4F4E63B8A7BB1FA36291E16B8919911B03E105CDD71EA40B5E" +deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" +dependencies = [ + { name = "Sui" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] + +[move.toolchain-version] +compiler-version = "1.30.3" +edition = "2024.beta" +flavor = "sui" diff --git a/contracts/sui/multisig/Move.toml b/contracts/sui/multisig/Move.toml new file mode 100644 index 00000000..0299139b --- /dev/null +++ b/contracts/sui/multisig/Move.toml @@ -0,0 +1,37 @@ +[package] +name = "multisig" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move +# license = "" # e.g., "MIT", "GPL", "Apache 2.0" +# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } + +# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. +# Revision can be a branch, a tag, and a commit hash. +# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" } + +# For local dependencies use `local = path`. Path is relative to the package root +# Local = { local = "../path/to" } + +# To resolve a version conflict and force a specific version for dependency +# override use `override = true` +# Override = { local = "../conflicting/version", override = true } + +[addresses] +multisig = "0x0" + +# Named addresses will be accessible in Move as `@name`. They're also exported: +# for example, `std = "0x1"` is exported by the Standard Library. +# alice = "0xA11CE" + +[dev-dependencies] +# The dev-dependencies section allows overriding dependencies for `--test` and +# `--dev` modes. You can introduce test-only dependencies here. +# Local = { local = "../path/to/dev-build" } + +[dev-addresses] +# The dev-addresses section allows overwriting named addresses for the `--test` +# and `--dev` modes. +# alice = "0xB0B" + diff --git a/contracts/sui/multisig/sources/base64.move b/contracts/sui/multisig/sources/base64.move new file mode 100644 index 00000000..d2488003 --- /dev/null +++ b/contracts/sui/multisig/sources/base64.move @@ -0,0 +1,134 @@ +module multisig::base64 { + use std::vector::{Self}; + use std::string::{Self,String}; + use sui::vec_map::{Self, VecMap}; + + const BASE64_CHARS: vector = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const PADDING_CHAR: vector = b"="; + + +public fun encode(input:&vector):String { + + let mut output:vector = vector::empty(); + let mut i=0; + while (i > 18) & 0x3F) as u64)); + output.push_back(*BASE64_CHARS.borrow(((triple >> 12) & 0x3F) as u64)); + + if (i + 1 < input.length()) { + output.push_back(*BASE64_CHARS.borrow(((triple >> 6) & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + if (i + 2 < input.length()) { + output.push_back(*BASE64_CHARS.borrow((triple & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + i =i+ 3; + }; + string::utf8(output) +} + +public fun decode(input:&String):vector{ + let char_index=get_char_map(); + let mut output = vector::empty(); + let input_bytes = input.as_bytes(); + let mut i = 0; + while( i < input_bytes.length()){ + let b1 = *char_index.get(input_bytes.borrow(i)); + let b2 = *char_index.get(input_bytes.borrow(i + 1)); + let b3 = if (i + 2 < input_bytes.length()) { + let key=input_bytes.borrow(i+2); + let val:u32 = if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + 64 + }; + let b4 = if (i + 3 < input_bytes.length()) { + let key=input_bytes.borrow(i+3); + let val:u32= if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + + 64 + }; + + let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; + + output.push_back(((triple >> 16) & 0xFF) as u8); + + if (b3 != 64) { + output.push_back(((triple >> 8) & 0xFF) as u8); + }; + if (b4 != 64) { + output.push_back((triple & 0xFF) as u8); + }; + + i = i+4; + + }; + output + +} + + + + fun get_char_map():VecMap{ + let mut char_map = vec_map::empty(); + let mut i:u64=0; + while( i < BASE64_CHARS.length()){ + let c=*BASE64_CHARS.borrow(i); + char_map.insert(c,(i as u32)); + i=i+1; + }; + char_map + } +} + + +#[test_only] +module multisig::base64_tests { + use sui::hash::{Self}; + use multisig::base64::{Self}; + #[test] + fun test_base64(){ + let input = b"Hello, World!"; + let encoded = base64::encode(&input); + assert!(encoded.as_bytes()==b"SGVsbG8sIFdvcmxkIQ=="); + + let decoded = base64::decode(&encoded); + std::debug::print(&encoded); + assert!(decoded==input); + + } + + +} + + + diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move new file mode 100644 index 00000000..a772605f --- /dev/null +++ b/contracts/sui/multisig/sources/multisig.move @@ -0,0 +1,207 @@ + +module multisig::multisig { + use std::vector::{Self}; + use sui::linked_table::{Self, LinkedTable}; + use sui::types as sui_types; + use std::string::{Self, String}; + use sui::event; + use sui::hash::{Self}; + use sui::vec_map::{Self, VecMap}; + use sui::table::{Table,Self}; + use sui::bcs::{Self}; + use sui::address::{Self}; + use multisig::base64::{Self}; + + + public struct Signer has store{ + pub_key:vector, + sui_address:address, + weight:u8 + } + + public struct MultisigWallet has store { + multisig_address:address, + signers:vector, + threshold:u16, + + } + + public struct Proposal has store{ + id:u64, + title:String, + multisig_address:address, + tx_data:vector, + } + + public struct Vote has store{ + id:u64, + signature:vector, + pub_key:address, + } + + public struct VoteKey has store,drop,copy{ + proposal_id:u64, + sui_address:address, + } + + + public struct Storage has key,store{ + id:UID, + wallets:VecMap, + wallet_proposals:Table>, + proposals:Table, + votes:Table, + proposal_count:u64, + + + + } + public struct AdminCap has key,store { + id: UID + } + + + fun init(ctx: &mut TxContext) { + let admin = AdminCap { + id: object::new(ctx), + }; + let storage = Storage { + id:object::new(ctx), + wallets:vec_map::empty(), + wallet_proposals: table::new(ctx), + proposals:table::new(ctx), + votes: table::new(ctx), + proposal_count:0u64, + }; + transfer::transfer(admin, tx_context::sender(ctx)); + transfer::share_object(storage); + + } + + + + public fun create_multisig_address(pubkeys:vector>,weights:vector,threshold:u16):address{ + let mut bytes= vector::empty(); + bytes.push_back(0x03); + let threshold_bytes=bcs::to_bytes(&threshold); + bytes.append(threshold_bytes); + let mut i=0; + while(i < pubkeys.length()){ + bytes.append(*pubkeys.borrow(i)); + bytes.push_back(*weights.borrow(i)); + i=i+1; + }; + + let address_bytes=hash::blake2b256(&bytes); + address::from_bytes(address_bytes) + + } + + entry fun register_wallet(storage:&mut Storage,pub_keys:vector,weights:vector,threshold:u16){ + let mut pub_keys_bytes:vector> = vector::empty(); + let mut signers:vector = vector::empty(); + let mut i=0; + while(i < pub_keys.length()){ + let bytes=base64::decode(pub_keys.borrow(i)); + pub_keys_bytes.push_back(bytes); + let sui_address= address::from_bytes(hash::blake2b256(&bytes)); + let signer_1= Signer { + pub_key:bytes, + sui_address:sui_address, + weight:*weights.borrow(i), + }; + signers.push_back(signer_1); + i=i+1; + }; + let multisig_addr= create_multisig_address(pub_keys_bytes,weights,threshold); + let multisig_wallet= MultisigWallet{ + multisig_address:multisig_addr, + signers:signers, + threshold:threshold, + }; + storage.wallets.insert(multisig_addr,multisig_wallet); + } + + entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes:vector,multisig_address:address,ctx:&mut TxContext){ + let wallet=storage.wallets.get(&multisig_address); + assert!(only_member(wallet,ctx.sender())==true); + let proposal_id=get_proposal_id(storage); + let proposal= Proposal{ + id:proposal_id, + title:title, + multisig_address:multisig_address, + tx_data:tx_bytes, + + }; + storage.proposals.add(proposal_id,proposal); + storage.wallet_proposals.borrow_mut(multisig_address).push_back(proposal_id); + + } + + entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,signature:vector,ctx:&mut TxContext){ + + } + + fun only_member(wallet:&MultisigWallet,caller:address):bool{ + let mut i=0; + let mut is_member=false; + while(i < wallet.signers.length() && is_member==false){ + if (wallet.signers.borrow(i).sui_address==caller){ + is_member=true; + }; + }; + is_member + + } + + fun get_proposal_id(storage:&mut Storage):u64{ + let count=storage.proposal_count+1; + storage.proposal_count=count; + count + } + +} +#[test_only] +module multisig::tests { + use sui::hash::{Self}; + use multisig::multisig::{create_multisig_address}; + #[test] + fun test_public__key_address(){ + let pubkey = x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + let sui_address=hash::blake2b256(&pubkey); + assert!(x"29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589"==sui_address); + + + } + + #[test] + fun test_multisig_address(){ + let mut public_keys:vector> =vector::empty(); + public_keys.push_back(x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + public_keys.push_back(x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"); + public_keys.push_back(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + public_keys.push_back(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"); + public_keys.push_back(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + let mut weights:vector = vector::empty(); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + let threshold:u16=3u16; + + let sui_addr= create_multisig_address(public_keys,weights,threshold); + std::debug::print(&sui_addr); + let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + assert!(expected==sui_addr); + + + + } + + + + +} + + diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move new file mode 100644 index 00000000..94a5355c --- /dev/null +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -0,0 +1,19 @@ + +#[test_only] +module multisig::multisig_tests { + // uncomment this line to import the module + use multisig::multisig; + + const ENotImplemented: u64 = 0; + + #[test] + fun test_create_multisig() { + // let pub_keys=[]; + } + + #[test, expected_failure(abort_code = ::multisig::multisig_tests::ENotImplemented)] + fun test_multisig_fail() { + abort ENotImplemented + } +} +