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

[gh-509 and gh-531] improve rooch init, rooch account import, and rooch account create to ECDSA and Schnorr, refactor ETH signature to Rooch signature. #532

Merged
merged 11 commits into from
Jul 27, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn test_validate() {
.as_module_bundle::<rooch_framework::bindings::ecdsa_k1_validator::EcdsaK1Validator>(
);

let keystore = InMemKeystore::new_insecure_for_tests(1);
let keystore = InMemKeystore::new_ecdsa_insecure_for_tests(1);
let sender = keystore.addresses()[0];
let sequence_number = 0;
let action = MoveAction::new_function_call(Empty::empty_function_id(), vec![], vec![]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn test_validate() {
.as_module_bundle::<rooch_framework::bindings::ed25519_validator::Ed25519Validator>(
);

let keystore = InMemKeystore::new_insecure_for_tests(1);
let keystore = InMemKeystore::new_ed25519_insecure_for_tests(1);
let sender = keystore.addresses()[0];
let sequence_number = 0;
let action = MoveAction::new_function_call(Empty::empty_function_id(), vec![], vec![]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn test_validate() {
.as_module_bundle::<rooch_framework::bindings::schnorr_validator::SchnorrValidator>(
);

let keystore = InMemKeystore::new_insecure_for_tests(1);
let keystore = InMemKeystore::new_schnorr_insecure_for_tests(1);
let sender = keystore.addresses()[0];
let sequence_number = 0;
let action = MoveAction::new_function_call(Empty::empty_function_id(), vec![], vec![]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use rooch_types::transaction::{rooch::RoochTransactionData, AbstractTransaction}
use crate::binding_test;

#[test]
fn test_validate() {
fn test_validate_ed25519() {
let binding_test = binding_test::RustBindingTest::new().unwrap();
let transaction_validator = binding_test.as_module_bundle::<rooch_framework::bindings::transaction_validator::TransactionValidator>();

let keystore = InMemKeystore::new_insecure_for_tests(1);
let keystore = InMemKeystore::new_ed25519_insecure_for_tests(1);
let sender = keystore.addresses()[0];
let sequence_number = 0;
let action = MoveAction::new_function_call(Empty::empty_function_id(), vec![], vec![]);
Expand Down
174 changes: 137 additions & 37 deletions crates/rooch-key/src/key_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::anyhow;
use bip32::XPrv;
use bip32::{ChildNumber, DerivationPath};
use bip39::{Language, Mnemonic, MnemonicType, Seed};
use fastcrypto::ed25519::Ed25519KeyPair;
use fastcrypto::secp256k1::schnorr::{SchnorrKeyPair, SchnorrPrivateKey};
use fastcrypto::secp256k1::{Secp256k1KeyPair, Secp256k1PrivateKey};
use fastcrypto::{
ed25519::Ed25519PrivateKey,
traits::{KeyPair, ToFromBytes},
Expand All @@ -15,70 +18,167 @@ use rooch_types::error::RoochError;
use slip10_ed25519::derive_ed25519_private_key;
use std::string::String;

pub const DERIVATION_PATH_COIN_TYPE: u32 = 784;
// Coin type
pub const DERIVATION_PATH_COIN_TYPE_BTC: u32 = 0;
pub const DERIVATION_PATH_COIN_TYPE_ETH: u32 = 60;
pub const DERIVATION_PATH_COIN_TYPE_SUI: u32 = 784;
pub const DERIVATION_PATH_COIN_TYPE_LBTC: u32 = 998;
pub const DERIVATION_PATH_COIN_TYPE_NOSTR: u32 = 1237;
// Purpose
/// Ed25519 follows SLIP-0010 using hardened path: m/44'/784'/0'/0'/{index}'
/// Note that the purpose node is used to distinguish signature schemes.
pub const DERVIATION_PATH_PURPOSE_ED25519: u32 = 44;
// BIP32 is used to derive the path m/44'/1237'/<account>'/0/0 (according to the Nostr entry on SLIP44).
/// BIP39 is used to generate mnemonic seed words and derive a binary seed from them.
/// BIP32 is used to derive the path m/44'/1237'/<account>'/0/0 (according to the Nostr entry on SLIP44).
/// A basic client can simply use an account of 0 to derive a single key. For more advanced use-cases you can increment account, allowing generation of practically infinite keys from the 5-level path with hardened derivation.
/// Other types of clients can still get fancy and use other derivation paths for their own other purposes.
pub const DERVIATION_PATH_PURPOSE_SCHNORR: u32 = 44;
pub const DERVIATION_PATH_PURPOSE_ECDSA: u32 = 54;
pub const DERVIATION_PATH_PURPOSE_SECP256R1: u32 = 74;

/// Ed25519 follows SLIP-0010 using hardened path: m/44'/784'/0'/0'/{index}'
/// Note that the purpose node is used to distinguish signature schemes.
pub fn derive_key_pair_from_path(
seed: &[u8],
derivation_path: Option<DerivationPath>,
key_scheme: &BuiltinScheme,
crypto_scheme: &BuiltinScheme,
) -> Result<(RoochAddress, RoochKeyPair), RoochError> {
// TODO:: Other scheme
let path = validate_path(key_scheme, derivation_path)?;
let indexes = path.into_iter().map(|i| i.into()).collect::<Vec<_>>();
let derived = derive_ed25519_private_key(seed, &indexes);
let sk = Ed25519PrivateKey::from_bytes(&derived)
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?;
let kp: Ed25519KeyPair = sk.into();
Ok((kp.public().into(), RoochKeyPair::Ed25519(kp)))
let path = validate_path(crypto_scheme, derivation_path)?;
match crypto_scheme {
BuiltinScheme::Ed25519 => {
let indexes = path.into_iter().map(|i| i.into()).collect::<Vec<_>>();
let derived = derive_ed25519_private_key(seed, &indexes);
let sk = Ed25519PrivateKey::from_bytes(&derived)
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?;
let kp: Ed25519KeyPair = sk.into();
Ok((kp.public().into(), RoochKeyPair::Ed25519(kp)))
}
BuiltinScheme::MultiEd25519 => {
todo!()
}
BuiltinScheme::Ecdsa => {
let child_xprv = XPrv::derive_from_path(seed, &path)
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?;
let kp = Secp256k1KeyPair::from(
Secp256k1PrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?,
);
Ok((kp.public().into(), RoochKeyPair::Ecdsa(kp)))
}
BuiltinScheme::Schnorr => {
let child_xprv = XPrv::derive_from_path(seed, &path)
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?;
let kp = SchnorrKeyPair::from(
SchnorrPrivateKey::from_bytes(child_xprv.private_key().to_bytes().as_slice())
.map_err(|e| RoochError::SignatureKeyGenError(e.to_string()))?,
);
Ok((kp.public().into(), RoochKeyPair::Schnorr(kp)))
}
}
}

pub fn validate_path(
_: &BuiltinScheme,
crypto_scheme: &BuiltinScheme,
path: Option<DerivationPath>,
) -> Result<DerivationPath, RoochError> {
// The derivation path must be hardened at all levels with purpose = 44, coin_type = 784
match path {
Some(p) => {
// The derivation path must be hardened at all levels with purpose = 44, coin_type = 784
if let &[purpose, coin_type, account, change, address] = p.as_ref() {
if Some(purpose) == ChildNumber::new(DERVIATION_PATH_PURPOSE_ED25519, true).ok()
&& Some(coin_type) == ChildNumber::new(DERIVATION_PATH_COIN_TYPE, true).ok()
&& account.is_hardened()
&& change.is_hardened()
&& address.is_hardened()
{
Ok(p)
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
match crypto_scheme {
BuiltinScheme::Ed25519 => {
match path {
Some(p) => {
// The derivation path must be hardened at all levels with purpose = 44, coin_type = 784
if let &[purpose, coin_type, account, change, address] = p.as_ref() {
if Some(purpose)
== ChildNumber::new(DERVIATION_PATH_PURPOSE_ED25519, true).ok()
&& Some(coin_type)
== ChildNumber::new(DERIVATION_PATH_COIN_TYPE_SUI, true).ok()
&& account.is_hardened()
&& change.is_hardened()
&& address.is_hardened()
{
Ok(p)
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
}
None => Ok(format!(
"m/{DERVIATION_PATH_PURPOSE_ED25519}'/{DERIVATION_PATH_COIN_TYPE_SUI}'/0'/0'/0'"
)
.parse()
.map_err(|_| RoochError::SignatureKeyGenError("Cannot parse path".to_owned()))?),
}
}
BuiltinScheme::MultiEd25519 => {
todo!()
}
BuiltinScheme::Ecdsa => {
match path {
Some(p) => {
// The derivation path must be hardened at all levels with purpose = 44, coin_type = 784
if let &[purpose, coin_type, account, change, address] = p.as_ref() {
if Some(purpose)
== ChildNumber::new(DERVIATION_PATH_PURPOSE_ECDSA, true).ok()
&& Some(coin_type)
== ChildNumber::new(DERIVATION_PATH_COIN_TYPE_SUI, true).ok()
&& account.is_hardened()
&& change.is_hardened()
&& address.is_hardened()
{
Ok(p)
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
}
None => Ok(format!(
"m/{DERVIATION_PATH_PURPOSE_ECDSA}'/{DERIVATION_PATH_COIN_TYPE_SUI}'/0'/0'/0'"
)
.parse()
.map_err(|_| RoochError::SignatureKeyGenError("Cannot parse path".to_owned()))?),
}
}
BuiltinScheme::Schnorr => {
match path {
Some(p) => {
// The derivation path must be hardened at all levels with purpose = 44, coin_type = 784
if let &[purpose, coin_type, account, change, address] = p.as_ref() {
if Some(purpose) == ChildNumber::new(DERVIATION_PATH_PURPOSE_SCHNORR, true).ok()
&& Some(coin_type) == ChildNumber::new(DERIVATION_PATH_COIN_TYPE_NOSTR, true).ok()
&& account.is_hardened()
&& change.is_hardened()
&& address.is_hardened()
{
Ok(p)
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
}
}
} else {
Err(RoochError::SignatureKeyGenError("Invalid path".to_owned()))
None => Ok(format!(
// for a signle key
"m/{DERVIATION_PATH_PURPOSE_SCHNORR}'/{DERIVATION_PATH_COIN_TYPE_NOSTR}'/0'/0'/0'"
)
.parse()
.map_err(|_| RoochError::SignatureKeyGenError("Cannot parse path".to_owned()))?),
}
}
None => Ok(format!(
"m/{DERVIATION_PATH_PURPOSE_ED25519}'/{DERIVATION_PATH_COIN_TYPE}'/0'/0'/0'"
)
.parse()
.map_err(|_| RoochError::SignatureKeyGenError("Cannot parse path".to_owned()))?),
}
}

pub fn generate_new_key(
key_scheme: BuiltinScheme,
crypto_scheme: BuiltinScheme,
derivation_path: Option<DerivationPath>,
word_length: Option<String>,
) -> Result<(RoochAddress, RoochKeyPair, BuiltinScheme, String), anyhow::Error> {
let mnemonic = Mnemonic::new(parse_word_length(word_length)?, Language::English);
let seed = Seed::new(&mnemonic, "");
match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &key_scheme) {
Ok((address, kp)) => Ok((address, kp, key_scheme, mnemonic.phrase().to_string())),
match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &crypto_scheme) {
Ok((address, kp)) => Ok((address, kp, crypto_scheme, mnemonic.phrase().to_string())),
Err(e) => Err(anyhow!("Failed to generate keypair: {:?}", e)),
}
}
Expand Down
34 changes: 27 additions & 7 deletions crates/rooch-key/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,26 @@ pub trait AccountKeystore: Send + Sync {

fn generate_and_add_new_key(
&mut self,
key_scheme: BuiltinScheme,
crypto_scheme: BuiltinScheme,
derivation_path: Option<DerivationPath>,
word_length: Option<String>,
) -> Result<(RoochAddress, String, BuiltinScheme), anyhow::Error> {
let (address, kp, scheme, phrase) =
generate_new_key(key_scheme, derivation_path, word_length)?;
generate_new_key(crypto_scheme, derivation_path, word_length)?;
self.add_key(kp)?;
Ok((address, phrase, scheme))
}

fn import_from_mnemonic(
&mut self,
phrase: &str,
key_scheme: BuiltinScheme,
crypto_scheme: BuiltinScheme,
derivation_path: Option<DerivationPath>,
) -> Result<RoochAddress, anyhow::Error> {
let mnemonic = Mnemonic::from_phrase(phrase, Language::English)
.map_err(|e| anyhow::anyhow!("Invalid mnemonic phrase: {:?}", e))?;
let seed = Seed::new(&mnemonic, "");
match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &key_scheme) {
match derive_key_pair_from_path(seed.as_bytes(), derivation_path, &crypto_scheme) {
Ok((address, kp)) => {
self.add_key(kp)?;
Ok(address)
Expand Down Expand Up @@ -183,7 +183,7 @@ impl AccountKeystore for FileBasedKeystore {

let auth = match pk.public().scheme() {
BuiltinScheme::Ed25519 => authenticator::Authenticator::ed25519(signature),
BuiltinScheme::Ecdsa => todo!(),
BuiltinScheme::Ecdsa => authenticator::Authenticator::ecdsa(signature),
BuiltinScheme::MultiEd25519 => todo!(),
BuiltinScheme::Schnorr => authenticator::Authenticator::schnorr(signature),
};
Expand Down Expand Up @@ -298,7 +298,7 @@ impl AccountKeystore for InMemKeystore {

let auth = match pk.public().scheme() {
BuiltinScheme::Ed25519 => authenticator::Authenticator::ed25519(signature),
BuiltinScheme::Ecdsa => todo!(),
BuiltinScheme::Ecdsa => authenticator::Authenticator::ecdsa(signature),
BuiltinScheme::MultiEd25519 => todo!(),
BuiltinScheme::Schnorr => authenticator::Authenticator::schnorr(signature),
};
Expand Down Expand Up @@ -337,7 +337,7 @@ impl AccountKeystore for InMemKeystore {
}

impl InMemKeystore {
pub fn new_insecure_for_tests(initial_key_number: usize) -> Self {
pub fn new_ed25519_insecure_for_tests(initial_key_number: usize) -> Self {
let mut rng = StdRng::from_seed([0; 32]);
let keys = (0..initial_key_number)
.map(|_| get_key_pair_from_rng(&mut rng))
Expand All @@ -346,4 +346,24 @@ impl InMemKeystore {

Self { keys }
}

pub fn new_ecdsa_insecure_for_tests(initial_key_number: usize) -> Self {
let mut rng = StdRng::from_seed([0; 32]);
let keys = (0..initial_key_number)
.map(|_| get_key_pair_from_rng(&mut rng))
.map(|(ad, k)| (ad, RoochKeyPair::Ecdsa(k)))
.collect::<BTreeMap<RoochAddress, RoochKeyPair>>();

Self { keys }
}

pub fn new_schnorr_insecure_for_tests(initial_key_number: usize) -> Self {
let mut rng = StdRng::from_seed([0; 32]);
let keys = (0..initial_key_number)
.map(|_| get_key_pair_from_rng(&mut rng))
.map(|(ad, k)| (ad, RoochKeyPair::Schnorr(k)))
.collect::<BTreeMap<RoochAddress, RoochKeyPair>>();

Self { keys }
}
}
2 changes: 1 addition & 1 deletion crates/rooch-rpc-client/src/wallet_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl WalletContext {
let signature = Signature::new_hashed(tx_data.hash().as_bytes(), pk);
let auth = match pk.public().scheme() {
BuiltinScheme::Ed25519 => Authenticator::ed25519(signature),
BuiltinScheme::Ecdsa => todo!(),
BuiltinScheme::Ecdsa => Authenticator::ecdsa(signature),
BuiltinScheme::MultiEd25519 => todo!(),
BuiltinScheme::Schnorr => Authenticator::schnorr(signature),
};
Expand Down
1 change: 1 addition & 0 deletions crates/rooch-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ strum = { workspace = true }
strum_macros = { workspace = true }
async-trait ={ workspace = true }
nostr = { workspace = true }
clap = { workspace = true }

move-core-types = { workspace = true }
move-stdlib = { workspace = true }
Expand Down