Refactor SignerSource to expose DerivationPath to other kinds of signers (#16933)

* One use statement

* Add stdin uri scheme

* Convert parse_signer_source to return Result

* A-Z deps

* Convert Usb data to Locator

* Pull DerivationPath out of Locator

* Wrap SignerSource to share derivation_path

* Review comments

* Check Filepath existence, readability in parse_signer_source
This commit is contained in:
Tyera Eulberg
2021-04-29 01:42:21 -06:00
committed by GitHub
parent d640ac143b
commit d6f30b7537
15 changed files with 509 additions and 447 deletions

View File

@ -1,8 +1,5 @@
use {
solana_sdk::{
derivation_path::{DerivationPath, DerivationPathError},
pubkey::{ParsePubkeyError, Pubkey},
},
solana_sdk::pubkey::{ParsePubkeyError, Pubkey},
std::{
convert::{Infallible, TryFrom, TryInto},
str::FromStr,
@ -77,8 +74,6 @@ pub enum LocatorError {
#[error(transparent)]
PubkeyError(#[from] ParsePubkeyError),
#[error(transparent)]
DerivationPathError(#[from] DerivationPathError),
#[error(transparent)]
UriReferenceError(#[from] URIReferenceError),
#[error("unimplemented scheme")]
UnimplementedScheme,
@ -96,15 +91,12 @@ impl From<Infallible> for LocatorError {
pub struct Locator {
pub manufacturer: Manufacturer,
pub pubkey: Option<Pubkey>,
pub derivation_path: Option<DerivationPath>,
}
impl std::fmt::Display for Locator {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let maybe_path = self.pubkey.map(|p| p.to_string());
let path = maybe_path.as_deref().unwrap_or("/");
let maybe_query = self.derivation_path.as_ref().map(|d| d.get_query());
let maybe_query2 = maybe_query.as_ref().map(|q| &q[1..]);
let mut builder = URIReferenceBuilder::new();
builder
@ -113,8 +105,6 @@ impl std::fmt::Display for Locator {
.try_authority(Some(self.manufacturer.as_ref()))
.unwrap()
.try_path(path)
.unwrap()
.try_query(maybe_query2)
.unwrap();
let uri = builder.build().unwrap();
@ -141,28 +131,7 @@ impl Locator {
None
}
});
let key = if let Some(query) = uri.query() {
let query_str = query.as_str();
let query = qstring::QString::from(query_str);
if query.len() > 1 {
return Err(DerivationPathError::InvalidDerivationPath(
"invalid query string, extra fields not supported".to_string(),
)
.into());
}
let key = query.get("key");
if key.is_none() {
return Err(DerivationPathError::InvalidDerivationPath(format!(
"invalid query string `{}`, only `key` supported",
query_str,
))
.into());
}
key.map(|v| v.to_string())
} else {
None
};
Self::new_from_parts(host.as_str(), path, key.as_deref())
Self::new_from_parts(host.as_str(), path)
}
(Some(_scheme), Some(_host)) => Err(LocatorError::UnimplementedScheme),
(None, Some(_host)) => Err(LocatorError::UnimplementedScheme),
@ -170,18 +139,15 @@ impl Locator {
}
}
pub fn new_from_parts<V, VE, P, PE, D, DE>(
pub fn new_from_parts<V, VE, P, PE>(
manufacturer: V,
pubkey: Option<P>,
derivation_path: Option<D>,
) -> Result<Self, LocatorError>
where
VE: Into<LocatorError>,
V: TryInto<Manufacturer, Error = VE>,
PE: Into<LocatorError>,
P: TryInto<Pubkey, Error = PE>,
DE: Into<LocatorError>,
D: TryInto<DerivationPath, Error = DE>,
{
let manufacturer = manufacturer.try_into().map_err(|e| e.into())?;
let pubkey = if let Some(pubkey) = pubkey {
@ -189,15 +155,9 @@ impl Locator {
} else {
None
};
let derivation_path = if let Some(derivation_path) = derivation_path {
Some(derivation_path.try_into().map_err(|e| e.into())?)
} else {
None
};
Ok(Self {
manufacturer,
pubkey,
derivation_path,
})
}
}
@ -225,76 +185,50 @@ mod tests {
let manufacturer_str = "ledger";
let pubkey = Pubkey::new_unique();
let pubkey_str = pubkey.to_string();
let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
let derivation_path_str = "0/0";
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: None,
};
assert!(matches!(
Locator::new_from_parts(manufacturer, None::<Pubkey>, None::<DerivationPath>),
Locator::new_from_parts(manufacturer, None::<Pubkey>),
Ok(e) if e == expect,
));
assert!(matches!(
Locator::new_from_parts(manufacturer_str, None::<Pubkey>, None::<DerivationPath>),
Locator::new_from_parts(manufacturer_str, None::<Pubkey>),
Ok(e) if e == expect,
));
let expect = Locator {
manufacturer,
pubkey: Some(pubkey),
derivation_path: None,
};
assert!(matches!(
Locator::new_from_parts(manufacturer, Some(pubkey), None::<DerivationPath>),
Locator::new_from_parts(manufacturer, Some(pubkey)),
Ok(e) if e == expect,
));
assert!(matches!(
Locator::new_from_parts(manufacturer_str, Some(pubkey_str.as_str()), None::<DerivationPath>),
Ok(e) if e == expect,
));
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path.clone()),
};
assert!(matches!(
Locator::new_from_parts(manufacturer, None::<Pubkey>, Some(derivation_path)),
Ok(e) if e == expect,
));
assert!(matches!(
Locator::new_from_parts(manufacturer, None::<Pubkey>, Some(derivation_path_str)),
Locator::new_from_parts(manufacturer_str, Some(pubkey_str.as_str())),
Ok(e) if e == expect,
));
assert!(matches!(
Locator::new_from_parts("bad-manufacturer", None::<Pubkey>, None::<DerivationPath>),
Locator::new_from_parts("bad-manufacturer", None::<Pubkey>),
Err(LocatorError::ManufacturerError(e)) if e == ManufacturerError,
));
assert!(matches!(
Locator::new_from_parts(manufacturer, Some("bad-pubkey"), None::<DerivationPath>),
Locator::new_from_parts(manufacturer, Some("bad-pubkey")),
Err(LocatorError::PubkeyError(e)) if e == ParsePubkeyError::Invalid,
));
let bad_path = "bad-derivation-path".to_string();
assert!(matches!(
Locator::new_from_parts(manufacturer, None::<Pubkey>, Some(bad_path.as_str())),
Err(LocatorError::DerivationPathError(
DerivationPathError::InvalidDerivationPath(_)
)),
));
}
#[test]
fn test_locator_new_from_uri() {
let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
let manufacturer = Manufacturer::Ledger;
let pubkey = Pubkey::new_unique();
let pubkey_str = pubkey.to_string();
// usb://ledger/{PUBKEY}?key=0'/0'
// usb://ledger/{PUBKEY}?key=0/0
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
@ -309,7 +243,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: Some(pubkey),
derivation_path: Some(derivation_path.clone()),
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
@ -326,7 +259,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: Some(pubkey),
derivation_path: None,
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
@ -343,7 +275,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: None,
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
@ -360,64 +291,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: None,
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
// usb://ledger?key=0/0
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("key=0/0"))
.unwrap();
let uri = builder.build().unwrap();
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path.clone()),
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
// usb://ledger?key=0'/0'
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("key=0'/0'"))
.unwrap();
let uri = builder.build().unwrap();
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path.clone()),
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
// usb://ledger/?key=0/0
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("/")
.unwrap()
.try_query(Some("key=0/0"))
.unwrap();
let uri = builder.build().unwrap();
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path),
};
assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
@ -465,79 +338,10 @@ mod tests {
Locator::new_from_uri(&uri),
Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid))
);
// usb://ledger?bad-key=0/0
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("bad-key=0/0"))
.unwrap();
let uri = builder.build().unwrap();
assert!(matches!(
Locator::new_from_uri(&uri),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key=bad-value
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("key=bad-value"))
.unwrap();
let uri = builder.build().unwrap();
assert!(matches!(
Locator::new_from_uri(&uri),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key=
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("key="))
.unwrap();
let uri = builder.build().unwrap();
assert!(matches!(
Locator::new_from_uri(&uri),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key
let mut builder = URIReferenceBuilder::new();
builder
.try_scheme(Some("usb"))
.unwrap()
.try_authority(Some(Manufacturer::Ledger.as_ref()))
.unwrap()
.try_path("")
.unwrap()
.try_query(Some("key"))
.unwrap();
let uri = builder.build().unwrap();
assert!(matches!(
Locator::new_from_uri(&uri),
Err(LocatorError::DerivationPathError(_))
));
}
#[test]
fn test_locator_new_from_path() {
let derivation_path = DerivationPath::new_bip44(Some(0), Some(0));
let manufacturer = Manufacturer::Ledger;
let pubkey = Pubkey::new_unique();
let path = format!("usb://ledger/{}?key=0/0", pubkey);
@ -548,7 +352,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: Some(pubkey),
derivation_path: Some(derivation_path.clone()),
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
@ -557,7 +360,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: Some(pubkey),
derivation_path: None,
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
@ -566,7 +368,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: None,
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
@ -575,25 +376,6 @@ mod tests {
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: None,
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
// usb://ledger?key=0'/0'
let path = "usb://ledger?key=0'/0'";
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path.clone()),
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
// usb://ledger/?key=0'/0'
let path = "usb://ledger?key=0'/0'";
let expect = Locator {
manufacturer,
pubkey: None,
derivation_path: Some(derivation_path),
};
assert_eq!(Locator::new_from_path(path), Ok(expect));
@ -617,40 +399,5 @@ mod tests {
Locator::new_from_path(path),
Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid))
);
// usb://ledger?bad-key=0/0
let path = "usb://ledger?bad-key=0/0";
assert!(matches!(
Locator::new_from_path(path),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?bad-key=0'/0'
let path = "usb://ledger?bad-key=0'/0'";
assert!(matches!(
Locator::new_from_path(path),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key=bad-value
let path = format!("usb://ledger/{}?key=bad-value", pubkey);
assert!(matches!(
Locator::new_from_path(path),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key=
let path = format!("usb://ledger/{}?key=", pubkey);
assert!(matches!(
Locator::new_from_path(path),
Err(LocatorError::DerivationPathError(_))
));
// usb://ledger?key
let path = format!("usb://ledger/{}?key", pubkey);
assert!(matches!(
Locator::new_from_path(path),
Err(LocatorError::DerivationPathError(_))
));
}
}

View File

@ -1,7 +1,7 @@
use {
crate::{
ledger::get_ledger_from_info,
locator::Manufacturer,
locator::{Locator, Manufacturer},
remote_wallet::{
RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager,
RemoteWalletType,
@ -56,12 +56,13 @@ impl Signer for RemoteKeypair {
}
pub fn generate_remote_keypair(
path: String,
locator: Locator,
derivation_path: DerivationPath,
wallet_manager: &RemoteWalletManager,
confirm_key: bool,
keypair_name: &str,
) -> Result<RemoteKeypair, RemoteWalletError> {
let (remote_wallet_info, derivation_path) = RemoteWalletInfo::parse_path(path)?;
let remote_wallet_info = RemoteWalletInfo::parse_locator(locator);
if remote_wallet_info.manufacturer == Manufacturer::Ledger {
let ledger = get_ledger_from_info(remote_wallet_info, keypair_name, wallet_manager)?;
let path = format!("{}{}", ledger.pretty_path, derivation_path.get_query());

View File

@ -253,20 +253,12 @@ pub struct RemoteWalletInfo {
}
impl RemoteWalletInfo {
pub fn parse_path(path: String) -> Result<(Self, DerivationPath), RemoteWalletError> {
let Locator {
manufacturer,
pubkey,
derivation_path,
} = Locator::new_from_path(path)?;
Ok((
RemoteWalletInfo {
manufacturer,
pubkey: pubkey.unwrap_or_default(),
..RemoteWalletInfo::default()
},
derivation_path.unwrap_or_default(),
))
pub fn parse_locator(locator: Locator) -> Self {
RemoteWalletInfo {
manufacturer: locator.manufacturer,
pubkey: locator.pubkey.unwrap_or_default(),
..RemoteWalletInfo::default()
}
}
pub fn get_pretty_path(&self) -> String {
@ -308,10 +300,13 @@ mod tests {
use super::*;
#[test]
fn test_parse_path() {
fn test_parse_locator() {
let pubkey = solana_sdk::pubkey::new_rand();
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path(format!("usb://ledger/{:?}?key=1/2", pubkey)).unwrap();
let locator = Locator {
manufacturer: Manufacturer::Ledger,
pubkey: Some(pubkey),
};
let wallet_info = RemoteWalletInfo::parse_locator(locator);
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: Manufacturer::Ledger,
@ -320,33 +315,13 @@ mod tests {
pubkey,
error: None,
}));
assert_eq!(derivation_path, DerivationPath::new_bip44(Some(1), Some(2)));
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path(format!("usb://ledger/{:?}?key=1'/2'", pubkey)).unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: Manufacturer::Ledger,
serial: "".to_string(),
host_device_path: "/host/device/path".to_string(),
pubkey,
error: None,
}));
assert_eq!(derivation_path, DerivationPath::new_bip44(Some(1), Some(2)));
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path(format!("usb://ledger/{:?}?key=1\'/2\'", pubkey)).unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: Manufacturer::Ledger,
serial: "".to_string(),
host_device_path: "/host/device/path".to_string(),
pubkey,
error: None,
}));
assert_eq!(derivation_path, DerivationPath::new_bip44(Some(1), Some(2)));
// Test that wallet id need not be complete for key derivation to work
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path("usb://ledger?key=1".to_string()).unwrap();
// Test that pubkey need not be populated
let locator = Locator {
manufacturer: Manufacturer::Ledger,
pubkey: None,
};
let wallet_info = RemoteWalletInfo::parse_locator(locator);
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "nano-s".to_string(),
manufacturer: Manufacturer::Ledger,
@ -355,33 +330,6 @@ mod tests {
pubkey: Pubkey::default(),
error: None,
}));
assert_eq!(derivation_path, DerivationPath::new_bip44(Some(1), None));
let (wallet_info, derivation_path) =
RemoteWalletInfo::parse_path("usb://ledger/?key=1/2".to_string()).unwrap();
assert!(wallet_info.matches(&RemoteWalletInfo {
model: "".to_string(),
manufacturer: Manufacturer::Ledger,
serial: "".to_string(),
host_device_path: "/host/device/path".to_string(),
pubkey: Pubkey::default(),
error: None,
}));
assert_eq!(derivation_path, DerivationPath::new_bip44(Some(1), Some(2)));
// Failure cases
assert!(
RemoteWalletInfo::parse_path("usb://ledger/bad-pubkey?key=1/2".to_string()).is_err()
);
assert!(RemoteWalletInfo::parse_path("usb://?key=1/2".to_string()).is_err());
assert!(RemoteWalletInfo::parse_path("usb:/ledger?key=1/2".to_string()).is_err());
assert!(RemoteWalletInfo::parse_path("ledger?key=1/2".to_string()).is_err());
assert!(RemoteWalletInfo::parse_path("usb://ledger?key=1/2/3".to_string()).is_err());
// Other query strings cause an error
assert!(
RemoteWalletInfo::parse_path("usb://ledger/?key=1/2&test=other".to_string()).is_err()
);
assert!(RemoteWalletInfo::parse_path("usb://ledger/?Key=1/2".to_string()).is_err());
assert!(RemoteWalletInfo::parse_path("usb://ledger/?test=other".to_string()).is_err());
}
#[test]