// Copyright (C) 2017 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide wrappers around ed25519 keys and signatures
//!
//! Key pairs can be generated with [`KeyPairGenerator`].
//!
//! [`KeyPairGenerator`]: struct.KeyPairGenerator.html
use std::fmt::Display;
use std::fmt::Error;
use std::fmt::Formatter;
use std::fmt::Debug;
use base58::{ToBase58, FromBase58, FromBase58Error};
use base64;
use base64::DecodeError;
use crypto;
use super::BaseConvertionError;
// ---------------------------- //
// ----- struct Signature ----- //
// ---------------------------- //
/// Store a ed25519 signature.
#[derive(Clone, Copy)]
pub struct Signature(pub [u8; 64]);
impl super::Signature for Signature {
fn from_base64(base64_data: &str) -> Result<Signature, BaseConvertionError> {
match base64::decode(base64_data) {
Ok(result) => {
if result.len() == 64 {
let mut u8_array = [0; 64];
u8_array[..64].clone_from_slice(&result[..64]);
Ok(Signature(u8_array))
} else {
Err(BaseConvertionError::InvalidKeyLendth(result.len(), 64))
}
}
Err(DecodeError::InvalidByte(pos, byte)) => {
Err(BaseConvertionError::InvalidCharacter(byte as char, pos))
}
Err(DecodeError::InvalidLength) => Err(
BaseConvertionError::InvalidBaseConverterLength(),
),
}
}
fn to_base64(&self) -> String {
base64::encode(&self.0[..]) // need to take a slice for required trait `AsRef<[u8]>`
}
}
impl Display for Signature {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
use super::Signature;
write!(f, "{}", self.to_base64())
}
}
impl Debug for Signature {
// Signature { 1eubHHb... }
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(f, "Signature {{ {} }}", self)
}
}
impl PartialEq<Signature> for Signature {
fn eq(&self, other: &Signature) -> bool {
// No PartialEq for [u8;64], need to use 2 [u8;32]
self.0[0..32] == other.0[0..32] && self.0[32..64] == other.0[32..64]
}
}
impl Eq for Signature {}
// ---------------------------- //
// ----- struct PublicKey ----- //
// ---------------------------- //
/// Store a Ed25519 public key.
///
/// Can be generated with [`KeyPairGenerator`].
///
/// [`KeyPairGenerator`]: struct.KeyPairGenerator.html
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct PublicKey(pub [u8; 32]);
impl ToBase58 for PublicKey {
fn to_base58(&self) -> String {
self.0.to_base58()
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.to_base58())
}
}
impl Debug for PublicKey {
// PublicKey { DNann1L... }
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "PublicKey {{ {} }}", self)
}
}
impl super::PublicKey for PublicKey {
type Signature = Signature;
fn from_base58(base58_data: &str) -> Result<Self, BaseConvertionError> {
match base58_data.from_base58() {
Ok(result) => {
if result.len() == 32 {
let mut u8_array = [0; 32];
u8_array[..32].clone_from_slice(&result[..32]);
Ok(PublicKey(u8_array))
} else {
Err(BaseConvertionError::InvalidKeyLendth(result.len(), 32))
}
}
Err(FromBase58Error::InvalidBase58Character(character, pos)) => Err(
BaseConvertionError::InvalidCharacter(character, pos),
),
Err(FromBase58Error::InvalidBase58Length) => Err(
BaseConvertionError::InvalidBaseConverterLength(),
),
}
}
fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool {
crypto::ed25519::verify(message, &self.0, &signature.0)
}
}
// ----------------------------- //
// ----- struct PrivateKey ----- //
// ----------------------------- //
/// Store a Ed25519 private key.
///
/// Can be generated with [`KeyPairGenerator`].
///
/// [`KeyPairGenerator`]: struct.KeyPairGenerator.html
#[derive(Copy, Clone)]
pub struct PrivateKey(pub [u8; 64]);
impl ToBase58 for PrivateKey {
fn to_base58(&self) -> String {
self.0.to_base58()
}
}
impl Display for PrivateKey {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.to_base58())
}
}
impl Debug for PrivateKey {
// PrivateKey { 468Q1XtT... }
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "PrivateKey {{ {} }}", self)
}
}
impl PartialEq<PrivateKey> for PrivateKey {
fn eq(&self, other: &PrivateKey) -> bool {
// No PartialEq for [u8;64], need to use 2 [u8;32]
self.0[0..32] == other.0[0..32] && self.0[32..64] == other.0[32..64]
}
}
impl Eq for PrivateKey {}
impl super::PrivateKey for PrivateKey {
type Signature = Signature;
fn from_base58(base58_data: &str) -> Result<Self, BaseConvertionError> {
match base58_data.from_base58() {
Ok(result) => {
if result.len() == 64 {
let mut u8_array = [0; 64];
u8_array[..64].clone_from_slice(&result[..64]);
Ok(PrivateKey(u8_array))
} else {
Err(BaseConvertionError::InvalidKeyLendth(result.len(), 64))
}
}
Err(FromBase58Error::InvalidBase58Character(character, pos)) => Err(
BaseConvertionError::InvalidCharacter(character, pos),
),
Err(FromBase58Error::InvalidBase58Length) => Err(
BaseConvertionError::InvalidBaseConverterLength(),
),
}
}
/// Sign a message with this private key.
fn sign(&self, message: &[u8]) -> Self::Signature {
Signature(crypto::ed25519::signature(message, &self.0))
}
}
// ----------------------------------- //
// ----- struct KeyPairGenerator ----- //
// ----------------------------------- //
/// Keypair generator with given parameters for `scrypt` keypair function.
#[derive(Debug, Copy, Clone)]
pub struct KeyPairGenerator {
/// The log2 of the Scrypt parameter `N`.
log_n: u8,
/// The Scrypt parameter `r`
r: u32,
/// The Scrypt parameter `p`
p: u32,
}
impl KeyPairGenerator {
/// Create a `KeyPairGenerator` with default arguments `(log_n: 12, r: 16, p: 1)`
pub fn with_default_parameters() -> KeyPairGenerator {
KeyPairGenerator {
log_n: 12,
r: 16,
p: 1,
}
}
/// Create a `KeyPairGenerator` with given arguments.
///
/// # Arguments
///
/// - log_n - The log2 of the Scrypt parameter N
/// - r - The Scrypt parameter r
/// - p - The Scrypt parameter p
pub fn with_parameters(log_n: u8, r: u32, p: u32) -> KeyPairGenerator {
KeyPairGenerator { log_n, r, p }
}
/// Create a keypair based on a given password and salt.
///
/// The [`PublicKey`](struct.PublicKey.html) will be able to verify messaged signed with
/// the [`PrivateKey`](struct.PrivateKey.html).
pub fn generate(&self, password: &[u8], salt: &[u8]) -> (PrivateKey, PublicKey) {
let mut seed = [0u8; 32];
crypto::scrypt::scrypt(
password,
salt,
&crypto::scrypt::ScryptParams::new(self.log_n, self.r, self.p),
&mut seed,
);
let (private, public) = crypto::ed25519::keypair(&seed);
(PrivateKey(private), PublicKey(public))
}
}
// ---------------------- //
// ----- UNIT TESTS ----- //
// ---------------------- //
#[cfg(test)]
mod tests {
use super::*;
use {Signature, PublicKey, PrivateKey};
#[test]
fn base58_private_key() {
let private58 = "468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9r\
qnXuW3iAfZACm7";
let private_key = super::PrivateKey::from_base58(private58).unwrap();
let private_raw = private58.from_base58().unwrap();
for (key, raw) in private_key.0.iter().zip(private_raw.iter()) {
assert_eq!(key, raw);
}
assert_eq!(private_key.to_base58(), private58);
assert_eq!(
private_key,
super::PrivateKey::from_base58(private58).unwrap()
);
assert_eq!(
super::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iA\
fZACm7djh",
).unwrap_err(),
BaseConvertionError::InvalidKeyLendth(67, 64)
);
assert_eq!(
super::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9",
).unwrap_err(),
BaseConvertionError::InvalidKeyLendth(53, 64)
);
assert_eq!(
super::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9<<",
).unwrap_err(),
BaseConvertionError::InvalidCharacter('<', 73)
);
}
#[test]
fn base58_public_key() {
let public58 = "DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV";
let public_key = super::PublicKey::from_base58(public58).unwrap();
let public_raw = public58.from_base58().unwrap();
for (key, raw) in public_key.0.iter().zip(public_raw.iter()) {
assert_eq!(key, raw);
}
assert_eq!(public_key.to_base58(), public58);
assert_eq!(public_key, super::PublicKey::from_base58(public58).unwrap());
assert_eq!(
super::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLVdjq")
.unwrap_err(),
BaseConvertionError::InvalidKeyLendth(35, 32)
);
assert_eq!(
super::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQd").unwrap_err(),
BaseConvertionError::InvalidKeyLendth(31, 32)
);
assert_eq!(
super::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQd<<")
.unwrap_err(),
BaseConvertionError::InvalidCharacter('<', 42)
);
}
#[test]
fn base64_signature() {
let signature64 = "1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FG\
MMmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==";
let signature = super::Signature::from_base64(signature64).unwrap();
let signature_raw = base64::decode(signature64).unwrap();
for (sig, raw) in signature.0.iter().zip(signature_raw.iter()) {
assert_eq!(sig, raw);
}
assert_eq!(
super::Signature::from_base64("YmhlaW9iaHNlcGlvaGVvaXNlcGl2ZXBvdm5pc2U=").unwrap_err(),
BaseConvertionError::InvalidKeyLendth(29, 64)
);
assert_eq!(
super::Signature::from_base64(
"YmhlaW9iaHNlcGlvaGVvaXNlcGl2ZXBvdm5pc2V2c2JlaW9idmVpb3Zqc\
2V2Z3BpaHNlamVwZ25qZXNqb2dwZWpnaW9zZXNkdnNic3JicmJyZGJyZGI=",
).unwrap_err(),
BaseConvertionError::InvalidKeyLendth(86, 64)
);
assert_eq!(
super::Signature::from_base64(
"1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAgdha<<",
).unwrap_err(),
BaseConvertionError::InvalidCharacter('<', 89)
);
}
#[test]
fn message_sign_verify() {
let pubkey = super::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV")
.unwrap();
let prikey = super::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt\
5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7",
).unwrap();
let expected_signature = super::Signature::from_base64(
"1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FG\
MMmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
).unwrap();
let message = "Version: 10
Type: Identity
Currency: duniter_unit_test_currency
Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
UniqueID: tic
Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
";
let sig = prikey.sign(message.as_bytes());
assert_eq!(sig, expected_signature);
assert!(pubkey.verify(message.as_bytes(), &sig));
}
}