Implement Bip32 for seed-phrase/passphrase signing (#16942)
* Add Keypair helpers for bip32 derivation * Plumb bip32 for SignerSourceKind::Ask * Support full-path querystring * Use as_ref * Add public wrappers for from_uri cases * Support master root derivations (and fix too-deep print * Add ask:// HD documentation * Update ASK elsewhere in docs
This commit is contained in:
@ -44,6 +44,12 @@ impl TryFrom<&str> for DerivationPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[ChildIndex]> for DerivationPath {
|
||||
fn as_ref(&self) -> &[ChildIndex] {
|
||||
&self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerivationPath {
|
||||
fn new<P: Into<Box<[ChildIndex]>>>(path: P) -> Self {
|
||||
Self(DerivationPathInner::new(path))
|
||||
@ -54,8 +60,12 @@ impl DerivationPath {
|
||||
}
|
||||
|
||||
fn from_key_str_with_coin<T: Bip44>(path: &str, coin: T) -> Result<Self, DerivationPathError> {
|
||||
let path = format!("m/{}", path);
|
||||
let extend = DerivationPathInner::from_str(&path)
|
||||
let master_path = if path == "m" {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("m/{}", path)
|
||||
};
|
||||
let extend = DerivationPathInner::from_str(&master_path)
|
||||
.map_err(|err| DerivationPathError::InvalidDerivationPath(err.to_string()))?;
|
||||
let mut extend = extend.into_iter();
|
||||
let account = extend.next().map(|index| index.to_u32());
|
||||
@ -69,7 +79,7 @@ impl DerivationPath {
|
||||
Ok(Self::new_bip44_with_coin(coin, account, change))
|
||||
}
|
||||
|
||||
fn _from_absolute_path_str(path: &str) -> Result<Self, DerivationPathError> {
|
||||
fn from_absolute_path_str(path: &str) -> Result<Self, DerivationPathError> {
|
||||
let inner = DerivationPath::_from_absolute_path_insecure_str(path)?
|
||||
.into_iter()
|
||||
.map(|c| ChildIndex::Hardened(c.to_u32()))
|
||||
@ -123,8 +133,18 @@ impl DerivationPath {
|
||||
}
|
||||
}
|
||||
|
||||
// Only accepts single query string pair of type `key`
|
||||
pub fn from_uri(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
|
||||
pub fn from_uri_key_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
|
||||
Self::from_uri(uri, true)
|
||||
}
|
||||
|
||||
pub fn from_uri_any_query(uri: &URIReference<'_>) -> Result<Option<Self>, DerivationPathError> {
|
||||
Self::from_uri(uri, false)
|
||||
}
|
||||
|
||||
fn from_uri(
|
||||
uri: &URIReference<'_>,
|
||||
key_only: bool,
|
||||
) -> Result<Option<Self>, DerivationPathError> {
|
||||
if let Some(query) = uri.query() {
|
||||
let query_str = query.as_str();
|
||||
if query_str.is_empty() {
|
||||
@ -136,16 +156,26 @@ impl DerivationPath {
|
||||
"invalid query string, extra fields not supported".to_string(),
|
||||
));
|
||||
}
|
||||
let key = query.get("key");
|
||||
if key.is_none() {
|
||||
let key = query.get(QueryKey::Key.as_ref());
|
||||
if let Some(key) = key {
|
||||
// Use from_key_str instead of TryInto here to make it more explicit that this
|
||||
// generates a Solana bip44 DerivationPath
|
||||
return Self::from_key_str(key).map(Some);
|
||||
}
|
||||
if key_only {
|
||||
return Err(DerivationPathError::InvalidDerivationPath(format!(
|
||||
"invalid query string `{}`, only `key` supported",
|
||||
query_str,
|
||||
)));
|
||||
}
|
||||
// Use from_key_str instead of TryInto here to make it a little more explicit that this
|
||||
// generates a Solana bip44 DerivationPath
|
||||
key.map(Self::from_key_str).transpose()
|
||||
let full_path = query.get(QueryKey::FullPath.as_ref());
|
||||
if let Some(full_path) = full_path {
|
||||
return Self::from_absolute_path_str(full_path).map(Some);
|
||||
}
|
||||
Err(DerivationPathError::InvalidDerivationPath(format!(
|
||||
"invalid query string `{}`, only `key` and `full-path` supported",
|
||||
query_str,
|
||||
)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@ -170,6 +200,46 @@ impl<'a> IntoIterator for &'a DerivationPath {
|
||||
}
|
||||
}
|
||||
|
||||
const QUERY_KEY_FULL_PATH: &str = "full-path";
|
||||
const QUERY_KEY_KEY: &str = "key";
|
||||
|
||||
#[derive(Clone, Debug, Error, PartialEq)]
|
||||
#[error("invalid query key `{0}`")]
|
||||
struct QueryKeyError(String);
|
||||
|
||||
enum QueryKey {
|
||||
FullPath,
|
||||
Key,
|
||||
}
|
||||
|
||||
impl FromStr for QueryKey {
|
||||
type Err = QueryKeyError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let lowercase = s.to_ascii_lowercase();
|
||||
match lowercase.as_str() {
|
||||
QUERY_KEY_FULL_PATH => Ok(Self::FullPath),
|
||||
QUERY_KEY_KEY => Ok(Self::Key),
|
||||
_ => Err(QueryKeyError(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for QueryKey {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::FullPath => QUERY_KEY_FULL_PATH,
|
||||
Self::Key => QUERY_KEY_KEY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for QueryKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s: &str = self.as_ref();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
trait Bip44 {
|
||||
const PURPOSE: u32 = 44;
|
||||
const COIN: u32;
|
||||
@ -240,41 +310,41 @@ mod tests {
|
||||
fn test_from_absolute_path_str() {
|
||||
let s = "m/44/501";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::default()
|
||||
);
|
||||
let s = "m/44'/501'";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::default()
|
||||
);
|
||||
let s = "m/44'/501'/1/2";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new_bip44(Some(1), Some(2))
|
||||
);
|
||||
let s = "m/44'/501'/1'/2'";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new_bip44(Some(1), Some(2))
|
||||
);
|
||||
|
||||
// Test non-Solana Bip44
|
||||
let s = "m/44'/999'/1/2";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
||||
);
|
||||
let s = "m/44'/999'/1'/2'";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new_bip44_with_coin(TestCoin, Some(1), Some(2))
|
||||
);
|
||||
|
||||
// Test non-bip44 paths
|
||||
let s = "m/501'/0'/0/0";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new(vec![
|
||||
ChildIndex::Hardened(501),
|
||||
ChildIndex::Hardened(0),
|
||||
@ -284,7 +354,7 @@ mod tests {
|
||||
);
|
||||
let s = "m/501'/0'/0'/0'";
|
||||
assert_eq!(
|
||||
DerivationPath::_from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::from_absolute_path_str(s).unwrap(),
|
||||
DerivationPath::new(vec![
|
||||
ChildIndex::Hardened(501),
|
||||
ChildIndex::Hardened(0),
|
||||
@ -295,7 +365,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_from_uri() {
|
||||
fn test_from_uri() {
|
||||
let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
|
||||
|
||||
// test://path?key=0/0
|
||||
@ -311,7 +381,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri).unwrap(),
|
||||
DerivationPath::from_uri(&uri, true).unwrap(),
|
||||
Some(derivation_path.clone())
|
||||
);
|
||||
|
||||
@ -328,7 +398,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri).unwrap(),
|
||||
DerivationPath::from_uri(&uri, true).unwrap(),
|
||||
Some(derivation_path.clone())
|
||||
);
|
||||
|
||||
@ -345,10 +415,27 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri).unwrap(),
|
||||
DerivationPath::from_uri(&uri, true).unwrap(),
|
||||
Some(derivation_path)
|
||||
);
|
||||
|
||||
// test://path?key=m
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("key=m"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri, true).unwrap(),
|
||||
Some(DerivationPath::new_bip44(None, None))
|
||||
);
|
||||
|
||||
// test://path
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
@ -359,7 +446,7 @@ mod tests {
|
||||
.try_path("")
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(DerivationPath::from_uri(&uri).unwrap(), None);
|
||||
assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
|
||||
|
||||
// test://path?
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
@ -373,7 +460,7 @@ mod tests {
|
||||
.try_query(Some(""))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(DerivationPath::from_uri(&uri).unwrap(), None);
|
||||
assert_eq!(DerivationPath::from_uri(&uri, true).unwrap(), None);
|
||||
|
||||
// test://path?key=0/0/0
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
@ -388,7 +475,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
@ -405,7 +492,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
@ -422,7 +509,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
@ -439,7 +526,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
@ -456,7 +543,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
@ -473,7 +560,182 @@ mod tests {
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri),
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_uri_full_path() {
|
||||
let derivation_path = DerivationPath::from_absolute_path_str("m/44'/999'/1'").unwrap();
|
||||
|
||||
// test://path?full-path=m/44/999/1
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m/44/999/1"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri, false).unwrap(),
|
||||
Some(derivation_path.clone())
|
||||
);
|
||||
|
||||
// test://path?full-path=m/44'/999'/1'
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m/44'/999'/1'"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri, false).unwrap(),
|
||||
Some(derivation_path.clone())
|
||||
);
|
||||
|
||||
// test://path?full-path=m/44\'/999\'/1\'
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m/44\'/999\'/1\'"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri, false).unwrap(),
|
||||
Some(derivation_path)
|
||||
);
|
||||
|
||||
// test://path?full-path=m
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert_eq!(
|
||||
DerivationPath::from_uri(&uri, false).unwrap(),
|
||||
Some(DerivationPath(DerivationPathInner::from_str("m").unwrap()))
|
||||
);
|
||||
|
||||
// test://path?full-path=m/44/999/1, only `key` supported
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m/44/999/1"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, true),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
// test://path?key=0/0&full-path=m/44/999/1
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("key=0/0&full-path=m/44/999/1"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, false),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
// test://path?full-path=m/44/999/1&bad-key=0/0
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=m/44/999/1&bad-key=0/0"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, false),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
// test://path?full-path=bad-value
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path=bad-value"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, false),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
// test://path?full-path=
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path="))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, false),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
|
||||
// test://path?full-path
|
||||
let mut builder = URIReferenceBuilder::new();
|
||||
builder
|
||||
.try_scheme(Some("test"))
|
||||
.unwrap()
|
||||
.try_authority(Some("path"))
|
||||
.unwrap()
|
||||
.try_path("")
|
||||
.unwrap()
|
||||
.try_query(Some("full-path"))
|
||||
.unwrap();
|
||||
let uri = builder.build().unwrap();
|
||||
assert!(matches!(
|
||||
DerivationPath::from_uri(&uri, false),
|
||||
Err(DerivationPathError::InvalidDerivationPath(_))
|
||||
));
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
//! The `signature` module provides functionality for public, and private keys.
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use crate::{pubkey::Pubkey, transaction::TransactionError};
|
||||
use crate::{derivation_path::DerivationPath, pubkey::Pubkey, transaction::TransactionError};
|
||||
use ed25519_dalek::Signer as DalekSigner;
|
||||
use ed25519_dalek_bip32::Error as Bip32Error;
|
||||
use generic_array::{typenum::U64, GenericArray};
|
||||
use hmac::Hmac;
|
||||
use itertools::Itertools;
|
||||
@ -396,10 +397,37 @@ pub fn keypair_from_seed(seed: &[u8]) -> Result<Keypair, Box<dyn error::Error>>
|
||||
Ok(Keypair(dalek_keypair))
|
||||
}
|
||||
|
||||
pub fn keypair_from_seed_phrase_and_passphrase(
|
||||
/// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided;
|
||||
/// otherwise builds standard Keypair using the seed as SecretKey
|
||||
pub fn keypair_from_seed_and_derivation_path(
|
||||
seed: &[u8],
|
||||
derivation_path: Option<DerivationPath>,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
if let Some(derivation_path) = derivation_path {
|
||||
bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into())
|
||||
} else {
|
||||
keypair_from_seed(seed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Keypair using Bip32 Hierarchical Derivation
|
||||
fn bip32_derived_keypair(
|
||||
seed: &[u8],
|
||||
derivation_path: DerivationPath,
|
||||
) -> Result<Keypair, Bip32Error> {
|
||||
let extended = ed25519_dalek_bip32::ExtendedSecretKey::from_seed(seed)
|
||||
.and_then(|extended| extended.derive(&derivation_path))?;
|
||||
let extended_public_key = extended.public_key();
|
||||
Ok(Keypair(ed25519_dalek::Keypair {
|
||||
secret: extended.secret_key,
|
||||
public: extended_public_key,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn generate_seed_from_seed_phrase_and_passphrase(
|
||||
seed_phrase: &str,
|
||||
passphrase: &str,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
) -> Vec<u8> {
|
||||
const PBKDF2_ROUNDS: u32 = 2048;
|
||||
const PBKDF2_BYTES: usize = 64;
|
||||
|
||||
@ -412,7 +440,17 @@ pub fn keypair_from_seed_phrase_and_passphrase(
|
||||
PBKDF2_ROUNDS,
|
||||
&mut seed,
|
||||
);
|
||||
keypair_from_seed(&seed[..])
|
||||
seed
|
||||
}
|
||||
|
||||
pub fn keypair_from_seed_phrase_and_passphrase(
|
||||
seed_phrase: &str,
|
||||
passphrase: &str,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
keypair_from_seed(&generate_seed_from_seed_phrase_and_passphrase(
|
||||
seed_phrase,
|
||||
passphrase,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Reference in New Issue
Block a user