From 21d41b976b8c0505d78bfed0e0473a694cff8e8f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 11 May 2021 20:44:54 +0000 Subject: [PATCH] Move `Signer` types out of the `signature` module (backport #17099) (#17177) * sdk: Move `Signer` trait to own module (cherry picked from commit af6f3d776eb55f5222e34d191ab8e9310c43f7fb) * sdk: Move `Keypair` to `signer` module (cherry picked from commit 0eba6eb401548b1d37ede9898d6acf3ce034bb02) * sdk: Move `Presigner` to `signer` module (cherry picked from commit 12bf6c06c3616ed521bd81469ff4446f057b4186) * sdk: Move `NullSigner` to `signer` module (cherry picked from commit b71e4bdc61403e5ba7cb39853f021ef34a0e290a) * sdk: Move `signers` module into `signer` module (cherry picked from commit 967840aed6eb5a44fb150ebf11a36124ffd08575) * sdk: keypair - drop superfluous iter() (cherry picked from commit dbac38702acfce213df12e875ab8527e07637269) Co-authored-by: Trent Nelson --- sdk/src/lib.rs | 4 +- sdk/src/signature.rs | 491 +------------------------------- sdk/src/signer/keypair.rs | 326 +++++++++++++++++++++ sdk/src/signer/mod.rs | 117 ++++++++ sdk/src/signer/null_signer.rs | 40 +++ sdk/src/signer/presigner.rs | 88 ++++++ sdk/src/{ => signer}/signers.rs | 1 + 7 files changed, 579 insertions(+), 488 deletions(-) create mode 100644 sdk/src/signer/keypair.rs create mode 100644 sdk/src/signer/mod.rs create mode 100644 sdk/src/signer/null_signer.rs create mode 100644 sdk/src/signer/presigner.rs rename sdk/src/{ => signer}/signers.rs (98%) diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 64f89324ee..16d531e541 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -5,6 +5,8 @@ // Allows macro expansion of `use ::solana_sdk::*` to work within this crate extern crate self as solana_sdk; +#[cfg(feature = "full")] +pub use signer::signers; pub use solana_program::*; pub mod account; @@ -40,7 +42,7 @@ pub mod rpc_port; pub mod secp256k1_instruction; pub mod shred_version; pub mod signature; -pub mod signers; +pub mod signer; pub mod stake_weighted_timestamp; pub mod system_transaction; pub mod timing; diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 8102b9582a..725e6ffd88 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -1,63 +1,18 @@ //! The `signature` module provides functionality for public, and private keys. #![cfg(feature = "full")] -use crate::{derivation_path::DerivationPath, pubkey::Pubkey, transaction::TransactionError}; -use ed25519_dalek::Signer as DalekSigner; -use ed25519_dalek_bip32::Error as Bip32Error; +use crate::pubkey::Pubkey; use generic_array::{typenum::U64, GenericArray}; -use hmac::Hmac; -use itertools::Itertools; -use rand::{rngs::OsRng, CryptoRng, RngCore}; use std::{ borrow::{Borrow, Cow}, convert::TryInto, - error, fmt, - fs::{self, File, OpenOptions}, - io::{Read, Write}, - mem, - path::Path, + fmt, mem, str::FromStr, }; use thiserror::Error; -#[derive(Debug)] -pub struct Keypair(ed25519_dalek::Keypair); - -impl Keypair { - pub fn generate(csprng: &mut R) -> Self - where - R: CryptoRng + RngCore, - { - Self(ed25519_dalek::Keypair::generate(csprng)) - } - - /// Return a new ED25519 keypair - pub fn new() -> Self { - let mut rng = OsRng::default(); - Self::generate(&mut rng) - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - ed25519_dalek::Keypair::from_bytes(bytes).map(Self) - } - - pub fn to_bytes(&self) -> [u8; 64] { - self.0.to_bytes() - } - - pub fn from_base58_string(s: &str) -> Self { - Self::from_bytes(&bs58::decode(s).into_vec().unwrap()).unwrap() - } - - pub fn to_base58_string(&self) -> String { - // Remove .iter() once we're rust 1.47+ - bs58::encode(&self.0.to_bytes().iter()).into_string() - } - - pub fn secret(&self) -> &ed25519_dalek::SecretKey { - &self.0.secret - } -} +// legacy module paths +pub use crate::signer::{keypair::*, null_signer::*, presigner::*, *}; /// Number of bytes in a signature pub const SIGNATURE_BYTES: usize = 64; @@ -158,380 +113,9 @@ impl FromStr for Signature { } } -pub trait Signer { - fn pubkey(&self) -> Pubkey { - self.try_pubkey().unwrap_or_default() - } - fn try_pubkey(&self) -> Result; - fn sign_message(&self, message: &[u8]) -> Signature { - self.try_sign_message(message).unwrap_or_default() - } - fn try_sign_message(&self, message: &[u8]) -> Result; -} - -impl PartialEq for dyn Signer { - fn eq(&self, other: &dyn Signer) -> bool { - self.pubkey() == other.pubkey() - } -} - -impl std::fmt::Debug for dyn Signer { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(fmt, "Signer: {:?}", self.pubkey()) - } -} - -/// Remove duplicates signers while preserving order. O(n²) -pub fn unique_signers(signers: Vec<&dyn Signer>) -> Vec<&dyn Signer> { - signers.into_iter().unique_by(|s| s.pubkey()).collect() -} - -impl Signer for Keypair { - /// Return the public key for the given keypair - fn pubkey(&self) -> Pubkey { - Pubkey::new(self.0.public.as_ref()) - } - - fn try_pubkey(&self) -> Result { - Ok(self.pubkey()) - } - - fn sign_message(&self, message: &[u8]) -> Signature { - Signature::new(&self.0.sign(message).to_bytes()) - } - - fn try_sign_message(&self, message: &[u8]) -> Result { - Ok(self.sign_message(message)) - } -} - -impl PartialEq for Keypair -where - T: Signer, -{ - fn eq(&self, other: &T) -> bool { - self.pubkey() == other.pubkey() - } -} - -impl From for Box -where - T: Signer + 'static, -{ - fn from(signer: T) -> Self { - Box::new(signer) - } -} - -#[derive(Debug, Error, PartialEq)] -pub enum SignerError { - #[error("keypair-pubkey mismatch")] - KeypairPubkeyMismatch, - - #[error("not enough signers")] - NotEnoughSigners, - - #[error("transaction error")] - TransactionError(#[from] TransactionError), - - #[error("custom error: {0}")] - Custom(String), - - // Presigner-specific Errors - #[error("presigner error")] - PresignerError(#[from] PresignerError), - - // Remote Keypair-specific Errors - #[error("connection error: {0}")] - Connection(String), - - #[error("invalid input: {0}")] - InvalidInput(String), - - #[error("no device found")] - NoDeviceFound, - - #[error("{0}")] - Protocol(String), - - #[error("{0}")] - UserCancel(String), -} - -#[derive(Clone, Debug, Default)] -pub struct Presigner { - pubkey: Pubkey, - signature: Signature, -} - -impl Presigner { - pub fn new(pubkey: &Pubkey, signature: &Signature) -> Self { - Self { - pubkey: *pubkey, - signature: *signature, - } - } -} - -#[derive(Debug, Error, PartialEq)] -pub enum PresignerError { - #[error("pre-generated signature cannot verify data")] - VerificationFailure, -} - -impl Signer for Presigner { - fn try_pubkey(&self) -> Result { - Ok(self.pubkey) - } - - fn try_sign_message(&self, message: &[u8]) -> Result { - if self.signature.verify(self.pubkey.as_ref(), message) { - Ok(self.signature) - } else { - Err(PresignerError::VerificationFailure.into()) - } - } -} - -impl PartialEq for Presigner -where - T: Signer, -{ - fn eq(&self, other: &T) -> bool { - self.pubkey() == other.pubkey() - } -} - -/// NullSigner - A `Signer` implementation that always produces `Signature::default()`. -/// Used as a placeholder for absentee signers whose 'Pubkey` is required to construct -/// the transaction -#[derive(Clone, Debug, Default)] -pub struct NullSigner { - pubkey: Pubkey, -} - -impl NullSigner { - pub fn new(pubkey: &Pubkey) -> Self { - Self { pubkey: *pubkey } - } -} - -impl Signer for NullSigner { - fn try_pubkey(&self) -> Result { - Ok(self.pubkey) - } - - fn try_sign_message(&self, _message: &[u8]) -> Result { - Ok(Signature::default()) - } -} - -impl PartialEq for NullSigner -where - T: Signer, -{ - fn eq(&self, other: &T) -> bool { - self.pubkey == other.pubkey() - } -} - -pub fn read_keypair(reader: &mut R) -> Result> { - let bytes: Vec = serde_json::from_reader(reader)?; - let dalek_keypair = ed25519_dalek::Keypair::from_bytes(&bytes) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - Ok(Keypair(dalek_keypair)) -} - -pub fn read_keypair_file>(path: F) -> Result> { - let mut file = File::open(path.as_ref())?; - read_keypair(&mut file) -} - -pub fn write_keypair( - keypair: &Keypair, - writer: &mut W, -) -> Result> { - let keypair_bytes = keypair.0.to_bytes(); - let serialized = serde_json::to_string(&keypair_bytes.to_vec())?; - writer.write_all(&serialized.clone().into_bytes())?; - Ok(serialized) -} - -pub fn write_keypair_file>( - keypair: &Keypair, - outfile: F, -) -> Result> { - let outfile = outfile.as_ref(); - - if let Some(outdir) = outfile.parent() { - fs::create_dir_all(outdir)?; - } - - let mut f = { - #[cfg(not(unix))] - { - OpenOptions::new() - } - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - OpenOptions::new().mode(0o600) - } - } - .write(true) - .truncate(true) - .create(true) - .open(outfile)?; - - write_keypair(keypair, &mut f) -} - -pub fn keypair_from_seed(seed: &[u8]) -> Result> { - if seed.len() < ed25519_dalek::SECRET_KEY_LENGTH { - return Err("Seed is too short".into()); - } - let secret = ed25519_dalek::SecretKey::from_bytes(&seed[..ed25519_dalek::SECRET_KEY_LENGTH]) - .map_err(|e| e.to_string())?; - let public = ed25519_dalek::PublicKey::from(&secret); - let dalek_keypair = ed25519_dalek::Keypair { secret, public }; - Ok(Keypair(dalek_keypair)) -} - -/// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided; -/// otherwise generates the base Bip44 Solana keypair from the seed -pub fn keypair_from_seed_and_derivation_path( - seed: &[u8], - derivation_path: Option, -) -> Result> { - let derivation_path = derivation_path.unwrap_or_else(DerivationPath::default); - bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into()) -} - -/// Generates a Keypair using Bip32 Hierarchical Derivation -fn bip32_derived_keypair( - seed: &[u8], - derivation_path: DerivationPath, -) -> Result { - 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, -) -> Vec { - const PBKDF2_ROUNDS: u32 = 2048; - const PBKDF2_BYTES: usize = 64; - - let salt = format!("mnemonic{}", passphrase); - - let mut seed = vec![0u8; PBKDF2_BYTES]; - pbkdf2::pbkdf2::>( - seed_phrase.as_bytes(), - salt.as_bytes(), - PBKDF2_ROUNDS, - &mut seed, - ); - seed -} - -pub fn keypair_from_seed_phrase_and_passphrase( - seed_phrase: &str, - passphrase: &str, -) -> Result> { - keypair_from_seed(&generate_seed_from_seed_phrase_and_passphrase( - seed_phrase, - passphrase, - )) -} - #[cfg(test)] mod tests { use super::*; - use bip39::{Language, Mnemonic, MnemonicType, Seed}; - use std::mem; - - fn tmp_file_path(name: &str) -> String { - use std::env; - let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); - let keypair = Keypair::new(); - - format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()) - } - - #[test] - fn test_write_keypair_file() { - let outfile = tmp_file_path("test_write_keypair_file.json"); - let serialized_keypair = write_keypair_file(&Keypair::new(), &outfile).unwrap(); - let keypair_vec: Vec = serde_json::from_str(&serialized_keypair).unwrap(); - assert!(Path::new(&outfile).exists()); - assert_eq!( - keypair_vec, - read_keypair_file(&outfile).unwrap().0.to_bytes().to_vec() - ); - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - assert_eq!( - File::open(&outfile) - .expect("open") - .metadata() - .expect("metadata") - .permissions() - .mode() - & 0o777, - 0o600 - ); - } - - assert_eq!( - read_keypair_file(&outfile).unwrap().pubkey().as_ref().len(), - mem::size_of::() - ); - fs::remove_file(&outfile).unwrap(); - assert!(!Path::new(&outfile).exists()); - } - - #[test] - fn test_write_keypair_file_overwrite_ok() { - let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json"); - - write_keypair_file(&Keypair::new(), &outfile).unwrap(); - write_keypair_file(&Keypair::new(), &outfile).unwrap(); - } - - #[test] - fn test_write_keypair_file_truncate() { - let outfile = tmp_file_path("test_write_keypair_file_truncate.json"); - - write_keypair_file(&Keypair::new(), &outfile).unwrap(); - read_keypair_file(&outfile).unwrap(); - - // Ensure outfile is truncated - { - let mut f = File::create(&outfile).unwrap(); - f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes()) - .unwrap(); - } - write_keypair_file(&Keypair::new(), &outfile).unwrap(); - read_keypair_file(&outfile).unwrap(); - } - - #[test] - fn test_keypair_from_seed() { - let good_seed = vec![0; 32]; - assert!(keypair_from_seed(&good_seed).is_ok()); - - let too_short_seed = vec![0; 31]; - assert!(keypair_from_seed(&too_short_seed).is_err()); - } - #[test] fn test_signature_fromstr() { let signature = Keypair::new().sign_message(&[0u8]); @@ -576,73 +160,6 @@ mod tests { ); } - #[test] - fn test_keypair_from_seed_phrase_and_passphrase() { - let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); - let passphrase = "42"; - let seed = Seed::new(&mnemonic, passphrase); - let expected_keypair = keypair_from_seed(seed.as_bytes()).unwrap(); - let keypair = - keypair_from_seed_phrase_and_passphrase(mnemonic.phrase(), passphrase).unwrap(); - assert_eq!(keypair.pubkey(), expected_keypair.pubkey()); - } - - #[test] - fn test_keypair() { - let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let pubkey = keypair.pubkey(); - let data = [1u8]; - let sig = keypair.sign_message(&data); - - // Signer - assert_eq!(keypair.try_pubkey().unwrap(), pubkey); - assert_eq!(keypair.pubkey(), pubkey); - assert_eq!(keypair.try_sign_message(&data).unwrap(), sig); - assert_eq!(keypair.sign_message(&data), sig); - - // PartialEq - let keypair2 = keypair_from_seed(&[0u8; 32]).unwrap(); - assert_eq!(keypair, keypair2); - } - - #[test] - fn test_presigner() { - let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let pubkey = keypair.pubkey(); - let data = [1u8]; - let sig = keypair.sign_message(&data); - - // Signer - let presigner = Presigner::new(&pubkey, &sig); - assert_eq!(presigner.try_pubkey().unwrap(), pubkey); - assert_eq!(presigner.pubkey(), pubkey); - assert_eq!(presigner.try_sign_message(&data).unwrap(), sig); - assert_eq!(presigner.sign_message(&data), sig); - let bad_data = [2u8]; - assert!(presigner.try_sign_message(&bad_data).is_err()); - assert_eq!(presigner.sign_message(&bad_data), Signature::default()); - - // PartialEq - assert_eq!(presigner, keypair); - assert_eq!(keypair, presigner); - let presigner2 = Presigner::new(&pubkey, &sig); - assert_eq!(presigner, presigner2); - } - - fn pubkeys(signers: &[&dyn Signer]) -> Vec { - signers.iter().map(|x| x.pubkey()).collect() - } - - #[test] - fn test_unique_signers() { - let alice = Keypair::new(); - let bob = Keypair::new(); - assert_eq!( - pubkeys(&unique_signers(vec![&alice, &bob, &alice])), - pubkeys(&[&alice, &bob]) - ); - } - #[test] fn test_off_curve_pubkey_verify_fails() { // Golden point off the ed25519 curve diff --git a/sdk/src/signer/keypair.rs b/sdk/src/signer/keypair.rs new file mode 100644 index 0000000000..aa6f463256 --- /dev/null +++ b/sdk/src/signer/keypair.rs @@ -0,0 +1,326 @@ +#![cfg(feature = "full")] + +use { + crate::{ + derivation_path::DerivationPath, + pubkey::Pubkey, + signature::Signature, + signer::{Signer, SignerError}, + }, + ed25519_dalek::Signer as DalekSigner, + ed25519_dalek_bip32::Error as Bip32Error, + hmac::Hmac, + rand::{rngs::OsRng, CryptoRng, RngCore}, + std::{ + error, + fs::{self, File, OpenOptions}, + io::{Read, Write}, + path::Path, + }, +}; + +/// A vanilla Ed25519 key pair +#[derive(Debug)] +pub struct Keypair(ed25519_dalek::Keypair); + +impl Keypair { + /// Constructs a new, random `Keypair` using a caller-proveded RNG + pub fn generate(csprng: &mut R) -> Self + where + R: CryptoRng + RngCore, + { + Self(ed25519_dalek::Keypair::generate(csprng)) + } + + /// Constructs a new, random `Keypair` using `OsRng` + pub fn new() -> Self { + let mut rng = OsRng::default(); + Self::generate(&mut rng) + } + + /// Recovers a `Keypair` from a byte array + pub fn from_bytes(bytes: &[u8]) -> Result { + ed25519_dalek::Keypair::from_bytes(bytes).map(Self) + } + + /// Returns this `Keypair` as a byte array + pub fn to_bytes(&self) -> [u8; 64] { + self.0.to_bytes() + } + + /// Recovers a `Keypair` from a base58-encoded string + pub fn from_base58_string(s: &str) -> Self { + Self::from_bytes(&bs58::decode(s).into_vec().unwrap()).unwrap() + } + + /// Returns this `Keypair` as a base58-encoded string + pub fn to_base58_string(&self) -> String { + bs58::encode(&self.0.to_bytes()).into_string() + } + + /// Gets this `Keypair`'s SecretKey + pub fn secret(&self) -> &ed25519_dalek::SecretKey { + &self.0.secret + } +} + +impl Signer for Keypair { + fn pubkey(&self) -> Pubkey { + Pubkey::new(self.0.public.as_ref()) + } + + fn try_pubkey(&self) -> Result { + Ok(self.pubkey()) + } + + fn sign_message(&self, message: &[u8]) -> Signature { + Signature::new(&self.0.sign(message).to_bytes()) + } + + fn try_sign_message(&self, message: &[u8]) -> Result { + Ok(self.sign_message(message)) + } +} + +impl PartialEq for Keypair +where + T: Signer, +{ + fn eq(&self, other: &T) -> bool { + self.pubkey() == other.pubkey() + } +} + +/// Reads a JSON-encoded `Keypair` from a `Reader` implementor +pub fn read_keypair(reader: &mut R) -> Result> { + let bytes: Vec = serde_json::from_reader(reader)?; + let dalek_keypair = ed25519_dalek::Keypair::from_bytes(&bytes) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; + Ok(Keypair(dalek_keypair)) +} + +/// Reads a `Keypair` from a file +pub fn read_keypair_file>(path: F) -> Result> { + let mut file = File::open(path.as_ref())?; + read_keypair(&mut file) +} + +/// Writes a `Keypair` to a `Write` implementor with JSON-encoding +pub fn write_keypair( + keypair: &Keypair, + writer: &mut W, +) -> Result> { + let keypair_bytes = keypair.0.to_bytes(); + let serialized = serde_json::to_string(&keypair_bytes.to_vec())?; + writer.write_all(&serialized.clone().into_bytes())?; + Ok(serialized) +} + +/// Writes a `Keypair` to a file with JSON-encoding +pub fn write_keypair_file>( + keypair: &Keypair, + outfile: F, +) -> Result> { + let outfile = outfile.as_ref(); + + if let Some(outdir) = outfile.parent() { + fs::create_dir_all(outdir)?; + } + + let mut f = { + #[cfg(not(unix))] + { + OpenOptions::new() + } + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + OpenOptions::new().mode(0o600) + } + } + .write(true) + .truncate(true) + .create(true) + .open(outfile)?; + + write_keypair(keypair, &mut f) +} + +/// Constructs a `Keypair` from caller-provided seed entropy +pub fn keypair_from_seed(seed: &[u8]) -> Result> { + if seed.len() < ed25519_dalek::SECRET_KEY_LENGTH { + return Err("Seed is too short".into()); + } + let secret = ed25519_dalek::SecretKey::from_bytes(&seed[..ed25519_dalek::SECRET_KEY_LENGTH]) + .map_err(|e| e.to_string())?; + let public = ed25519_dalek::PublicKey::from(&secret); + let dalek_keypair = ed25519_dalek::Keypair { secret, public }; + Ok(Keypair(dalek_keypair)) +} + +/// Generates a Keypair using Bip32 Hierarchical Derivation if derivation-path is provided; +/// otherwise generates the base Bip44 Solana keypair from the seed +pub fn keypair_from_seed_and_derivation_path( + seed: &[u8], + derivation_path: Option, +) -> Result> { + let derivation_path = derivation_path.unwrap_or_else(DerivationPath::default); + bip32_derived_keypair(seed, derivation_path).map_err(|err| err.to_string().into()) +} + +/// Generates a Keypair using Bip32 Hierarchical Derivation +fn bip32_derived_keypair( + seed: &[u8], + derivation_path: DerivationPath, +) -> Result { + 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, +) -> Vec { + const PBKDF2_ROUNDS: u32 = 2048; + const PBKDF2_BYTES: usize = 64; + + let salt = format!("mnemonic{}", passphrase); + + let mut seed = vec![0u8; PBKDF2_BYTES]; + pbkdf2::pbkdf2::>( + seed_phrase.as_bytes(), + salt.as_bytes(), + PBKDF2_ROUNDS, + &mut seed, + ); + seed +} + +pub fn keypair_from_seed_phrase_and_passphrase( + seed_phrase: &str, + passphrase: &str, +) -> Result> { + keypair_from_seed(&generate_seed_from_seed_phrase_and_passphrase( + seed_phrase, + passphrase, + )) +} + +#[cfg(test)] +mod tests { + use { + super::*, + bip39::{Language, Mnemonic, MnemonicType, Seed}, + std::mem, + }; + + fn tmp_file_path(name: &str) -> String { + use std::env; + let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); + let keypair = Keypair::new(); + + format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()) + } + + #[test] + fn test_write_keypair_file() { + let outfile = tmp_file_path("test_write_keypair_file.json"); + let serialized_keypair = write_keypair_file(&Keypair::new(), &outfile).unwrap(); + let keypair_vec: Vec = serde_json::from_str(&serialized_keypair).unwrap(); + assert!(Path::new(&outfile).exists()); + assert_eq!( + keypair_vec, + read_keypair_file(&outfile).unwrap().0.to_bytes().to_vec() + ); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + assert_eq!( + File::open(&outfile) + .expect("open") + .metadata() + .expect("metadata") + .permissions() + .mode() + & 0o777, + 0o600 + ); + } + + assert_eq!( + read_keypair_file(&outfile).unwrap().pubkey().as_ref().len(), + mem::size_of::() + ); + fs::remove_file(&outfile).unwrap(); + assert!(!Path::new(&outfile).exists()); + } + + #[test] + fn test_write_keypair_file_overwrite_ok() { + let outfile = tmp_file_path("test_write_keypair_file_overwrite_ok.json"); + + write_keypair_file(&Keypair::new(), &outfile).unwrap(); + write_keypair_file(&Keypair::new(), &outfile).unwrap(); + } + + #[test] + fn test_write_keypair_file_truncate() { + let outfile = tmp_file_path("test_write_keypair_file_truncate.json"); + + write_keypair_file(&Keypair::new(), &outfile).unwrap(); + read_keypair_file(&outfile).unwrap(); + + // Ensure outfile is truncated + { + let mut f = File::create(&outfile).unwrap(); + f.write_all(String::from_utf8([b'a'; 2048].to_vec()).unwrap().as_bytes()) + .unwrap(); + } + write_keypair_file(&Keypair::new(), &outfile).unwrap(); + read_keypair_file(&outfile).unwrap(); + } + + #[test] + fn test_keypair_from_seed() { + let good_seed = vec![0; 32]; + assert!(keypair_from_seed(&good_seed).is_ok()); + + let too_short_seed = vec![0; 31]; + assert!(keypair_from_seed(&too_short_seed).is_err()); + } + + #[test] + fn test_keypair_from_seed_phrase_and_passphrase() { + let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); + let passphrase = "42"; + let seed = Seed::new(&mnemonic, passphrase); + let expected_keypair = keypair_from_seed(seed.as_bytes()).unwrap(); + let keypair = + keypair_from_seed_phrase_and_passphrase(mnemonic.phrase(), passphrase).unwrap(); + assert_eq!(keypair.pubkey(), expected_keypair.pubkey()); + } + + #[test] + fn test_keypair() { + let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let pubkey = keypair.pubkey(); + let data = [1u8]; + let sig = keypair.sign_message(&data); + + // Signer + assert_eq!(keypair.try_pubkey().unwrap(), pubkey); + assert_eq!(keypair.pubkey(), pubkey); + assert_eq!(keypair.try_sign_message(&data).unwrap(), sig); + assert_eq!(keypair.sign_message(&data), sig); + + // PartialEq + let keypair2 = keypair_from_seed(&[0u8; 32]).unwrap(); + assert_eq!(keypair, keypair2); + } +} diff --git a/sdk/src/signer/mod.rs b/sdk/src/signer/mod.rs new file mode 100644 index 0000000000..5a8e0e27cc --- /dev/null +++ b/sdk/src/signer/mod.rs @@ -0,0 +1,117 @@ +#![cfg(feature = "full")] + +use { + crate::{ + pubkey::Pubkey, + signature::{PresignerError, Signature}, + transaction::TransactionError, + }, + itertools::Itertools, + thiserror::Error, +}; + +pub mod keypair; +pub mod null_signer; +pub mod presigner; +pub mod signers; + +#[derive(Debug, Error, PartialEq)] +pub enum SignerError { + #[error("keypair-pubkey mismatch")] + KeypairPubkeyMismatch, + + #[error("not enough signers")] + NotEnoughSigners, + + #[error("transaction error")] + TransactionError(#[from] TransactionError), + + #[error("custom error: {0}")] + Custom(String), + + // Presigner-specific Errors + #[error("presigner error")] + PresignerError(#[from] PresignerError), + + // Remote Keypair-specific Errors + #[error("connection error: {0}")] + Connection(String), + + #[error("invalid input: {0}")] + InvalidInput(String), + + #[error("no device found")] + NoDeviceFound, + + #[error("{0}")] + Protocol(String), + + #[error("{0}")] + UserCancel(String), +} + +/// The `Signer` trait declares operations that all digital signature providers +/// must support. It is the primary interface by which signers are specified in +/// `Transaction` signing interfaces +pub trait Signer { + /// Infallibly gets the implementor's public key. Returns the all-zeros + /// `Pubkey` if the implementor has none. + fn pubkey(&self) -> Pubkey { + self.try_pubkey().unwrap_or_default() + } + /// Fallibly gets the implementor's public key + fn try_pubkey(&self) -> Result; + /// Invallibly produces an Ed25519 signature over the provided `message` + /// bytes. Returns the all-zeros `Signature` if signing is not possible. + fn sign_message(&self, message: &[u8]) -> Signature { + self.try_sign_message(message).unwrap_or_default() + } + /// Fallibly produces an Ed25519 signature over the provided `message` bytes. + fn try_sign_message(&self, message: &[u8]) -> Result; +} + +impl From for Box +where + T: Signer + 'static, +{ + fn from(signer: T) -> Self { + Box::new(signer) + } +} + +impl PartialEq for dyn Signer { + fn eq(&self, other: &dyn Signer) -> bool { + self.pubkey() == other.pubkey() + } +} + +impl std::fmt::Debug for dyn Signer { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "Signer: {:?}", self.pubkey()) + } +} + +/// Removes duplicate signers while preserving order. O(n²) +pub fn unique_signers(signers: Vec<&dyn Signer>) -> Vec<&dyn Signer> { + signers.into_iter().unique_by(|s| s.pubkey()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::signer::keypair::Keypair; + + fn pubkeys(signers: &[&dyn Signer]) -> Vec { + signers.iter().map(|x| x.pubkey()).collect() + } + + #[test] + fn test_unique_signers() { + let alice = Keypair::new(); + let bob = Keypair::new(); + assert_eq!( + pubkeys(&unique_signers(vec![&alice, &bob, &alice])), + pubkeys(&[&alice, &bob]) + ); + } +} diff --git a/sdk/src/signer/null_signer.rs b/sdk/src/signer/null_signer.rs new file mode 100644 index 0000000000..d5b5ccd38b --- /dev/null +++ b/sdk/src/signer/null_signer.rs @@ -0,0 +1,40 @@ +#![cfg(feature = "full")] + +use crate::{ + pubkey::Pubkey, + signature::Signature, + signer::{Signer, SignerError}, +}; + +/// NullSigner - A `Signer` implementation that always produces `Signature::default()`. +/// Used as a placeholder for absentee signers whose 'Pubkey` is required to construct +/// the transaction +#[derive(Clone, Debug, Default)] +pub struct NullSigner { + pubkey: Pubkey, +} + +impl NullSigner { + pub fn new(pubkey: &Pubkey) -> Self { + Self { pubkey: *pubkey } + } +} + +impl Signer for NullSigner { + fn try_pubkey(&self) -> Result { + Ok(self.pubkey) + } + + fn try_sign_message(&self, _message: &[u8]) -> Result { + Ok(Signature::default()) + } +} + +impl PartialEq for NullSigner +where + T: Signer, +{ + fn eq(&self, other: &T) -> bool { + self.pubkey == other.pubkey() + } +} diff --git a/sdk/src/signer/presigner.rs b/sdk/src/signer/presigner.rs new file mode 100644 index 0000000000..4b9c12c11a --- /dev/null +++ b/sdk/src/signer/presigner.rs @@ -0,0 +1,88 @@ +#![cfg(feature = "full")] + +use { + crate::{ + pubkey::Pubkey, + signature::Signature, + signer::{Signer, SignerError}, + }, + thiserror::Error, +}; + +/// A `Signer` implementation that represents a `Signature` that has been +/// constructed externally. Performs a signature verification against the +/// expected message upon `sign()` requests to affirm its relationship to +/// the `message` bytes +#[derive(Clone, Debug, Default)] +pub struct Presigner { + pubkey: Pubkey, + signature: Signature, +} + +impl Presigner { + pub fn new(pubkey: &Pubkey, signature: &Signature) -> Self { + Self { + pubkey: *pubkey, + signature: *signature, + } + } +} + +#[derive(Debug, Error, PartialEq)] +pub enum PresignerError { + #[error("pre-generated signature cannot verify data")] + VerificationFailure, +} + +impl Signer for Presigner { + fn try_pubkey(&self) -> Result { + Ok(self.pubkey) + } + + fn try_sign_message(&self, message: &[u8]) -> Result { + if self.signature.verify(self.pubkey.as_ref(), message) { + Ok(self.signature) + } else { + Err(PresignerError::VerificationFailure.into()) + } + } +} + +impl PartialEq for Presigner +where + T: Signer, +{ + fn eq(&self, other: &T) -> bool { + self.pubkey() == other.pubkey() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::signer::keypair::keypair_from_seed; + + #[test] + fn test_presigner() { + let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + let pubkey = keypair.pubkey(); + let data = [1u8]; + let sig = keypair.sign_message(&data); + + // Signer + let presigner = Presigner::new(&pubkey, &sig); + assert_eq!(presigner.try_pubkey().unwrap(), pubkey); + assert_eq!(presigner.pubkey(), pubkey); + assert_eq!(presigner.try_sign_message(&data).unwrap(), sig); + assert_eq!(presigner.sign_message(&data), sig); + let bad_data = [2u8]; + assert!(presigner.try_sign_message(&bad_data).is_err()); + assert_eq!(presigner.sign_message(&bad_data), Signature::default()); + + // PartialEq + assert_eq!(presigner, keypair); + assert_eq!(keypair, presigner); + let presigner2 = Presigner::new(&pubkey, &sig); + assert_eq!(presigner, presigner2); + } +} diff --git a/sdk/src/signers.rs b/sdk/src/signer/signers.rs similarity index 98% rename from sdk/src/signers.rs rename to sdk/src/signer/signers.rs index e25b6a059d..abc0772096 100644 --- a/sdk/src/signers.rs +++ b/sdk/src/signer/signers.rs @@ -4,6 +4,7 @@ use crate::{ signature::{Signature, Signer, SignerError}, }; +/// Convenience trait for working with mixed collections of `Signer`s pub trait Signers { fn pubkeys(&self) -> Vec; fn try_pubkeys(&self) -> Result, SignerError>;