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-neo #152

Merged
merged 17 commits into from
Oct 26, 2023
Merged
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
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"anychain-filecoin",
"anychain-polkadot",
"anychain-ripple",
"anychain-neo",
"anychain-kms",
"examples/*"
]
Expand Down Expand Up @@ -48,13 +49,13 @@ num-bigint = "0.4.3"
anyhow = "1.0.75"
forest_encoding = "0.2.2"
cid = "0.8.6"
base64 = "0.21.1"
base64 = "0.21.5"
bytes = "1.0"
protobuf = { version = "3.3.0" }
chrono = "0.4"
ethabi = "17.2.0"
regex = { version = "1.3" }
clap = "4.4.6"
clap = { version = "4.4.7", features = ["derive"] }
primitive-types = { version = "0.11.1", features = ["rlp"] }
rustc-hash = "1.1.0"
hmac = "0.12.1"
Expand All @@ -65,6 +66,7 @@ zeroize = { version = "1.5.5", default-features = false }
once_cell = { version = "1.18.0" }
subtle = { version = "2", default-features = false }
encoding = "0.2.33"
p256 = "0.13.2"

[profile.release]
strip = true # Automatically strip symbols from the binary
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ public blockchains including Bitcoin, Ethereum, Tron, Filecoin, etc.
- Filecoin
- Tron
- Ripple

- Arbitrum, Optimism, and Avalanche
- Neo

## License

This project is licensed under the [MIT license][license].
Expand Down
19 changes: 19 additions & 0 deletions anychain-neo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "anychain-neo"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anychain-core = { path = "../anychain-core", version = "0.1.3"}
p256 = { workspace = true }
base58 = { workspace = true }
base64 = { workspace = true }

[dev-dependencies]
rand = { workspace = true }
165 changes: 165 additions & 0 deletions anychain-neo/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use crate::{NeoFormat, NeoPublicKey};
use anychain_core::{
crypto::{checksum, hash160},
Address, AddressError, PublicKey,
};
use base58::{FromBase58, ToBase58};
use std::{fmt::Display, str::FromStr};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct NeoAddress(pub String);

impl Address for NeoAddress {
type Format = NeoFormat;
type SecretKey = p256::SecretKey;
type PublicKey = NeoPublicKey;

fn from_secret_key(
secret_key: &Self::SecretKey,
format: &Self::Format,
) -> Result<Self, AddressError> {
Self::PublicKey::from_secret_key(secret_key).to_address(format)
}

fn from_public_key(
public_key: &Self::PublicKey,
_format: &Self::Format,
) -> Result<Self, AddressError> {
let bytes = public_key.serialize_compressed();
let bytes = [
vec![
0x0c, /* PushData1 */
0x21, /* compressed key length */
],
bytes, /* compressed public key bytes */
vec![
0x41, /* Opcode.Syscall */
0x56, 0xe7, 0xb3, 0x27, /* System.Crypto.CheckSig */
],
]
.concat();

let hash = hash160(&bytes);
let payload = [vec![0x35u8 /* version byte */], hash].concat();

let checksum = checksum(&payload)[..4].to_vec();
let res = [payload, checksum].concat();

Ok(Self(res.to_base58()))
}
}

impl NeoAddress {
pub fn to_script_hash(&self) -> Vec<u8> {
let bytes = self.0.as_str().from_base58().unwrap();
// strip the version byte and the checksum
bytes[1..21].to_vec()
}
}

impl FromStr for NeoAddress {
type Err = AddressError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.from_base58()?;
let checksum_provided = bytes[21..].to_vec();
let checksum_gen = checksum(&bytes[..21])[..4].to_vec();

if checksum_gen != checksum_provided {
return Err(AddressError::Message(format!("Invalid address {}", s)));
}

Ok(Self(s.to_string()))
}
}

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

#[allow(unused_imports)]
#[cfg(test)]
mod tests {
// use anychain_core::{Address, PublicKey};
// use super::{NeoFormat, NeoAddress, NeoPublicKey};
use super::*;
use rand::rngs::OsRng;

#[test]
fn test_address_from_secret_key() {
// Create a secret key for testing
let mut rng = OsRng;
let secret_key = p256::SecretKey::random(&mut rng);

// Define the desired format
let format = NeoFormat::Standard;
let result = NeoAddress::from_secret_key(&secret_key, &format);

assert!(result.is_ok());

let address = result.unwrap();
assert_eq!(address.to_script_hash().len(), 20);
}

#[test]
fn test_address_from_public_key() {
// Create a public key for testing

let mut rng = OsRng;
let secret_key = p256::SecretKey::random(&mut rng);
let public_key = NeoPublicKey::from_secret_key(&secret_key);

let format = NeoFormat::Standard;
let result = NeoAddress::from_public_key(&public_key, &format);

assert!(result.is_ok());

let address = result.unwrap();
assert_eq!(address.to_script_hash().len(), 20);
}

#[test]
fn test_address_from_str() {
let address_str = "NVEqR4e73afGKpVBzBXLEnY5F5uZSmSKZZ";
let address = NeoAddress::from_str(address_str);

assert!(address.is_ok());

let parsed_address = address.unwrap();
assert_eq!(parsed_address.to_string(), address_str);
}

#[test]
fn tesa_addresses_from_sk() {
let mut sk = [
1u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1,
];

let format = NeoFormat::Standard;

let addresses = [
"NKvAasuNZqDc8dQDXd6Wm2XccTh6Dt3nr4",
"NUz6PKTAM7NbPJzkKJFNay3VckQtcDkgWo",
"NgZs1i1hZSWh9Lpj9mVRZyNLT3gvydn5Y7",
"NVKhps7L2souj2xUSw9dWuyGn5Sdgv8g6J",
"NcBrH4zAwCf3XhywpxgWPcTv3y67y8C1C2",
"NdvNCaDAt4gn4cWz1Ua8AUp6RQ3u5uABpK",
"NMkKsN8SrmSTDB4j6EsbELxnJ5UmcUZLVS",
"NYQLdcRTgLcnsZ3fWjnobBxZkzc4rRnbtb",
"NbnMx2Bt6b6AWgZzuK7c3iuNFySoGhyg5S",
"NWUTErT9hs9QiphzhkxbFzhpfKQXX5xvqf",
];

for i in 0..10 {
sk[3] = i;
let sk = p256::SecretKey::from_slice(&sk).unwrap();
let addr = NeoPublicKey::from_secret_key(&sk)
.to_address(&format)
.unwrap();
assert_eq!(format!("{}", addr), addresses[i as usize]);
}
}
}
17 changes: 17 additions & 0 deletions anychain-neo/src/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use anychain_core::Format;

use core::fmt;

/// Represents the format of a Ethereum address
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NeoFormat {
Standard,
}

impl Format for NeoFormat {}

impl fmt::Display for NeoFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Standard")
}
}
11 changes: 11 additions & 0 deletions anychain-neo/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod address;
pub use address::*;

mod format;
pub use format::*;

mod public_key;
pub use public_key::*;

mod transaction;
pub use transaction::*;
69 changes: 69 additions & 0 deletions anychain-neo/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::{NeoAddress, NeoFormat};
use anychain_core::{hex, Address, AddressError, PublicKey, PublicKeyError};
use std::{fmt::Display, str::FromStr};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeoPublicKey(pub p256::PublicKey);

impl PublicKey for NeoPublicKey {
type SecretKey = p256::SecretKey;
type Address = NeoAddress;
type Format = NeoFormat;

fn from_secret_key(secret_key: &Self::SecretKey) -> Self {
let scalar = secret_key.to_nonzero_scalar();
Self(p256::PublicKey::from_secret_scalar(&scalar))
}

fn to_address(&self, format: &Self::Format) -> Result<Self::Address, AddressError> {
Self::Address::from_public_key(self, format)
}
}

impl NeoPublicKey {
pub fn serialize_compressed(&self) -> Vec<u8> {
p256::CompressedPoint::from(self.0).as_slice().to_vec()
}
}

impl FromStr for NeoPublicKey {
type Err = PublicKeyError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bin = hex::decode(s)?;
let public_key = p256::PublicKey::from_sec1_bytes(&bin).unwrap();
Ok(NeoPublicKey(public_key))
}
}

impl Display for NeoPublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes = self.0.to_sec1_bytes().to_vec();
let s = hex::encode(bytes);
write!(f, "pk = {}, len = {}", s, s.len())
}
}

#[cfg(test)]
mod test {

use super::*;
use crate::NeoPublicKey;
use anychain_core::PublicKey;

#[test]
fn test_public_key_from_str() {
let sk = [
1u8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1,
];

let sk = p256::SecretKey::from_slice(&sk).unwrap();
let _public_key = NeoPublicKey::from_secret_key(&sk);

let public_key = "046ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca163c4f753a55bf01dc53f6c0b0c7eee78b40c6ff7d25a96e2282b989cef71c144a";

let pb = NeoPublicKey::from_str(public_key);
assert!(pb.is_ok());
}
}
Loading
Loading