diff --git a/chain-common/Cargo.toml b/chain-common/Cargo.toml index d1507f8..099a45b 100644 --- a/chain-common/Cargo.toml +++ b/chain-common/Cargo.toml @@ -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] diff --git a/chain-common/proto/base.proto b/chain-common/proto/base.proto index 6552491..78ca8af 100644 --- a/chain-common/proto/base.proto +++ b/chain-common/proto/base.proto @@ -6,6 +6,7 @@ enum Coin { Ethereum = 0; Polkadot = 1; Solana = 2; + Arweave = 3; } enum StoredKeyType { diff --git a/chain-common/src/coin.rs b/chain-common/src/coin.rs index 984f8dd..bb4e74e 100644 --- a/chain-common/src/coin.rs +++ b/chain-common/src/coin.rs @@ -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), } } diff --git a/chain-common/src/entry.rs b/chain-common/src/entry.rs index 1c563a6..0fcf36c 100644 --- a/chain-common/src/entry.rs +++ b/chain-common/src/entry.rs @@ -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 { diff --git a/chain-common/src/generated/api.rs b/chain-common/src/generated/api.rs index c610f6f..8b06d7e 100644 --- a/chain-common/src/generated/api.rs +++ b/chain-common/src/generated/api.rs @@ -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)] diff --git a/chain-common/src/private_key.rs b/chain-common/src/private_key.rs index 9b00a16..3e0dcb0 100644 --- a/chain-common/src/private_key.rs +++ b/chain-common/src/private_key.rs @@ -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; @@ -78,6 +84,18 @@ impl PrivateKey { } } + pub fn new_jwk_key_from_rsa(bit_size: usize) -> Result { + 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 { let public_key_type = PublicKeyType::from_str(public_key_type_str) .map_err(|_| CryptoError::NotSupportedPublicKeyType)?; @@ -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"); + // } } diff --git a/chain-common/src/public_key.rs b/chain-common/src/public_key.rs index 1f1237a..93ac527 100644 --- a/chain-common/src/public_key.rs +++ b/chain-common/src/public_key.rs @@ -27,6 +27,7 @@ impl PublicKey { PublicKeyType::Ed25519 => { size == ED25519_SIZE || (size == ED25519_SIZE + 1 && data[0] == 0x01) } + PublicKeyType::Arweave => true, } } diff --git a/chain/arweave/Cargo.toml b/chain/arweave/Cargo.toml new file mode 100644 index 0000000..5f17bb7 --- /dev/null +++ b/chain/arweave/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "arweave" +version = "0.1.0" +authors = ["jk234ert "] +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" } \ No newline at end of file diff --git a/chain/arweave/src/address.rs b/chain/arweave/src/address.rs new file mode 100644 index 0000000..fb9334e --- /dev/null +++ b/chain/arweave/src/address.rs @@ -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, +} + +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 { + 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); + } +} diff --git a/chain/arweave/src/entry.rs b/chain/arweave/src/entry.rs new file mode 100644 index 0000000..fb668fc --- /dev/null +++ b/chain/arweave/src/entry.rs @@ -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 { + vec![ChainImportType::PrivateKey] + } + + fn get_supported_export_types(&self) -> Vec { + 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 { + let address = ArweaveAddress::new(public_key)?; + Ok(address.to_string()) + } + + fn sign( + &self, + _coin: &Coin, + private_key: &PrivateKey, + payload: &[u8], + ) -> Result, 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()) + } +} diff --git a/chain/arweave/src/lib.rs b/chain/arweave/src/lib.rs new file mode 100644 index 0000000..97876e7 --- /dev/null +++ b/chain/arweave/src/lib.rs @@ -0,0 +1 @@ +pub mod address;pub mod entry; diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 0b2cb74..d07a55d 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -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"] } diff --git a/crypto/src/jwk.rs b/crypto/src/jwk.rs new file mode 100644 index 0000000..a654521 --- /dev/null +++ b/crypto/src/jwk.rs @@ -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, 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 = 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, CryptoError> { + let jwk: JWK = + 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()) +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index b83c2d8..7992f54 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -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; diff --git a/crypto/src/public_key.rs b/crypto/src/public_key.rs index dd04087..3550d63 100644 --- a/crypto/src/public_key.rs +++ b/crypto/src/public_key.rs @@ -1,3 +1,4 @@ +use super::jwk; use crate::Error; use ed25519_dalek; use secp256k1; @@ -8,6 +9,7 @@ pub enum PublicKeyType { Secp256k1, Secp256k1Extended, Ed25519, + Arweave, } impl FromStr for PublicKeyType { @@ -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(()), } } @@ -58,6 +61,14 @@ impl PublicKeyConvert for Ed25519Converter { } } +struct ArweaveConverter; + +impl PublicKeyConvert for ArweaveConverter { + fn convert(&self, private_key: &[u8]) -> Result, Error> { + Ok(jwk::jwk_from_bytes(&private_key)?) + } +} + trait PublicKeyConvert { fn convert(&self, private_key: &[u8]) -> Result, Error>; } @@ -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), } } diff --git a/interface/resource/coin.json b/interface/resource/coin.json index 3f12ddb..99d9f47 100644 --- a/interface/resource/coin.json +++ b/interface/resource/coin.json @@ -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/", diff --git a/interface/src/coins.rs b/interface/src/coins.rs index 8350257..41ba291 100644 --- a/interface/src/coins.rs +++ b/interface/src/coins.rs @@ -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"); } diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 3638e3f..248451f 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -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"] }