Newer
Older
Nicolas80
committed
use sp_core::ed25519;
pub const SUBSTRATE_MNEMONIC: &str =
"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// Raw 32B seed
Seed,
/// Substrate secret key or BIP39 mnemonic (optionally followed by derivation path)
/// Predefined (Alice, Bob, ...)
Predefined,
/// G1v1 id+secret using (scrypt + ed25519)
G1v1,
Nicolas80
committed
impl FromStr for SecretFormat {
fn from_str(s: &str) -> std::io::Result<Self> {
match s {
"seed" => Ok(SecretFormat::Seed),
"substrate" => Ok(SecretFormat::Substrate),
"predefined" => Ok(SecretFormat::Predefined),
"g1v1" => Ok(SecretFormat::G1v1),
//Still support "cesium" input as well for backward compatibility
"cesium" => Ok(SecretFormat::G1v1),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
}
}
}
impl From<SecretFormat> for &'static str {
fn from(val: SecretFormat) -> &'static str {
match val {
SecretFormat::Seed => "seed",
SecretFormat::Substrate => "substrate",
SecretFormat::Predefined => "predefined",
SecretFormat::G1v1 => "g1v1",
}
impl From<SecretFormat> for OsStr {
fn from(val: SecretFormat) -> OsStr {
OsStr::from(Into::<&str>::into(val))
}
/// The crypto scheme to use - partial copy from sc_cli::arg_enums::CryptoScheme
///
/// Preferred making a copy since adding the dependency to sc-cli brings more than 300 dependencies
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CryptoScheme {
/// Use ed25519 - used for SecretFormat::Cesium
Ed25519,
/// Use sr25519.
Sr25519,
}
impl FromStr for CryptoScheme {
type Err = std::io::Error;
fn from_str(s: &str) -> std::io::Result<Self> {
match s {
"ed25519" => Ok(CryptoScheme::Ed25519),
"sr25519" => Ok(CryptoScheme::Sr25519),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
}
}
}
impl From<CryptoScheme> for &'static str {
fn from(val: CryptoScheme) -> &'static str {
match val {
CryptoScheme::Ed25519 => "ed25519",
CryptoScheme::Sr25519 => "sr25519",
}
}
}
impl From<CryptoScheme> for OsStr {
fn from(val: CryptoScheme) -> OsStr {
OsStr::from(Into::<&str>::into(val))
}
}
/// wrapper type for keys + signature
Nicolas80
committed
//FIXME check if it's ok to keep large enum variant
// Sr25519 second-largest variant contains at least 256 bytes
// Ed25519 largest variant contains at least 480 bytes
// Replace by CryptoType from sp-core ?
// sp_core::crypto::CryptoType (trait)
Nicolas80
committed
#[allow(clippy::large_enum_variant)]
}
impl KeyPair {
pub fn address(&self) -> AccountId {
match self {
KeyPair::Sr25519(keypair) => keypair.public().into(),
Nicolas80
committed
KeyPair::Ed25519(keypair) => keypair.public().into(),
}
}
// can not derive clone because nacl does not implement it
impl Clone for KeyPair {
fn clone(&self) -> Self {
match self {
KeyPair::Sr25519(keypair) => KeyPair::Sr25519(keypair.clone()),
Nicolas80
committed
KeyPair::Ed25519(keypair) => KeyPair::Ed25519(*keypair),
impl From<sr25519::Pair> for KeyPair {
fn from(pair: sr25519::Pair) -> KeyPair {
impl From<ed25519::Pair> for KeyPair {
fn from(pair: ed25519::Pair) -> KeyPair {
Nicolas80
committed
KeyPair::Ed25519(pair)
}
}
pub enum Signature {
Sr25519(sr25519::Signature),
Nicolas80
committed
Ed25519(ed25519::Signature),
/// get keypair in any possible way
/// at this point, if secret is predefined, it's not replaced yet
pub fn get_keypair(
secret_format: SecretFormat,
secret: Option<&str>,
) -> Result<KeyPair, GcliError> {
match (secret_format, secret) {
(SecretFormat::Predefined, Some(deriv)) => pair_from_predefined(deriv).map(|v| v.into()),
(secret_format, None) => Ok(prompt_secret(secret_format)),
(_, Some(secret)) => Ok(pair_from_secret(secret_format, secret)?.into()),
}
}
/// get keypair from given secret
/// if secret is predefined, secret should contain the predefined value
pub fn pair_from_secret(
secret_format: SecretFormat,
secret: &str,
Nicolas80
committed
SecretFormat::Substrate => pair_from_sr25519_str(secret),
SecretFormat::Predefined => pair_from_sr25519_str(secret), /* if predefined, secret arg is replaced in config */
SecretFormat::Seed => pair_from_sr25519_seed(secret),
SecretFormat::G1v1 => Err(GcliError::Logic(
"G1v1 format incompatible with single secret".to_string(),
)),
}
}
/// get keypair from given string secret
pub fn pair_from_sr25519_str(secret: &str) -> Result<sr25519::Pair, GcliError> {
let _validation_only = vault::parse_prefix_and_derivation_path_from_suri(secret.to_string())?;
sr25519::Pair::from_string(secret, None)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}
/// get keypair from given seed
// note: sr25519::Pair::from_string does exactly that when seed is 0x prefixed
// (see from_string_with_seed method in crypto core)
pub fn pair_from_sr25519_seed(secret: &str) -> Result<sr25519::Pair, GcliError> {
let mut seed = [0; 32];
hex::decode_to_slice(secret, &mut seed)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
Nicolas80
committed
/// get keypair from given ed25519 string secret (used for cesium)
pub fn pair_from_ed25519_str(secret: &str) -> Result<ed25519::Pair, GcliError> {
ed25519::Pair::from_string(secret, None)
Nicolas80
committed
.map_err(|_| GcliError::Input("Invalid secret".to_string()))
}
/// get keypair from given ed25519 seed (used for cesium)
#[allow(unused)]
pub fn pair_from_ed25519_seed(secret: &str) -> Result<ed25519::Pair, GcliError> {
Nicolas80
committed
let mut seed = [0; 32];
hex::decode_to_slice(secret, &mut seed)
.map_err(|_| GcliError::Input("Invalid secret".to_string()))?;
Nicolas80
committed
Ok(pair)
}
/// get mnemonic from predefined derivation path
pub fn predefined_mnemonic(deriv: &str) -> String {
format!("{SUBSTRATE_MNEMONIC}//{deriv}")
}
/// get keypair from predefined secret
pub fn pair_from_predefined(deriv: &str) -> Result<sr25519::Pair, GcliError> {
Nicolas80
committed
pair_from_sr25519_str(&predefined_mnemonic(deriv))
pub fn seed_from_cesium(id: &str, pwd: &str) -> [u8; 32] {
let params = scrypt::Params::new(12u8, 16u32, 1u32, 32).unwrap();
let mut seed = [0u8; 32];
scrypt::scrypt(pwd.as_bytes(), id.as_bytes(), ¶ms, &mut seed).unwrap();
seed
}
/// ask user to input a secret
Nicolas80
committed
// Only interested in the keypair which is the second element of the tuple
prompt_secret_substrate_and_compute_keypair().1
}
pub fn prompt_secret_substrate_and_compute_keypair() -> (String, sr25519::Pair) {
println!("Substrate URI can be a mnemonic or a mini-secret ('0x' prefixed seed) together with optional derivation path");
let substrate_suri = inputs::prompt_password_query("Substrate URI: ").unwrap();
match pair_from_sr25519_str(&substrate_suri) {
Ok(pair) => return (substrate_suri, pair),
Err(_) => println!("Invalid secret"),
}
}
Nicolas80
committed
// Only interested in the keypair which is the second element of the tuple
prompt_secret_cesium_and_compute_keypair().1
}
pub fn prompt_secret_cesium_and_compute_keypair() -> (String, ed25519::Pair) {
let id = inputs::prompt_password_query("G1v1 id: ").unwrap();
let pwd = inputs::prompt_password_query("G1v1 password: ").unwrap();
Nicolas80
committed
let seed = seed_from_cesium(&id, &pwd);
let secret_suri = format!("0x{}", hex::encode(seed));
match pair_from_ed25519_str(&secret_suri) {
Ok(pair) => (secret_suri, pair),
Err(_) => panic!("Could not compute KeyPair from G1v1 id/pwd"),
Nicolas80
committed
}
Nicolas80
committed
// Only interested in the keypair which is the second element of the tuple
prompt_seed_and_compute_keypair().1
}
pub fn prompt_seed_and_compute_keypair() -> (String, sr25519::Pair) {
Nicolas80
committed
let seed_str = inputs::prompt_seed().unwrap();
let secret_suri = format!("0x{}", seed_str);
match pair_from_sr25519_str(&secret_suri) {
Ok(pair) => return (secret_suri, pair),
Err(_) => println!("Invalid seed"),
}
}
}
/// ask user pass (Cesium format)
Nicolas80
committed
// Only interested in the keypair which is the second element of the tuple
prompt_predefined_and_compute_keypair().1
}
pub fn prompt_predefined_and_compute_keypair() -> (String, sr25519::Pair) {
let deriv = inputs::prompt_password_query("Enter derivation path: ").unwrap();
Nicolas80
committed
(
Nicolas80
committed
predefined_mnemonic(&deriv),
Nicolas80
committed
pair_from_predefined(&deriv).expect("invalid secret"),
)
}
/// ask user secret in relevant format
pub fn prompt_secret(secret_format: SecretFormat) -> KeyPair {
match secret_format {
SecretFormat::Substrate => prompt_secret_substrate().into(),
SecretFormat::G1v1 => prompt_secret_cesium().into(),
SecretFormat::Seed => prompt_seed().into(),
SecretFormat::Predefined => prompt_predefined().into(),
}
}
/// get the secret from user, trying first keystore then input
Nicolas80
committed
pub async fn fetch_or_get_keypair(
data: &Data,
address: Option<AccountId>,
) -> Result<KeyPair, GcliError> {
if let Some(address) = address {
// if address corresponds to predefined, (for example saved to config)
// keypair is already known (useful for dev mode)
if let Some(d) = catch_known(&address.to_string()) {
return Ok(pair_from_predefined(d).unwrap().into());
};
Nicolas80
committed
// look for corresponding KeyPair in keystore
if let Some(key_pair) = commands::vault::try_fetch_key_pair(data, address).await? {
return Ok(key_pair);
};
}
// at the moment, there is no way to confg gcli to use an other kind of secret
// without telling explicitly each time
Ok(prompt_secret(SecretFormat::Substrate))
}
// catch known addresses
fn catch_known(address: &str) -> Option<&str> {
match address {
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" => Some("Alice"),
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" => Some("Bob"),
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" => Some("Charlie"),
"5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy" => Some("Dave"),
"5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw" => Some("Eve"),
_ => None,
}
}
Nicolas80
committed
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
mod subkey_like_tests {
use super::keys::SUBSTRATE_MNEMONIC;
use sp_core::crypto::Ss58Codec;
use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry};
use sp_core::ByteArray;
use sp_runtime::traits::IdentifyAccount;
use sp_runtime::MultiSigner;
#[test]
fn test_print_from_suri() {
// sc_cli::CryptoSchemeFlag::augment_args()
let suri_str = SUBSTRATE_MNEMONIC.to_string();
print_from_suri::<sp_core::sr25519::Pair>(&suri_str, None, None);
print_from_suri::<sp_core::sr25519::Pair>(
&suri_str,
None,
Some(Ss58AddressFormatRegistry::G1Account.into()),
);
// bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice
let suri_str = SUBSTRATE_MNEMONIC.to_string() + "//Alice";
print_from_suri::<sp_core::sr25519::Pair>(&suri_str, None, None);
print_from_suri::<sp_core::sr25519::Pair>(
&suri_str,
None,
Some(Ss58AddressFormatRegistry::G1Account.into()),
);
}
/// print account information from suri - simplification of code from
/// sc_cli::commands::utils::print_from_uri
pub fn print_from_suri<Pair>(
uri: &str,
password: Option<&str>,
network_override: Option<Ss58AddressFormat>,
) where
Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
let network_id = String::from(unwrap_or_default_ss58_version(network_override));
if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
println!(
"Secret Key URI `{}` is account:\n \
Network ID: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
network_id,
if let Some(seed) = seed {
// sc_cli::utils::format_seed::<Pair>(seed)
format!("0x{}", hex::encode(seed.as_ref()))
} else {
"n/a".into()
},
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public()
.into()
.into_account()
.to_ss58check_with_version(network_override),
);
} else {
println!("Invalid phrase/URI given");
}
}
/// Public key type for Runtime
pub type PublicFor<P> = <P as sp_core::Pair>::Public;
/// formats public key as hex
fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
format!("0x{}", hex::encode(&public_key.as_ref()))
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
}
/// formats public key as accountId as hex
fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where
PublicFor<P>: Into<MultiSigner>,
{
format!(
"0x{}",
hex::encode(&public_key.into().into_account().as_slice())
)
}
pub fn unwrap_or_default_ss58_version(
network: Option<Ss58AddressFormat>,
) -> Ss58AddressFormat {
network.unwrap_or_else(default_ss58_version)
}
pub fn default_ss58_version() -> Ss58AddressFormat {
DEFAULT_VERSION
.load(core::sync::atomic::Ordering::Relaxed)
.into()
}
static DEFAULT_VERSION: core::sync::atomic::AtomicU16 = core::sync::atomic::AtomicU16::new(
from_known_address_format(Ss58AddressFormatRegistry::SubstrateAccount),
);
pub const fn from_known_address_format(x: Ss58AddressFormatRegistry) -> u16 {
x as u16
}
}
Nicolas80
committed
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
mod substrate {
use super::*;
/// Testing sr25519 mnemonic derivations
///
/// Using `subkey` command to have expected values from mnemonic derivations (using `SUBSTRATE_MNEMONIC` for tests)
///
/// ##### The root mnemonic
/// ```
/// subkey inspect
/// URI:
/// Secret phrase: bottom drive obey lake curtain smoke basket hold race lonely fit walk
/// Network ID: substrate
/// Secret seed: 0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
/// Public key (hex): 0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
/// Account ID: 0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
/// Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
/// SS58 Address: 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
/// ```
///
/// ##### The '//0' derivation
/// ```
/// subkey inspect
/// URI:
/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//0` is account:
/// Network ID: substrate
/// Secret seed: 0x914dded06277afbe5b0e8a30bce539ec8a9552a784d08e530dc7c2915c478393
/// Public key (hex): 0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
/// Account ID: 0x2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972
/// Public key (SS58): 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
/// SS58 Address: 5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH
/// ```
#[test]
fn test_sr25519_mnemonic_derivations() {
let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();
let expected_root_ss58_address_string =
"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
let root_ss58_address: AccountId = root_sr25519_pair.public().into();
println!("root SS58 Address: '{}'", root_ss58_address);
assert_eq!(
expected_root_ss58_address_string,
root_ss58_address.to_string()
);
// Using derive on root keypair to get '//0'
let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
.unwrap();
let expected_deriv_0_ss58_address_string =
"5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH".to_string();
let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
assert_eq!(
expected_deriv_0_ss58_address_string,
deriv_0_ss58_address.to_string()
);
// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
let deriv_0_suri = SUBSTRATE_MNEMONIC.to_string() + "//0";
let deriv_0_suri_sr25519_pair =
sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
println!(
"derived '//0' from suri SS58 Address: '{}'",
deriv_0_suri_ss58_address
);
assert_eq!(
expected_deriv_0_ss58_address_string,
deriv_0_suri_ss58_address.to_string()
);
}
}
mod seed {
use super::*;
/// Testing sr25519 seed derivations
///
Nicolas80
committed
/// Using `subkey` command to have expected values from seed derivations (using a newly generated mnemonic `festival insane keep vivid surface photo razor unaware twice sudden involve false` for this test)
Nicolas80
committed
///
/// ##### The root seed
Nicolas80
committed
/// (from the mnemonic first)
Nicolas80
committed
/// ```
/// subkey inspect
/// URI:
Nicolas80
committed
/// Secret phrase: festival insane keep vivid surface photo razor unaware twice sudden involve false
Nicolas80
committed
/// Network ID: substrate
Nicolas80
committed
/// Secret seed: 0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253
/// Public key (hex): 0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
/// Account ID: 0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
/// Public key (SS58): 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
/// SS58 Address: 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
/// ```
///
/// When using the seed directly
/// ```
/// subkey inspect
/// URI:
/// Secret Key URI `0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253` is account:
/// Network ID: substrate
/// Secret seed: 0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253
/// Public key (hex): 0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
/// Account ID: 0xce47000c942392afacc938b0db0b79d3377b1d1f5fbad6374c2943af05dfe379
/// Public key (SS58): 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
/// SS58 Address: 5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt
Nicolas80
committed
/// ```
///
/// ##### The '//0' derivation
/// ```
/// subkey inspect
/// URI:
Nicolas80
committed
/// Secret Key URI `0xf813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253//0` is account:
Nicolas80
committed
/// Network ID: substrate
Nicolas80
committed
/// Secret seed: 0xb1f84cbfd8db9de2b198f6658cf8ad5aacc157589e891a653ca1eed3979f8220
/// Public key (hex): 0xe2b36186911ac4bc7480516b8711be124d97c625f255cbf494bd5f997b8e6023
/// Account ID: 0xe2b36186911ac4bc7480516b8711be124d97c625f255cbf494bd5f997b8e6023
/// Public key (SS58): 5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV
/// SS58 Address: 5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV
Nicolas80
committed
/// ```
#[test]
fn test_sr25519_seed_derivations() {
Nicolas80
committed
let root_seed = "f813535799c3a15b8e419a06964e87fabd3f265caebcbb38c935a1acdbe05253";
Nicolas80
committed
let root_sr25519_pair = pair_from_sr25519_seed(root_seed).unwrap();
let expected_root_ss58_address_string =
Nicolas80
committed
"5GjAp6kkbsDjxABGouMvu1Mxbr7PaFqbMrprEoZ466vPF2Vt".to_string();
Nicolas80
committed
let root_ss58_address: AccountId = root_sr25519_pair.public().into();
println!("root SS58 Address: '{}'", root_ss58_address);
assert_eq!(
expected_root_ss58_address_string,
root_ss58_address.to_string()
);
// Using derive on root keypair to get "//0"
let (deriv_0_sr25519_pair, _seed) = root_sr25519_pair
.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
.unwrap();
let expected_deriv_0_ss58_address_string =
Nicolas80
committed
"5HBwy19piXNWg7bfShuNgWsBCWRzkG99M8eRGB6PHkxeAKAV".to_string();
Nicolas80
committed
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
let deriv_0_ss58_address: AccountId = deriv_0_sr25519_pair.public().into();
println!("derived '//0' SS58 Address: '{}'", deriv_0_ss58_address);
assert_eq!(
expected_deriv_0_ss58_address_string,
deriv_0_ss58_address.to_string()
);
// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
let deriv_0_suri = "0x".to_string() + root_seed + "//0";
let deriv_0_suri_sr25519_pair =
sp_core::sr25519::Pair::from_string(&deriv_0_suri, None).unwrap();
let deriv_0_suri_ss58_address: AccountId = deriv_0_suri_sr25519_pair.public().into();
println!(
"derived '//0' from suri SS58 Address: '{}'",
deriv_0_suri_ss58_address
);
assert_eq!(
expected_deriv_0_ss58_address_string,
deriv_0_suri_ss58_address.to_string()
);
}
}
mod predefined {
use super::*;
/// Testing predefined mnemonic derivations (using names instead of indexes)
///
/// Using `subkey` command to have expected values from predefined mnemonic derivations
///
/// ##### The root mnemonic
/// ```
/// subkey inspect
/// URI:
/// Secret phrase: bottom drive obey lake curtain smoke basket hold race lonely fit walk
/// Network ID: substrate
/// Secret seed: 0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e
/// Public key (hex): 0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
/// Account ID: 0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a
/// Public key (SS58): 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
/// SS58 Address: 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
/// ```
///
/// ##### The '//Alice' derivation
/// ```
/// subkey inspect
/// URI:
/// Secret Key URI `bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice` is account:
/// Network ID: substrate
/// Secret seed: 0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a
/// Public key (hex): 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
/// Account ID: 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
/// Public key (SS58): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
/// SS58 Address: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
/// ```
#[test]
fn test_predefined_mnemonic_derivations() {
let root_sr25519_pair = pair_from_sr25519_str(SUBSTRATE_MNEMONIC).unwrap();
let expected_root_ss58_address_string =
"5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV".to_string();
let root_ss58_address: AccountId = root_sr25519_pair.public().into();
println!("root SS58 Address: '{}'", root_ss58_address);
assert_eq!(
expected_root_ss58_address_string,
root_ss58_address.to_string()
);
// Using derive on root keypair to get Alice
let (alice_sr25519_pair, _seed) = root_sr25519_pair
.derive(
Some(sp_core::DeriveJunction::hard("Alice")).into_iter(),
None,
)
.unwrap();
let expected_alice_ss58_address_string =
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string();
let alice_ss58_address: AccountId = alice_sr25519_pair.public().into();
println!("Alice SS58 Address: '{}'", alice_ss58_address);
assert_eq!(
expected_alice_ss58_address_string,
alice_ss58_address.to_string()
);
// Using sp_core::sr25519::Pair::from_string(suri, None) to derive keypair from suri
let alice_suri = SUBSTRATE_MNEMONIC.to_owned() + "//Alice";
let alice_suri_sr25519_pair =
sp_core::sr25519::Pair::from_string(&alice_suri, None).unwrap();
let alice_suri_ss58_address: AccountId = alice_suri_sr25519_pair.public().into();
println!("Alice suri SS58 Address: '{}'", alice_suri_ss58_address);
assert_eq!(
expected_alice_ss58_address_string,
alice_suri_ss58_address.to_string()
);
}
}
mod cesium {
use super::*;
/// Test which verifies that it's possible to derive a key coming from a cesium v1 id & password
///
/// Using subkey command with **ed25519** scheme to show we can derive a key from a seed
/// and to retrieve expected values.
///
/// ##### Without derivation (using seed from the test)
/// ```
/// subkey inspect --scheme ed25519
/// URI:
/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
/// Network ID: substrate
/// Secret seed: 0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
/// Public key (hex): 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Account ID: 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// SS58 Address: 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// ```
///
/// ##### With derivation '//0' (using seed from the test)
/// ```
/// subkey inspect --scheme ed25519
/// URI:
/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0` is account:
/// Network ID: substrate
/// Secret seed: 0x916e95359a49c82e3d84269b90551433352c433eb9bb270fb8cb86e8a6c9ec85
/// Public key (hex): 0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
/// Account ID: 0x1658ce32f039dff26d83b5282f611cfed8c71296a311417ef737db4e016194de
/// Public key (SS58): 5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
/// SS58 Address: 5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf
/// ```
#[test]
fn test_cesium_v1_key_derivation() {
let cesium_id = "test_cesium_id".to_string();
let cesium_pwd = "test_cesium_pwd".to_string();
let expected_cesium_v1_ss58_address: String =
"5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4".to_string();
Nicolas80
committed
let seed = seed_from_cesium(&cesium_id, &cesium_pwd);
Nicolas80
committed
println!();
Nicolas80
committed
println!("seed: '0x{}'", hex::encode(seed));
Nicolas80
committed
let ed25519_pair_from_seed = sp_core::ed25519::Pair::from_seed(&seed);
println!();
println!(
Nicolas80
committed
"ed25519 keypair from seed: public:'0x{}' raw_vec:'0x{}'",
Nicolas80
committed
hex::encode(ed25519_pair_from_seed.public().0),
hex::encode(ed25519_pair_from_seed.to_raw_vec().as_slice())
);
Nicolas80
committed
println!(
"ed25519 keypair from seed: Cesium v1 Pubkey: '{}'",
bs58::encode(ed25519_pair_from_seed.public()).into_string()
);
Nicolas80
committed
let ed25519_address_from_seed: AccountId = ed25519_pair_from_seed.public().into();
println!(
Nicolas80
committed
"ed25519 keypair from seed: public SS58 Address:'{}'",
Nicolas80
committed
ed25519_address_from_seed
);
Nicolas80
committed
assert_eq!(
expected_cesium_v1_ss58_address,
ed25519_address_from_seed.to_string()
);
Nicolas80
committed
let root_suri = "0x".to_string() + &hex::encode(seed);
let ed25519_pair_from_suri =
sp_core::ed25519::Pair::from_string(&root_suri, None).unwrap();
let ed25519_address_from_suri: AccountId = ed25519_pair_from_suri.public().into();
println!(
Nicolas80
committed
"ed25519 keypair from suri: public SS58 Address:'{}'",
Nicolas80
committed
ed25519_address_from_suri
);
Nicolas80
committed
assert_eq!(
expected_cesium_v1_ss58_address,
ed25519_address_from_suri.to_string()
);
Nicolas80
committed
// Tested derivation manually with `subkey` command using adapted suri: `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84//0`
let expected_ss58_address_derivation_0 =
"5Ca1HrNxQ4hiekd92Z99fzhfdSAqPy2rUkLBmwLsgLCjeSQf".to_string();
// Using derive on the ed25519 keypair
let (derived_ed25519_pair, _seed) = ed25519_pair_from_seed
.derive(Some(sp_core::DeriveJunction::hard(0)).into_iter(), None)
.unwrap();
println!();
println!(
"derived ed25519 keypair: public:'0x{}' raw_vec:'0x{}'",
hex::encode(derived_ed25519_pair.public().0),
hex::encode(derived_ed25519_pair.to_raw_vec().as_slice())
);
Nicolas80
committed
println!(
"derived ed25519 keypair: Cesium v1 Pubkey: '{}'",
bs58::encode(derived_ed25519_pair.public()).into_string()
);
Nicolas80
committed
let derived_ed25519_address =
subxt::utils::AccountId32::from(derived_ed25519_pair.public());
println!(
"derived ed25519 keypair: public SS58 Address:'{}'",
derived_ed25519_address
);
assert_eq!(
expected_ss58_address_derivation_0,
derived_ed25519_address.to_string()
);
// Using sp_core::ed25519::Pair::from_string(suri, None) to derive keypair from suri
let deriv_0_suri = root_suri.clone() + "//0";
let derived_ed25519_pair_from_suri =
sp_core::ed25519::Pair::from_string(&deriv_0_suri, None).unwrap();
let derived_ed25519_address_from_suri: AccountId =
derived_ed25519_pair_from_suri.public().into();
println!(
Nicolas80
committed
"derived ed25519 keypair from suri: public SS58 Address:'{}'",
Nicolas80
committed
derived_ed25519_address_from_suri
);
assert_eq!(
expected_ss58_address_derivation_0,
derived_ed25519_address_from_suri.to_string()
);
}
/// Test which verifies that it's possible to directly use a cesium v1 seed retrieved using scryt
/// as seed or suri to instantiate an ed25519 keypair - keeping the same resulting SS58 Address as when using nacl::sign::Keypair
///
/// Using subkey command with **ed25519** scheme to show the seed/suri and associated SS58 Address
///
/// ```
/// subkey inspect --scheme ed25519
/// URI:
/// Secret Key URI `0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84` is account:
/// Network ID: substrate
/// Secret seed: 0x2101d2bc68de9ad149c06293bfe489c8608de576c88927aa5439a81be17aae84
/// Public key (hex): 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Account ID: 0x697f6bd16ddebf142384e503fd3f3efc39fe5c7be7c693bd98d982403bb6eb74
/// Public key (SS58): 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// SS58 Address: 5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4
/// ```
#[test]
fn test_cesium_v1_seed_using_scrypt() {
let cesium_id = "test_cesium_id".to_string();
let cesium_pwd = "test_cesium_pwd".to_string();
let expected_cesium_v1_ss58_address: String =
"5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4".to_string();
// retrieving seed using scrypt
let seed = seed_from_cesium(&cesium_id, &cesium_pwd);
println!("seed value from scrypt: '0x{}'", hex::encode(&seed));
let ed25519_pair_from_seed = sp_core::ed25519::Pair::from_seed(&seed);
println!();
println!(
"ed25519 keypair from seed : public:'0x{}' raw_vec:'0x{}'",
hex::encode(ed25519_pair_from_seed.public().0),
hex::encode(ed25519_pair_from_seed.to_raw_vec().as_slice())
);
let ed25519_address_from_seed: AccountId = ed25519_pair_from_seed.public().into();
println!(
"ed25519 keypair from seed : public SS58 Address:'{}'",
ed25519_address_from_seed
);
assert_eq!(
expected_cesium_v1_ss58_address,
ed25519_address_from_seed.to_string()
);
let root_suri = "0x".to_string() + &hex::encode(seed);
let ed25519_pair_from_suri =
sp_core::ed25519::Pair::from_string(&root_suri, None).unwrap();
let ed25519_address_from_suri: AccountId = ed25519_pair_from_suri.public().into();
println!(
"ed25519 keypair from suri : public SS58 Address:'{}'",
ed25519_address_from_suri
);
assert_eq!(
expected_cesium_v1_ss58_address,
ed25519_address_from_suri.to_string()
);
}