Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: anychain sui #236

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,188 changes: 4,909 additions & 279 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"anychain-ripple",
"anychain-neo",
"anychain-kms",
"anychain-sui",
"examples/*",
]

Expand Down Expand Up @@ -66,6 +67,14 @@ once_cell = { version = "1.18.0" }
subtle = { version = "2", default-features = false }
p256 = "0.13.2"
bs58 = { version = "0.4", default-features = false, features = ["check", "alloc"] }
schemars = { version = "0.8.10", features = ["either"] }
serde_with = { version = "2.1.0", features = ["hex"] }
derive_more = "0.99.17"
bcs = "0.1.6"
fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", features = ["copy_key"], rev = "c101a5176799db3eb9c801b844e7add92153d291" }
sui_sdk = { git = "https://github.com/mystenlabs/sui", package = "sui-sdk", rev = "f1a2d61bebdf83f01b58830e4768631bd28ce48a"}
sui_types = { git = "https://github.com/mystenlabs/sui", package = "sui-types", rev = "f1a2d61bebdf83f01b58830e4768631bd28ce48a"}
shared_crypto = { git = "https://github.com/mystenlabs/sui", package = "shared-crypto", rev = "f1a2d61bebdf83f01b58830e4768631bd28ce48a"}

[profile.release]
strip = true # Automatically strip symbols from the binary
Expand Down
24 changes: 24 additions & 0 deletions anychain-sui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "anychain-sui"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
anychain-core = { path = "../anychain-core", version = "0.1.4" }
schemars.workspace = true
serde = { workspace = true }
serde_with.workspace = true
derive_more.workspace = true
fastcrypto.workspace = true
hex = { workspace = true }
sui_sdk = { workspace = true }
sui_types = { workspace = true }
shared_crypto = { workspace = true }
bcs = { workspace = true }

[dev-dependencies]
rand = { workspace = true }
213 changes: 213 additions & 0 deletions anychain-sui/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
use crate::format::SuiFormat;
use crate::{
public_key::{SuiPrivateKey, SuiPublicKey},
utils::*,
};
use anychain_core::{Address, AddressError, PublicKey};

use fastcrypto::encoding::Hex;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::{
fmt::{Debug, Display},
str::FromStr,
};
use sui_types::base_types::SuiAddress as RawSuiAddress;

pub const SUI_ADDRESS_LENGTH: usize = 32;

#[serde_as]
#[derive(
Eq, Default, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema,
)]
#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
pub struct SuiAddress(
#[schemars(with = "Hex")]
#[serde_as(as = "Readable<Hex, _>")]
[u8; SUI_ADDRESS_LENGTH],
);

impl SuiAddress {
pub const ZERO: Self = Self([0u8; SUI_ADDRESS_LENGTH]);

pub fn new(data: [u8; SUI_ADDRESS_LENGTH]) -> Self {
SuiAddress(data)
}

/// Parse a SuiAddress from a byte array or buffer.
pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, AddressError> {
<[u8; SUI_ADDRESS_LENGTH]>::try_from(bytes.as_ref())
.map_err(|err| AddressError::InvalidAddress(format!("Parse bytes failed: {}", err)))
.map(SuiAddress)
}

pub fn to_raw(&self) -> RawSuiAddress {
RawSuiAddress::from_bytes(self.0).expect("Failed to convert sui address to raw type")
}
}

impl Address for SuiAddress {
type SecretKey = SuiPrivateKey;
type Format = SuiFormat;
type PublicKey = SuiPublicKey;

fn from_secret_key(
secret_key: &Self::SecretKey,
format: &Self::Format,
) -> Result<Self, AddressError> {
Self::from_public_key(&SuiPublicKey::from_secret_key(secret_key), format)
}

fn from_public_key(
public_key: &Self::PublicKey,
format: &Self::Format,
) -> Result<Self, AddressError> {
public_key.to_address(format)
}
}

impl TryFrom<&[u8]> for SuiAddress {
type Error = AddressError;

/// Tries to convert the provided byte array into a SuiAddress.
fn try_from(bytes: &[u8]) -> Result<Self, AddressError> {
Self::from_bytes(bytes)
}
}

impl TryFrom<Vec<u8>> for SuiAddress {
type Error = AddressError;

/// Tries to convert the provided byte buffer into a SuiAddress.
fn try_from(bytes: Vec<u8>) -> Result<Self, AddressError> {
Self::from_bytes(bytes)
}
}

impl AsRef<[u8]> for SuiAddress {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}

impl FromStr for SuiAddress {
type Err = AddressError;
fn from_str(s: &str) -> Result<Self, AddressError>
where
Self: Sized,
{
let s = s.strip_prefix("0x").unwrap_or(s);
hex::decode(s)
.map_err(|err| {
AddressError::InvalidAddress(format!(
"Sui address should be hex format but decode failed: {}",
err
))
})
.and_then(|bytes| SuiAddress::try_from(&bytes[..]))
}
}

impl Display for SuiAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{}", hex::encode(self.as_ref()))
}
}

impl Debug for SuiAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{}", hex::encode(self.as_ref()))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::public_key::SignatureScheme;
use fastcrypto::{
ed25519::Ed25519KeyPair,
secp256k1::Secp256k1KeyPair,
secp256r1::Secp256r1KeyPair,
traits::{KeyPair, ToFromBytes},
};

const SAMPLE_SEED: [u8; 32] = [
51, 95, 147, 235, 93, 221, 105, 227, 208, 198, 105, 132, 164, 28, 174, 83, 68, 231, 82,
133, 50, 67, 181, 184, 126, 93, 85, 244, 135, 108, 205, 101,
];

#[test]
fn test_address_display() {
let sample_address = "af306e86c74e937552df132b41a6cb3af58559f5342c6e82a98f7d1f7a4a9f30";
let address = SuiAddress::from_str(sample_address).unwrap();

let sample_address_vec: [u8; 32] = [
175, 48, 110, 134, 199, 78, 147, 117, 82, 223, 19, 43, 65, 166, 203, 58, 245, 133, 89,
245, 52, 44, 110, 130, 169, 143, 125, 31, 122, 74, 159, 48,
];
assert_eq!(address.0, sample_address_vec);
assert_eq!(format!("{:?}", address), format!("0x{sample_address}"));
}

#[inline]
fn check_different_curve_address(
expected_address: &str,
key_type: SignatureScheme,
seed: &[u8],
) {
let (sk, pk) = match key_type {
SignatureScheme::ED25519 => {
let keypair = Ed25519KeyPair::from_bytes(seed).unwrap();
let sk = SuiPrivateKey::Ed25519(keypair.copy().private());
let pk = SuiPublicKey::Ed25519(keypair.public().into());
(sk, pk)
}
SignatureScheme::Secp256k1 => {
let keypair = Secp256k1KeyPair::from_bytes(seed).unwrap();
let sk = SuiPrivateKey::Secp256k1(keypair.copy().private());
let pk = SuiPublicKey::Secp256k1(keypair.public().into());
(sk, pk)
}
SignatureScheme::Secp256r1 => {
let keypair = Secp256r1KeyPair::from_bytes(seed).unwrap();
let sk = SuiPrivateKey::Secp256r1(keypair.copy().private());
let pk = SuiPublicKey::Secp256r1(keypair.public().into());
(sk, pk)
}
_ => {
panic!("The public key type is not supported!");
}
};

let address_from_secret_key = SuiAddress::from_secret_key(&sk, &SuiFormat::Hex).unwrap();
assert_eq!(
address_from_secret_key,
SuiAddress::from_str(expected_address).unwrap()
);

let address_from_public_key = SuiAddress::from_public_key(&pk, &SuiFormat::Hex).unwrap();
assert_eq!(
address_from_public_key,
SuiAddress::from_str(expected_address).unwrap()
);
}

#[test]
fn test_address_ed25519() {
let expected_address = "0xb6e7529acddc998333f553c385d400c8be99746e2cd6cd9818e9a4475862df65";
check_different_curve_address(expected_address, SignatureScheme::ED25519, &SAMPLE_SEED);
}

#[test]
fn test_address_secp256k1() {
let expected_address = "0x7607cd694df3a99be051c2400123fee106135086a192e1df7a2f344ea15bcd83";
check_different_curve_address(expected_address, SignatureScheme::Secp256k1, &SAMPLE_SEED);
}

#[test]
fn test_address_secp256r1() {
let expected_address = "0x73b4e70d671ba8171a1792d7d6116df4bd9870c6550ca4c6422f5e00396f82aa";
check_different_curve_address(expected_address, SignatureScheme::Secp256r1, &SAMPLE_SEED);
}
}
63 changes: 63 additions & 0 deletions anychain-sui/src/amount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use anychain_core::Amount;
use std::fmt;

/// 1 SUI = 10^9 MIST
pub enum Denomination {
MIST,
SUI,
}

impl Denomination {
fn precision(self) -> u32 {
match self {
Denomination::MIST => 0,
Denomination::SUI => 9,
}
}
}

impl fmt::Display for Denomination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Denomination::MIST => "MIST",
Denomination::SUI => "SUI",
}
)
}
}

/// Sui balance representation
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct SuiAmount(pub u64);

impl Amount for SuiAmount {}

impl fmt::Display for SuiAmount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let denomination = Denomination::SUI;
let precision = 10_u64.pow(denomination.precision());
let value = self.0 as f64 / precision as f64;
write!(f, "{} SUI", value)
}
}

impl AsRef<u64> for SuiAmount {
fn as_ref(&self) -> &u64 {
&self.0
}
}

#[test]
fn amount_display() {
let sui = SuiAmount(12000);
println!("amount = {}", sui);

let sui = SuiAmount(9_000_000_004);
println!("amount = {}", sui);

let sui = SuiAmount(1);
println!("amount = {}", sui);
}
16 changes: 16 additions & 0 deletions anychain-sui/src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use anychain_core::Format;
use std::fmt::Display;

#[derive(Hash, Clone, PartialEq, PartialOrd, Eq, Ord, Debug)]
pub enum SuiFormat {
Hex,
Base64,
}

impl Format for SuiFormat {}

impl Display for SuiFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Hex")
}
}
15 changes: 15 additions & 0 deletions anychain-sui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub mod address;
pub use address::*;

pub mod amount;

pub mod public_key;
pub use public_key::*;

pub mod format;
mod utils;

pub mod transaction;
pub use transaction::*;

pub mod network;
28 changes: 28 additions & 0 deletions anychain-sui/src/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use anychain_core::{
no_std::{
fmt::{Debug, Display},
hash::Hash,
FromStr,
},
Network, NetworkError,
};

#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct SuiNetwork;

impl Network for SuiNetwork {
const NAME: &'static str = "sui";
}

impl FromStr for SuiNetwork {
type Err = NetworkError;
fn from_str(_s: &str) -> Result<Self, Self::Err> {
todo!()
}
}

impl Display for SuiNetwork {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Self::NAME)
}
}
Loading
Loading