Skip to content

Commit

Permalink
[Sui]: Add SUI Implementation (#2883)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milerius authored Jan 27, 2023
1 parent 1709e79 commit 7772467
Show file tree
Hide file tree
Showing 38 changed files with 858 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class CoinAddressDerivationTests {
EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address)
TON -> assertEquals("EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9", address)
APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address)
SUI -> assertEquals("0x061ce2b2100a71bb7aa0da98998887ad82597948", address)
HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address)
SECRET -> assertEquals("secret1f69sk5033zcdr2p2yf3xjehn7xvgdeq09d2llh", address)
NATIVEINJECTIVE -> assertEquals("inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", address)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.sui

import com.trustwallet.core.app.utils.toHex
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.jni.*

class TestSuiAddress {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testAddress() {
val any = AnyAddress("0x061ce2b2100a71bb7aa0da98998887ad82597948", CoinType.SUI)
assertEquals(any.coin(), CoinType.SUI)
assertEquals(any.description(), "0x061ce2b2100a71bb7aa0da98998887ad82597948")

Assert.assertFalse(
AnyAddress.isValid(
"0xMQqpqMQgCBuiPkoXfgZZsJvuzCeI1zc00z6vHJj4",
CoinType.SUI
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package com.trustwallet.core.app.blockchains.sui

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.Numeric
import com.trustwallet.core.app.utils.toHexByteArray
import com.trustwallet.core.app.utils.toHexBytes
import com.trustwallet.core.app.utils.toHexBytesInByteString
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.CoinType
import wallet.core.jni.proto.Sui

class TestSuiSigner {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun SuiTransactionSigning() {
// Successfully broadcasted https://explorer.sui.io/transaction/rxLgxcAqgMg8gphp6eCsSGQcdZnwFYx2SRdwEhnAUC4
val txBytes = """
AAUCLiNiMy/EzosKCk5EZr5QQZmMVLnvAAAAAAAAACDqj/OT+1+qyLZKV4YLw8kpK3/bTZKspTUmh1pBuUfHPLb0crwkV1LQcBARaxER8XhTNJmK7wAAAAAAAAAgaQEguOdXa+m16IM536nsveakQ4u/GYJAc1fpYGGKEvgBQUP35yxF+cEL5qm153kw18dVeuYB6AMAAAAAAAAttQCskZzd41GsNuNxHYMsbbl2aS4jYjMvxM6LCgpORGa+UEGZjFS57wAAAAAAAAAg6o/zk/tfqsi2SleGC8PJKSt/202SrKU1JodaQblHxzwBAAAAAAAAAOgDAAAAAAAA
""".trimIndent()
val key =
"3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266".toHexBytesInByteString()
val signDirect = Sui.SignDirect.newBuilder().setUnsignedTxMsg(txBytes).build()
val signingInput =
Sui.SigningInput.newBuilder().setSignDirectMessage(signDirect).setPrivateKey(key).build()
val result = AnySigner.sign(signingInput, CoinType.SUI, Sui.SigningOutput.parser())
val expectedSignature = "AIYRmHDpQesfAx3iWBCMwInf3MZ56ZQGnPWNtECFjcSq0ssAgjRW6GLnFCX24tfDNjSm9gjYgoLmn1No15iFJAtqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg=="
assertEquals(result.unsignedTx, txBytes);
assertEquals(result.signature, expectedSignature)
}
}
1 change: 1 addition & 0 deletions docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ This list is generated from [./registry.json](../registry.json)
| 607 | TON | TON | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ton/info/logo.png" width="32" /> | <https://ton.org> |
| 637 | Aptos | APT | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/aptos/info/logo.png" width="32" /> | <https://aptoslabs.com/> |
| 714 | BNB Beacon Chain | BNB | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/binance/info/logo.png" width="32" /> | <https://binance.org> |
| 784 | Sui | SUI | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/sui/info/logo.png" width="32" /> | <https://sui.io/> |
| 818 | VeChain | VET | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/vechain/info/logo.png" width="32" /> | <https://vechain.org> |
| 820 | Callisto | CLO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/callisto/info/logo.png" width="32" /> | <https://callisto.network> |
| 888 | NEO | NEO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/neo/info/logo.png" width="32" /> | <https://neo.org> |
Expand Down
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWBlockchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum TWBlockchain {
TWBlockchainAptos = 43, // Aptos
TWBlockchainHedera = 44, // Hedera
TWBlockchainTheOpenNetwork = 45,
TWBlockchainSui = 46,
};

TW_EXTERN_C_END
1 change: 1 addition & 0 deletions include/TrustWalletCore/TWCoinType.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ enum TWCoinType {
TWCoinTypeNativeInjective = 10000060,
TWCoinTypeAgoric = 564,
TWCoinTypeTON = 607,
TWCoinTypeSui = 784,
};

/// Returns the blockchain for a coin type.
Expand Down
28 changes: 28 additions & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,34 @@
"documentation": "https://fullnode.mainnet.aptoslabs.com/v1/spec#/"
}
},
{
"id": "sui",
"name": "Sui",
"coinId": 784,
"symbol": "SUI",
"decimals": 9,
"blockchain": "Sui",
"derivation": [
{
"path": "m/44'/784'/0'/0'/0'"
}
],
"curve": "ed25519",
"publicKeyType": "ed25519",
"explorer": {
"url": "https://explorer.sui.io/",
"txPath": "/transaction/",
"accountPath": "/address/",
"sampleTx": "SWRW1RoMHxnD9NeobgBoC4cXGwp2Hc511CnfWUoTBmo",
"sampleAccount": "0x62107e1afefccc7b2267ab74e332c146f5c2ca15"
},
"info": {
"url": "https://sui.io/",
"source": "https://github.com/MystenLabs/sui",
"rpc": "https://fullnode.testnet.sui.io",
"documentation": "https://docs.sui.io/"
}
},
{
"id": "cosmos",
"name": "Cosmos",
Expand Down
9 changes: 8 additions & 1 deletion rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ starknet-ff = "0.1.0"
starknet-signers = "0.1.0"
bcs = "0.1.4"
hex = "0.4.3"
base64 = "0.21.0"

[dev-dependencies]
3 changes: 3 additions & 0 deletions rust/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Whether to add a `#pragma once` guard
# default: doesn't emit a `#pragma once`
pragma_once = true
115 changes: 115 additions & 0 deletions rust/src/encoding/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

use base64::{Engine as _, engine::{general_purpose}};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[no_mangle]
pub extern "C" fn encode_base64(data: *const u8, len: usize, is_url: bool) -> *mut c_char {
let data = unsafe { std::slice::from_raw_parts(data, len) };
let encoded = if is_url {
general_purpose::URL_SAFE.encode(data)
} else {
general_purpose::STANDARD.encode(data)
};
CString::new(encoded).unwrap().into_raw()
}

#[repr(C)]
pub struct CByteArray {
data: *mut u8,
size: usize,
}

#[no_mangle]
pub extern "C" fn decode_base64(data: *const c_char, is_url: bool) -> CByteArray {
if data.is_null() {
return CByteArray { data: std::ptr::null_mut(), size: 0 };
}
let c_str = unsafe { CStr::from_ptr(data) };
let str_slice = c_str.to_str().unwrap();
let decoded = if is_url {
general_purpose::URL_SAFE
.decode(str_slice)
} else {
general_purpose::STANDARD
.decode(str_slice)
};
let decoded = match decoded {
Ok(decoded) => decoded,
Err(_) => return CByteArray { data: std::ptr::null_mut(), size: 0 }
};
let size = decoded.len();
let mut decoded_vec = decoded.to_vec();
let ptr = decoded_vec.as_mut_ptr();
std::mem::forget(decoded_vec);
CByteArray { data: ptr, size }
}


#[cfg(test)]
mod tests {
use std::ffi::CString;
use crate::encoding::{decode_base64, encode_base64};

#[test]
fn test_encode_base64_ffi() {
let data = b"hello world";
let encoded = unsafe {
std::ffi::CStr::from_ptr(encode_base64(data.as_ptr(), data.len(), false))
};
let expected = "aGVsbG8gd29ybGQ=";
assert_eq!(encoded.to_str().unwrap(), expected);
}

#[test]
fn test_encode_base64_url_ffi() {
let data = b"+'?ab";
let encoded = unsafe {
std::ffi::CStr::from_ptr(encode_base64(data.as_ptr(), data.len(), true))
};
let expected = "Kyc_YWI=";
assert_eq!(encoded.to_str().unwrap(), expected);
}

#[test]
fn test_decode_base64_url() {
let encoded = "Kyc_YWI=";
let expected = b"+'?ab";

let encoded_c_str = CString::new(encoded).unwrap();
let encoded_ptr = encoded_c_str.as_ptr();

let decoded_ptr = decode_base64(encoded_ptr, true);
let decoded_slice = unsafe { std::slice::from_raw_parts(decoded_ptr.data, decoded_ptr.size) };

assert_eq!(decoded_slice, expected);
}

#[test]
fn test_decode_base64() {
let encoded = "aGVsbG8gd29ybGQh";
let expected = b"hello world!";

let encoded_c_str = CString::new(encoded).unwrap();
let encoded_ptr = encoded_c_str.as_ptr();

let decoded_ptr = decode_base64(encoded_ptr, false);
let decoded_slice = unsafe { std::slice::from_raw_parts(decoded_ptr.data, decoded_ptr.size) };

assert_eq!(decoded_slice, expected);
}

#[test]
fn test_decode_base64_invalid() {
let invalid_encoded = "_This_is_an_invalid_base64_";
let encoded_c_str = CString::new(invalid_encoded).unwrap();
let encoded_ptr = encoded_c_str.as_ptr();
let decoded_ptr = decode_base64(encoded_ptr, false);
assert_eq!(decoded_ptr.data.is_null(), true);
}
}
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
pub mod move_parser;
pub mod memory;
pub mod starknet;
pub mod encoding;
66 changes: 4 additions & 62 deletions src/Aptos/Address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,18 @@
#include "Address.h"
#include "HexCoding.h"

namespace {

std::string normalize(const std::string& string, std::size_t hexLen) {
std::string hexStr((TW::Aptos::Address::size * 2) - hexLen, '0');
hexStr.append(string);
return hexStr;
}

} // namespace

namespace TW::Aptos {

bool Address::isValid(const std::string& string) {
auto address = string;
if (address.starts_with("0x")) {
address = address.substr(2);
if (std::size_t hexLen = address.size(); hexLen < Address::size * 2) {
address = normalize(address, hexLen);
}
}
if (address.size() != 2 * Address::size) {
return false;
}
const auto data = parse_hex(address);
return isValid(data);
Address::Address(const std::string& string) : Address::AptosAddress(string) {
}

Address::Address(const std::string& string) {
if (!isValid(string)) {
throw std::invalid_argument("Invalid address string");
}
auto hexFunctor = [&string]() {
if (std::size_t hexLen = string.size() - 2; string.starts_with("0x") && hexLen < Address::size * 2) {
//! We have specific address like 0x1, padding it.
return parse_hex(normalize(string.substr(2), hexLen));
} else {
return parse_hex(string);
}
};

const auto data = hexFunctor();
std::copy(data.begin(), data.end(), bytes.begin());
Address::Address(const PublicKey& publicKey): Address::AptosAddress(publicKey) {
}

Address::Address(const Data& data) {
if (!isValid(data)) {
throw std::invalid_argument("Invalid address data");
}
std::copy(data.begin(), data.end(), bytes.begin());
}

Address::Address(const PublicKey& publicKey) {
if (publicKey.type != TWPublicKeyTypeED25519) {
throw std::invalid_argument("Invalid public key type");
}
Data Address::getDigest(const PublicKey& publicKey) {
auto key_data = publicKey.bytes;
append(key_data, 0x00);
const auto data = Hash::sha3_256(key_data);
std::copy(data.begin(), data.end(), bytes.begin());
}

std::string Address::string(bool withPrefix) const {
std::string output = withPrefix ? "0x" : "";
return output + hex(bytes);
}

std::string Address::shortString() const {
std::string s = hex(bytes);
s.erase(0, s.find_first_not_of('0'));
return s;
return key_data;
}

BCS::Serializer& operator<<(BCS::Serializer& stream, Address addr) noexcept {
Expand Down
Loading

0 comments on commit 7772467

Please sign in to comment.