Skip to content

feat: arweave key generation and address derivation #45

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions chain-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ prost = "0.7"
hex = "0.4.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rsa = { version = "0.5.0", features = ["serde"] }
biscuit = "0.6.0-beta1"
base64 = "0.13.0"
rand = { version = "0.8.4", features = ["getrandom"] }
crypto = { path = "../crypto" }

[build-dependencies]
Expand Down
1 change: 1 addition & 0 deletions chain-common/proto/base.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum Coin {
Ethereum = 0;
Polkadot = 1;
Solana = 2;
Arweave = 3;
}

enum StoredKeyType {
Expand Down
1 change: 1 addition & 0 deletions chain-common/src/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl std::str::FromStr for ProtoCoin {
"ethereum" => Ok(ProtoCoin::Ethereum),
"polkadot" => Ok(ProtoCoin::Polkadot),
"solana" => Ok(ProtoCoin::Solana),
"arweave" => Ok(ProtoCoin::Arweave),
_ => Err(Error::NotSupportedCoin),
}
}
Expand Down
2 changes: 2 additions & 0 deletions chain-common/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ pub enum ChainImportType {
PrivateKey = 0,
Mnemonic = 1,
KeyStoreJson = 2,
Jwk = 3,
}

pub enum ChainExportType {
PrivateKey = 0,
Mnemonic = 1,
KeyStoreJson = 2,
Jwk = 3,
}

pub trait Entry {
Expand Down
1 change: 1 addition & 0 deletions chain-common/src/generated/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub enum Coin {
Ethereum = 0,
Polkadot = 1,
Solana = 2,
Arweave = 3,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
Expand Down
25 changes: 25 additions & 0 deletions chain-common/src/private_key.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use super::public_key::PublicKey;
use crypto::curve::Curve;
use crypto::jwk;
use crypto::public_key::PublicKeyType;
use crypto::Error as CryptoError;
use rand::rngs::OsRng;
use rsa::BigUint;
use rsa::PublicKey as RsaPublicKey;
use rsa::RsaPrivateKey;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::string::ToString;

Expand Down Expand Up @@ -78,6 +84,18 @@ impl PrivateKey {
}
}

pub fn new_jwk_key_from_rsa(bit_size: usize) -> Result<PrivateKey, CryptoError> {
let exponent = BigUint::from_bytes_be(&[0x01, 0x00, 0x01]);
let rsa_priv_key = RsaPrivateKey::new_with_exp(&mut OsRng {}, bit_size, &exponent)
.map_err(|_| CryptoError::InvalidSeed)?;
let jwk_bytes = jwk::new_jwk(&rsa_priv_key).map_err(|_| CryptoError::InvalidPrivateKey)?;
Ok(PrivateKey {
data: jwk_bytes.to_vec(),
extends_data: vec![],
chain_code_bytes: vec![],
})
}

pub fn get_public_key(&self, public_key_type_str: &str) -> Result<PublicKey, CryptoError> {
let public_key_type = PublicKeyType::from_str(public_key_type_str)
.map_err(|_| CryptoError::NotSupportedPublicKeyType)?;
Expand Down Expand Up @@ -128,4 +146,11 @@ mod tests {
let pub_key_hex2 = hex::encode(&pub_key2.data);
assert_eq!(pub_key_hex2, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91");
}

// #[test]
// fn test_new_rsa_priv_key() {
// let priv_key = PrivateKey::new_jwk_key_from_rsa(4096).unwrap();
// let test = String::from_utf8(priv_key.data.to_vec()).unwrap();
// assert_eq!(test, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91");
// }
}
1 change: 1 addition & 0 deletions chain-common/src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl PublicKey {
PublicKeyType::Ed25519 => {
size == ED25519_SIZE || (size == ED25519_SIZE + 1 && data[0] == 0x01)
}
PublicKeyType::Arweave => true,
}
}

Expand Down
12 changes: 12 additions & 0 deletions chain/arweave/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "arweave"
version = "0.1.0"
authors = ["jk234ert <[email protected]>"]
edition = "2018"

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

[dependencies]
base64 = "0.13.0"
chain-common = { path = "../../chain-common" }
crypto = { path = "../../crypto" }
68 changes: 68 additions & 0 deletions chain/arweave/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use base64;
use chain_common::public_key::PublicKey;
use crypto::public_key::PublicKeyType;
use crypto::Error;

const ADDRESS_SIZE: usize = 32;

pub struct ArweaveAddress {
pub data: Vec<u8>,
}

impl ArweaveAddress {
pub fn is_valid(address: &str) -> bool {
let data = match base64::decode(&address) {
Ok(data) => data,
Err(_) => return false,
};
data.len() == ADDRESS_SIZE
}

pub fn new(public_key: &PublicKey) -> Result<Self, Error> {
if public_key.r#type != PublicKeyType::Arweave {
return Err(Error::NotSupportedPublicKeyType);
}
Ok(ArweaveAddress {
data: public_key.data.to_vec(),
})
}
}

impl ToString for ArweaveAddress {
fn to_string(&self) -> String {
base64::encode_config(&self.data, base64::URL_SAFE_NO_PAD)
}
}

#[cfg(test)]
mod tests {
use crate::address::ArweaveAddress;
use chain_common::public_key::PublicKey;
use crypto::public_key::PublicKeyType;

#[test]
fn test_validate_address() {
let test1 = "abc";
let test2 = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpd";
let test3 = "2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdSl";

let test4 = "wAaIfLoaYVqgVnsYenyhkU8AbBPlRJzNGFgUwfWf2u0";

assert_eq!(ArweaveAddress::is_valid(&test1), false);
assert_eq!(ArweaveAddress::is_valid(&test2), false);
assert_eq!(ArweaveAddress::is_valid(&test3), false);
assert_eq!(ArweaveAddress::is_valid(&test4), true);
}

#[test]
fn test_derive_from_pub_key() {
use crypto::jwk;
let jwk_json_str = r#"{"kty":"RSA","e":"AQAB","n":"kmM4O08BJB85RbxfQ2nkka9VNO6Czm2Tc_IGQNYCTSXRzOc6W9bHRrlZ_eDhWO0OdfaRalgLeuYCXx9DV-n1djeerKHdFo2ZAjRv5WjL_b4IxbQnPFnHOSNHVg49yp7CUWUgDQOKtylt3x0YENIW37RQPZJ-Fvyk7Z0jvibj2iZ0K3K8yNenJ4mWswyQdyPaJcbP6AMWvUWT62giWHa3lDgBZNhXqakkYdoaM157kRUfrZDRSWXbilr-4f40PQF1DV5YSj81Fl72N7j30r0vL1yoj0bZn74WRquQ5j3QsiAA-SzhAxpecWniljj1wvZlyIgJpCYCvCrKZCcCq_JW1nYP6to5YM3fAqcYRadbTNdQ3oH0Sjy8vyvLYNe48Ur_TFTTAwZxJV70BgZfkJ00BxiNTb8EhSchejabeExUkCNlOrQsCHDxOig-WXOrjX5fb4NeR3jedeYWbhN922ORLuEwVLeyjc7hBfQXU2-mYraFAVTc0QST201P7rRu-UGtZ4gRavFuOvAyYrMimFVW9dTwTrcYXFK2zKCEv2aRRQAHZanKjBv0Xq9m3BqvxKy-_3Cj1O6ft7FT21drPoDRDzfnkyOeUjlXzRJzn-iQ0nqgHAQr9WBWPzLEcaTFpw3KmwDYHW_6JOkUWDyMW9anuS8cyqt_2O29SK_rHHuucD8","d":"Bq6C13vknF6Ln1MrKI3Ilq-83IuSvQpe7NRAuT69u7i8sv4XwsHOJAV7qpGvp37NXT5R1G3ehEZ6qoSxJbcN4IVrQMKq5mMiCY6DBv5C6fHZGoNZE2gxXV7uydf8I1Vnnw4xYIj5oyC_5nSJlFAc3U-MAcbkfJuvrhGxLVGsrqHmjoqQPGG_hTxCjuAOlOBs-9cmWTujbm1-OyjaAQwfTbXYbUy7hC1TCE05SxLPmTUwaJxY8AXJigpbYqjpWsc15HjRlv38A44tEnIwHjHda_3JpmbSsffSslRej2vPCCgSPHHyLeO437Nc7DraogKStugisRfhoe89yY4QSBVXbtvWJeF1LxPtg8uPtfoKt3wdnGWKaLDqYNDeA3AckbKrPp50kHEMR7hnNHq3lAoMAXTz8BbI_Czo5n9-f9DQpvJC8kpM7gCGG8DptA2nTPuQG02MOx7AsEE99EN8ltD_dA0l0MgG7CDsaQC5IPMHcRs1wyvZMBGA8fvZdURiVv9YSnCddndXjBJuetf5KdES-1EmrSLzo5hobQbkc7dkHMS5dmLm5YtK-aLYXZi31nRIGkA1UfZhf2TtfRxP6uKlRT106EtDX1rT3RgsLqg06y_xoS4SFQ6u-8wHqgbIHBmKdsWVtBkC4SGUlYDPgrJe2V9CaPFAcoSDFK1D_IPvU2U","p":"zhyauK0ISMg9Wk7iZK2ifW2cj5KSr-_k_Em0nfUrtsaKp0iXOsCKxOH__zcAVj7oLxaEP2l8i2Pdi7CzhVRiqrjgVwA1JuLPgxtryuVqwRCYbO_Y_2Xutk404iKmDX6_LQ7BeIzUI8GD6rQCeLq1HBd3Yvok9bPvbbMZjFtUmBf_Kfb0cYP8tewMmV_USGpqwJXdB_4aHF6qBrZBtd1KLoO1E7MNkAPk7pbiA1-KO2Xa6oY6fy2pztNe1MO7tz_QywqlDdymfhnpk41arY3A6US-ZFXOinqXKdh9uEfxiyZwzaLMNWVKEaWxRxbqSUOLV3uZS05N6B2ZqvOp2h9Csw","q":"tdHmYcbJpZ0U27d_j0YvUeb6sdcuFLg2vmDgKwUamC5_vvV41LSm8LIkLuY2DAN5MKg6-HTWetKWQhgbCIbubLtX5164MFrES1YVZI-aggrYohhH8MRn_hwMZZQndv9H07WUVgQ1GZ2ZDvhO7XxPDIXyBNQ46x6V1AikHtyTmqARjgrkgs-1XN55S9rhcffixOlJ-egIDPVei_Z6YNdSpLlhtiqHOp_lX37mrPSYGxjgIZVxevpPgBhVFlnAMqC2iRd87XupmWgiluSos8I7i1VESBzwlFZGk5hRb8och4zwmDBDwx65XWngg6LneSXTWcKjKKGM2NnX7wHrZBuyRQ","dp":"Gfo49fW5CZNTSEKQ_id0R2K9TMsoecw-jB2uCgqQi-TSLOtVRC5oTxA896my_SvIj8bCvEtLSzY3AhgvSCqulN3gSJbaHCCSDvAx0czAe7zfuTsxml76izeoKqg7TZAgAEnP0KXPRwJo4ff2J8lAcl3yyiLE7cLT9nuQSMRqERFVM7DQdk4wV618mQge9VGUStmYlh1MpS65N0dZWNafNuWauPTkTLZw8DFMIyizf3EC-nQYg1b6A_tYBHD3A82jPzQEQY8B3PrfGZ3DRASNv9jONk8qTQHOc5O5pLRMmUErDn_qRQCTKU483bzhooJE2a3WUEt6Pjsc1xMG4Vr3SQ","dq":"cCVai36Yi-06m1cwd8fbkhH9GUpXIvKI2Z5ZRk-smqc7piY0dEZFHftS9BaMyZYu3wM09GDklfdkNLo3mmfXkftv-cbjpvelUa50HYWx0HouKrT9UpVia0sTnmfme7BztjKunuuTcQxTBvfDfxoIi_nmUHIx9Vv1IEaALITzChGnIky3q7O_8ttKR65nFevG1JvsRBeJN6z0tzG9RBQr5mxtx3Wt2Uwcp21XjOCFHVmXjT9nMmpINQNNIC8VrGSSkjaJmNWIw5WGmDnLkKzCG2vpZO1suqIIgCsYN_Ka7ETTdZt3gFdoECUpFSiay4-4MAospvgWLv8XAFXXwfSPXQ","qi":"n-R81MpbwfWfqRSVgD8nDk7D8zlJ-tpMaojfTwNNqDt34Cr-BpMjxaQyEfMnzOd2dY4OV0rKhd29DIuwFEb2UERHdVWF3gM8f2byYGj4357CRkiwq6I050bUxd1ODgAXjVGNpOK_fmaNHDWfe5v3wVIcCmwH0mJxEu9kuz7fr9TJNxGJBGUphpGS6NQZDCbDXg9-FPafMeNV-Jdo0NQaKMwm8uZyW7YGSNpUXYnksrWt4Fa-B9H2KoC4PPSWESPxNooXdxK7Y0J1KbzNyrUmOl4dT6p_oFKcU-1unuDCZ11e6EmMKyUGjpDzTIAZ2XxmyWUJ06yzEw7oLo8noiCE_Q"}"#;
let expect_address = "fOVzBRTBnyt4VrUUYadBH8yras_-jhgpmNgg-5b3vEw";
let jwk = jwk::jwk_from_bytes(&jwk_json_str.as_bytes()).unwrap();

let pub_key = PublicKey::new(PublicKeyType::Arweave, &jwk).unwrap();
let address = ArweaveAddress::new(&pub_key).unwrap();
assert_eq!(address.to_string(), expect_address);
}
}
54 changes: 54 additions & 0 deletions chain/arweave/src/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use super::address::ArweaveAddress;
use chain_common::coin::Coin;
use chain_common::entry::{ChainExportType, ChainImportType, Entry};
use chain_common::private_key::PrivateKey;
use chain_common::public_key::PublicKey;
use crypto::Error;

pub struct ArweaveEntry;

impl Entry for ArweaveEntry {
fn get_supported_import_types(&self) -> Vec<ChainImportType> {
vec![ChainImportType::PrivateKey]
}

fn get_supported_export_types(&self) -> Vec<ChainExportType> {
vec![ChainExportType::PrivateKey]
}

fn validate_address(&self, address: &str) -> bool {
ArweaveAddress::is_valid(&address)
}

fn derive_address(
&self,
_coin: &Coin,
public_key: &PublicKey,
_p2pkh: &[u8],
_hrp: &[u8],
) -> Result<String, Error> {
let address = ArweaveAddress::new(public_key)?;
Ok(address.to_string())
}

fn sign(
&self,
_coin: &Coin,
private_key: &PrivateKey,
payload: &[u8],
) -> Result<Vec<u8>, Error> {
Ok(vec![])
// let sign_input: SignInput = match SignInput::decode(payload) {
// Ok(request) => request,
// Err(_) => return Err(Error::InvalidPrivateKey),
// };
// let output =
// Signer::sign(&private_key, &sign_input).map_err(|_| Error::InvalidPrivateKey)?;

// let mut buf = BytesMut::with_capacity(output.encoded_len());
// output
// .encode(&mut buf)
// .expect("Fail to encode the SignOutput");
// Ok(buf.to_vec())
}
}
1 change: 1 addition & 0 deletions chain/arweave/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod address;pub mod entry;
3 changes: 3 additions & 0 deletions crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ edition = "2018"
getrandom = { version = "0.2", features = ["js"] }
hex = "0.4.3"
sha2 = "0.9.4"
biscuit = "0.6.0-beta1"
base64 = "0.13.0"
rsa = { version = "0.5.0", features = ["serde"] }
aes = { version = "0.7.0", features = ["ctr"] }
bitcoin = { version = "0.26.0" }
bip39 = { version = "1.0.1", features = ["all-languages"] }
Expand Down
64 changes: 64 additions & 0 deletions crypto/src/jwk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::Error as CryptoError;
use rsa::RsaPrivateKey;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::str::FromStr;

use base64;
use biscuit::jwa::{Algorithm, SignatureAlgorithm};
use biscuit::jwk::{AlgorithmParameters, CommonParameters, RSAKeyParameters, JWK};
use biscuit::Empty;
use rsa::PublicKeyParts;

#[derive(Serialize, Deserialize)]
struct RSAMid {
kty: String,
n: String,
e: String,
d: String,
p: String,
q: String,
}

pub fn new_jwk(rsa_priv_key: &RsaPrivateKey) -> Result<Vec<u8>, CryptoError> {
let rsa_mid = RSAMid {
kty: "RSA".to_owned(),
n: base64::encode_config(rsa_priv_key.n().to_bytes_be(), base64::URL_SAFE_NO_PAD),
e: base64::encode_config(rsa_priv_key.e().to_bytes_be(), base64::URL_SAFE_NO_PAD),
d: base64::encode_config(rsa_priv_key.d().to_bytes_be(), base64::URL_SAFE_NO_PAD),
p: base64::encode_config(
rsa_priv_key.primes()[0].to_bytes_be(),
base64::URL_SAFE_NO_PAD,
),
q: base64::encode_config(
rsa_priv_key.primes()[1].to_bytes_be(),
base64::URL_SAFE_NO_PAD,
),
};
let rsa_mid_str =
serde_json::to_string(&rsa_mid).map_err(|_| CryptoError::InvalidKeyIvLength)?;

let rsa_param: RSAKeyParameters =
serde_json::from_str(&rsa_mid_str).map_err(|_| CryptoError::InvalidKeyIvLength)?;

let jwk: JWK<Empty> = JWK {
common: CommonParameters {
algorithm: Some(Algorithm::Signature(SignatureAlgorithm::RS256)),
..Default::default()
},
algorithm: AlgorithmParameters::RSA(rsa_param),
additional: Default::default(),
};
let jwk_bytes = serde_json::to_vec(&jwk).map_err(|_| CryptoError::InvalidPrivateKey)?;
Ok(jwk_bytes)
}

pub fn jwk_from_bytes(jwk_bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
let jwk: JWK<Empty> =
serde_json::from_slice(&jwk_bytes).map_err(|_| CryptoError::InvalidPrivateKey)?;
let rsa_param = match jwk.algorithm {
AlgorithmParameters::RSA(rsa_param) => rsa_param,
_ => return Err(CryptoError::InvalidPrivateKey),
};
Ok(Sha256::digest(&rsa_param.n.to_bytes_be()).to_vec())
}
1 change: 1 addition & 0 deletions crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod aes;
pub mod aes_params;
pub mod curve;
pub mod hash;
pub mod jwk;
pub mod kdf_params;
pub mod key_store_json;
pub mod public_key;
Expand Down
12 changes: 12 additions & 0 deletions crypto/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::jwk;
use crate::Error;
use ed25519_dalek;
use secp256k1;
Expand All @@ -8,6 +9,7 @@ pub enum PublicKeyType {
Secp256k1,
Secp256k1Extended,
Ed25519,
Arweave,
}

impl FromStr for PublicKeyType {
Expand All @@ -18,6 +20,7 @@ impl FromStr for PublicKeyType {
"secp256k1" => Ok(Self::Secp256k1),
"secp256k1extended" => Ok(Self::Secp256k1Extended),
"ed25519" => Ok(Self::Ed25519),
"arweave" => Ok(Self::Arweave),
_ => Err(()),
}
}
Expand Down Expand Up @@ -58,6 +61,14 @@ impl PublicKeyConvert for Ed25519Converter {
}
}

struct ArweaveConverter;

impl PublicKeyConvert for ArweaveConverter {
fn convert(&self, private_key: &[u8]) -> Result<Vec<u8>, Error> {
Ok(jwk::jwk_from_bytes(&private_key)?)
}
}

trait PublicKeyConvert {
fn convert(&self, private_key: &[u8]) -> Result<Vec<u8>, Error>;
}
Expand Down Expand Up @@ -85,5 +96,6 @@ pub fn get_public_key(
PublickKeyConvertter::convert(Secp256k1ExtendConverter, private_key)
}
PublicKeyType::Ed25519 => PublickKeyConvertter::convert(Ed25519Converter, private_key),
PublicKeyType::Arweave => PublickKeyConvertter::convert(ArweaveConverter, private_key),
}
}
2 changes: 1 addition & 1 deletion interface/resource/coin.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"blockchain": "Arweave",
"derivation_path": "",
"curve": "",
"public_key_type": "rsa",
"public_key_type": "arweave",
"explorer": {
"url": "https://viewblock.io/arweave",
"txPath": "/txs/",
Expand Down
2 changes: 1 addition & 1 deletion interface/src/coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ mod tests {
use super::COINS_MAP;
#[test]
fn test_get_coin_info() {
assert_eq!(COINS_MAP.len(), 3);
assert_eq!(COINS_MAP.len(), 4);
let coin_info = COINS_MAP.get("ethereum").unwrap();
assert_eq!(coin_info.curve, "secp256k1");
}
Expand Down
1 change: 1 addition & 0 deletions wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ crypto = { path = "../crypto" }
chain-common = { path = "../chain-common" }
ethereum = { path = "../chain/ethereum" }
solana = { path = "../chain/solana" }
arweave = { path = "../chain/arweave" }

hex = "0.4.3"
uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] }
Expand Down