Skip to content

Commit

Permalink
ed448
Browse files Browse the repository at this point in the history
  • Loading branch information
jasoncolburne committed Mar 6, 2023
1 parent f00a844 commit 70a8516
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ blake2 = "~0.10"
blake3 = "~1"
chrono = { version = "~0.4", default-features = false, features = ["clock"] }
ed25519-dalek = "~1"
ed448-rust = { git = "https://github.com/jasoncolburne/ed448-rust.git" }
indexmap = "~1"
k256 = "~0.12"
lazy_static = "~1"
Expand Down
7 changes: 2 additions & 5 deletions src/core/cigar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ impl Default for Cigar {
}

fn validate_code(code: &str) -> Result<()> {
const CODES: &[&str] = &[
matter::Codex::Ed25519_Sig,
matter::Codex::ECDSA_256k1_Sig,
// matter::Codex::Ed448_Sig,
];
const CODES: &[&str] =
&[matter::Codex::Ed25519_Sig, matter::Codex::ECDSA_256k1_Sig, matter::Codex::Ed448_Sig];

if !CODES.contains(&code) {
return err!(Error::UnexpectedCode(code.to_string()));
Expand Down
20 changes: 10 additions & 10 deletions src/core/matter/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ pub(crate) fn sizage(s: &str) -> Result<Sizage> {
"H" => Sizage { hs: 1, ss: 0, fs: 44, ls: 0 },
"I" => Sizage { hs: 1, ss: 0, fs: 44, ls: 0 },
"J" => Sizage { hs: 1, ss: 0, fs: 44, ls: 0 },
"K" => Sizage { hs: 1, ss: 0, fs: 76, ls: 0 },
"L" => Sizage { hs: 1, ss: 0, fs: 76, ls: 0 },
"M" => Sizage { hs: 1, ss: 0, fs: 4, ls: 0 },
"N" => Sizage { hs: 1, ss: 0, fs: 12, ls: 0 },
"O" => Sizage { hs: 1, ss: 0, fs: 44, ls: 0 },
Expand All @@ -43,10 +41,12 @@ pub(crate) fn sizage(s: &str) -> Result<Sizage> {
"1AAB" => Sizage { hs: 4, ss: 0, fs: 48, ls: 0 },
"1AAC" => Sizage { hs: 4, ss: 0, fs: 80, ls: 0 },
"1AAD" => Sizage { hs: 4, ss: 0, fs: 80, ls: 0 },
"1AAE" => Sizage { hs: 4, ss: 0, fs: 56, ls: 0 },
"1AAE" => Sizage { hs: 4, ss: 0, fs: 156, ls: 0 },
"1AAF" => Sizage { hs: 4, ss: 0, fs: 8, ls: 0 },
"1AAG" => Sizage { hs: 4, ss: 0, fs: 36, ls: 0 },
"1AAH" => Sizage { hs: 4, ss: 0, fs: 100, ls: 0 },
"1AAI" => Sizage { hs: 4, ss: 0, fs: 80, ls: 0 },
"1AAJ" => Sizage { hs: 4, ss: 0, fs: 80, ls: 0 },
"2AAA" => Sizage { hs: 4, ss: 0, fs: 8, ls: 1 },
"3AAA" => Sizage { hs: 4, ss: 0, fs: 8, ls: 2 },
"4A" => Sizage { hs: 2, ss: 2, fs: 0, ls: 0 },
Expand Down Expand Up @@ -110,8 +110,6 @@ pub mod Codex {
pub const SHA3_256: &str = "H"; // SHA3 256 bit digest self-addressing derivation.
pub const SHA2_256: &str = "I"; // SHA2 256 bit digest self-addressing derivation.
pub const ECDSA_256k1_Seed: &str = "J"; // ECDSA secp256k1 256 bit random Seed for private key
pub const Ed448_Seed: &str = "K"; // Ed448 448 bit random Seed for private key
pub const X448: &str = "L"; // X448 public encryption key, converted from Ed448
pub const Short: &str = "M"; // Short 2 byte b2 number
pub const Big: &str = "N"; // Big 8 byte b2 number
pub const X25519_Private: &str = "O"; // X25519 private decryption key converted from Ed25519
Expand All @@ -132,6 +130,8 @@ pub mod Codex {
pub const Tern: &str = "1AAF"; // 3 byte b2 number or 4 char B64 str.
pub const DateTime: &str = "1AAG"; // Base64 custom encoded 32 char ISO-8601 DateTime
pub const X25519_Cipher_Salt: &str = "1AAH"; // X25519 100 char b64 Cipher of 24 char qb64 Salt
pub const Ed448_Seed: &str = "1AAI"; // Ed448 448 bit random Seed for private key
pub const X448: &str = "1AAJ"; // X448 public encryption key, converted from Ed448
pub const TBD1: &str = "2AAA"; // Testing purposes only fixed with lead size 1
pub const TBD2: &str = "3AAA"; // Testing purposes only of fixed with lead size 2
pub const StrB64_L0: &str = "4A"; // String Base64 Only Lead Size 0 (4095 * 3 | 4)
Expand Down Expand Up @@ -164,8 +164,6 @@ mod test {
#[case("H", 1, 0, 44, 0)]
#[case("I", 1, 0, 44, 0)]
#[case("J", 1, 0, 44, 0)]
#[case("K", 1, 0, 76, 0)]
#[case("L", 1, 0, 76, 0)]
#[case("M", 1, 0, 4, 0)]
#[case("N", 1, 0, 12, 0)]
#[case("O", 1, 0, 44, 0)]
Expand All @@ -182,10 +180,12 @@ mod test {
#[case("1AAB", 4, 0, 48, 0)]
#[case("1AAC", 4, 0, 80, 0)]
#[case("1AAD", 4, 0, 80, 0)]
#[case("1AAE", 4, 0, 56, 0)]
#[case("1AAE", 4, 0, 156, 0)]
#[case("1AAF", 4, 0, 8, 0)]
#[case("1AAG", 4, 0, 36, 0)]
#[case("1AAH", 4, 0, 100, 0)]
#[case("1AAI", 4, 0, 80, 0)]
#[case("1AAJ", 4, 0, 80, 0)]
#[case("2AAA", 4, 0, 8, 1)]
#[case("3AAA", 4, 0, 8, 2)]
#[case("4A", 2, 2, 0, 0)]
Expand Down Expand Up @@ -292,8 +292,6 @@ mod test {
#[case(Codex::SHA3_256, "H")]
#[case(Codex::SHA2_256, "I")]
#[case(Codex::ECDSA_256k1_Seed, "J")]
#[case(Codex::Ed448_Seed, "K")]
#[case(Codex::X448, "L")]
#[case(Codex::Short, "M")]
#[case(Codex::Big, "N")]
#[case(Codex::X25519_Private, "O")]
Expand All @@ -314,6 +312,8 @@ mod test {
#[case(Codex::Tern, "1AAF")]
#[case(Codex::DateTime, "1AAG")]
#[case(Codex::X25519_Cipher_Salt, "1AAH")]
#[case(Codex::Ed448_Seed, "1AAI")]
#[case(Codex::X448, "1AAJ")]
#[case(Codex::TBD1, "2AAA")]
#[case(Codex::TBD2, "3AAA")]
#[case(Codex::StrB64_L0, "4A")]
Expand Down
29 changes: 24 additions & 5 deletions src/core/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ impl Default for Signer {
}

fn validate_code(code: &str) -> Result<()> {
const CODES: &[&str] = &[
matter::Codex::Ed25519_Seed,
matter::Codex::ECDSA_256k1_Seed,
// matter::Codex::Ed448_Seed,
];
const CODES: &[&str] =
&[matter::Codex::Ed25519_Seed, matter::Codex::ECDSA_256k1_Seed, matter::Codex::Ed448_Seed];

if !CODES.contains(&code) {
return err!(Error::UnexpectedCode(code.to_string()));
Expand All @@ -70,11 +67,13 @@ fn derive_verfer(code: &str, private_key: &[u8], transferable: bool) -> Result<V
true => match code {
matter::Codex::Ed25519_Seed => matter::Codex::Ed25519,
matter::Codex::ECDSA_256k1_Seed => matter::Codex::ECDSA_256k1,
matter::Codex::Ed448_Seed => matter::Codex::Ed448,
_ => return err!(Error::UnexpectedCode(code.to_string())),
},
false => match code {
matter::Codex::Ed25519_Seed => matter::Codex::Ed25519N,
matter::Codex::ECDSA_256k1_Seed => matter::Codex::ECDSA_256k1N,
matter::Codex::Ed448_Seed => matter::Codex::Ed448N,
_ => return err!(Error::UnexpectedCode(code.to_string())),
},
};
Expand Down Expand Up @@ -137,6 +136,7 @@ impl Signer {
let code = match self.code().as_str() {
matter::Codex::Ed25519_Seed => matter::Codex::Ed25519_Sig,
matter::Codex::ECDSA_256k1_Seed => matter::Codex::ECDSA_256k1_Sig,
matter::Codex::Ed448_Seed => matter::Codex::Ed448_Sig,
_ => return err!(Error::UnexpectedCode(self.code())),
};

Expand All @@ -157,12 +157,14 @@ impl Signer {
match self.code().as_str() {
matter::Codex::Ed25519_Seed => indexer::Codex::Ed25519_Crt,
matter::Codex::ECDSA_256k1_Seed => indexer::Codex::ECDSA_256k1_Crt,
matter::Codex::Ed448_Seed => indexer::Codex::Ed448_Crt,
_ => return err!(Error::UnexpectedCode(self.code())),
}
} else {
match self.code().as_str() {
matter::Codex::Ed25519_Seed => indexer::Codex::Ed25519_Big_Crt,
matter::Codex::ECDSA_256k1_Seed => indexer::Codex::ECDSA_256k1_Big_Crt,
matter::Codex::Ed448_Seed => indexer::Codex::Ed448_Big_Crt,
_ => return err!(Error::UnexpectedCode(self.code())),
}
};
Expand All @@ -175,12 +177,14 @@ impl Signer {
match self.code().as_str() {
matter::Codex::Ed25519_Seed => indexer::Codex::Ed25519,
matter::Codex::ECDSA_256k1_Seed => indexer::Codex::ECDSA_256k1,
matter::Codex::Ed448_Seed => indexer::Codex::Ed448,
_ => return err!(Error::UnexpectedCode(self.code())),
}
} else {
match self.code().as_str() {
matter::Codex::Ed25519_Seed => indexer::Codex::Ed25519_Big,
matter::Codex::ECDSA_256k1_Seed => indexer::Codex::ECDSA_256k1_Big,
matter::Codex::Ed448_Seed => indexer::Codex::Ed448_Big,
_ => return err!(Error::UnexpectedCode(self.code())),
}
};
Expand Down Expand Up @@ -325,6 +329,21 @@ mod test {
assert!(!signer.verfer().verify(&cigar.raw(), bad_ser).unwrap());
}

#[test]
fn sign_ed448_unindexed() {
let ser = b"abcdefghijklmnopqrstuvwxyz0123456789";
let bad_ser = b"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";

let signer =
Signer::new(Some(true), Some(matter::Codex::Ed448_Seed), None, None, None, None)
.unwrap();

let cigar = signer.sign_unindexed(ser).unwrap();
assert_eq!(cigar.code(), matter::Codex::Ed448_Sig);
assert!(signer.verfer().verify(&cigar.raw(), ser).unwrap());
assert!(!signer.verfer().verify(&cigar.raw(), bad_ser).unwrap());
}

#[rstest]
#[case(false, 0, None, 0, indexer::Codex::Ed25519)]
#[case(false, 1, None, 1, indexer::Codex::Ed25519)]
Expand Down
4 changes: 2 additions & 2 deletions src/core/verfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ fn validate_code(code: &str) -> Result<()> {
matter::Codex::Ed25519,
matter::Codex::ECDSA_256k1N,
matter::Codex::ECDSA_256k1,
// matter::Codex::Ed448N,
// matter::Codex::Ed448,
matter::Codex::Ed448N,
matter::Codex::Ed448,
];

if !CODES.contains(&code) {
Expand Down
103 changes: 102 additions & 1 deletion src/crypto/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub(crate) fn generate(code: &str) -> Result<Vec<u8>> {
| matter::Codex::ECDSA_256k1N
| matter::Codex::ECDSA_256k1_Seed
| matter::Codex::ECDSA_256k1_Sig => ecdsa_256k1::generate(),
matter::Codex::Ed448
| matter::Codex::Ed448N
| matter::Codex::Ed448_Seed
| matter::Codex::Ed448_Sig => ed448::generate(),
_ => err!(Error::UnexpectedCode(code.to_string())),
}
}
Expand All @@ -25,6 +29,10 @@ pub(crate) fn public_key(code: &str, private_key: &[u8]) -> Result<Vec<u8>> {
| matter::Codex::ECDSA_256k1N
| matter::Codex::ECDSA_256k1_Seed
| matter::Codex::ECDSA_256k1_Sig => ecdsa_256k1::public_key(private_key),
matter::Codex::Ed448
| matter::Codex::Ed448N
| matter::Codex::Ed448_Seed
| matter::Codex::Ed448_Sig => ed448::public_key(private_key),
_ => err!(Error::UnexpectedCode(code.to_string())),
}
}
Expand All @@ -39,6 +47,10 @@ pub(crate) fn sign(code: &str, private_key: &[u8], ser: &[u8]) -> Result<Vec<u8>
| matter::Codex::ECDSA_256k1N
| matter::Codex::ECDSA_256k1_Seed
| matter::Codex::ECDSA_256k1_Sig => ecdsa_256k1::sign(private_key, ser),
matter::Codex::Ed448
| matter::Codex::Ed448N
| matter::Codex::Ed448_Seed
| matter::Codex::Ed448_Sig => ed448::sign(private_key, ser),
_ => err!(Error::UnexpectedCode(code.to_string())),
}
}
Expand All @@ -53,6 +65,10 @@ pub(crate) fn verify(code: &str, public_key: &[u8], sig: &[u8], ser: &[u8]) -> R
| matter::Codex::ECDSA_256k1N
| matter::Codex::ECDSA_256k1_Seed
| matter::Codex::ECDSA_256k1_Sig => ecdsa_256k1::verify(public_key, sig, ser),
matter::Codex::Ed448
| matter::Codex::Ed448N
| matter::Codex::Ed448_Seed
| matter::Codex::Ed448_Sig => ed448::verify(public_key, sig, ser),
_ => err!(Error::UnexpectedCode(code.to_string())),
}
}
Expand Down Expand Up @@ -134,21 +150,106 @@ mod ecdsa_256k1 {
}
}

mod ed448 {
use crate::error::{err, Error, Result};

use ed448_rust::{PrivateKey, PublicKey, KEY_LENGTH};
use rand_core::OsRng;

pub(crate) fn generate() -> Result<Vec<u8>> {
let key = PrivateKey::new(&mut OsRng);
Ok(key.as_bytes().to_vec())
}

pub(crate) fn public_key(private_key: &[u8]) -> Result<Vec<u8>> {
if private_key.len() < KEY_LENGTH {
return err!(Error::Conversion(
"not enough private key material submitted to generate public key".to_string()
));
}

let mut key = [0u8; KEY_LENGTH];
key.copy_from_slice(private_key);
Ok(PublicKey::from(&PrivateKey::from(key)).as_bytes().to_vec())
}

pub(crate) fn sign(private_key: &[u8], ser: &[u8]) -> Result<Vec<u8>> {
if private_key.len() < KEY_LENGTH {
return err!(Error::Conversion(
"not enough private key material submitted to generate public key".to_string()
));
}

let mut key = [0u8; KEY_LENGTH];
key.copy_from_slice(private_key);

let result = match PrivateKey::from(key).sign(ser, None) {
Ok(s) => s,
Err(_) => return err!(Error::Derivation("could not sign data".to_string())),
};

Ok(result.to_vec())
}

pub(crate) fn verify(public_key: &[u8], sig: &[u8], ser: &[u8]) -> Result<bool> {
if public_key.len() < KEY_LENGTH {
return err!(Error::Conversion(
"not enough private key material submitted to generate public key".to_string()
));
}

let mut key = [0u8; KEY_LENGTH];
key.copy_from_slice(public_key);

match PublicKey::try_from(&key) {
Ok(public_key) => match public_key.verify(ser, sig, None) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
},
Err(_) => Ok(false),
}
}
}

#[cfg(test)]
mod test {
use crate::core::matter::tables as matter;
use crate::crypto::sign;
use hex_literal::hex;
use rstest::rstest;

#[rstest]
fn end_to_end(#[values(matter::Codex::Ed25519, matter::Codex::ECDSA_256k1)] code: &str) {
fn end_to_end(
#[values(matter::Codex::Ed25519, matter::Codex::ECDSA_256k1, matter::Codex::Ed448)]
code: &str,
) {
let ser = b"abcdefghijklmnopqrstuvwxyz";
let private_key = sign::generate(code).unwrap();
let signature = sign::sign(code, &private_key, ser).unwrap();
let public_key = sign::public_key(code, &private_key).unwrap();
assert!(sign::verify(code, &public_key, &signature, ser).unwrap());
}

#[test]
// rfc 8032
fn ed448() {
let code = matter::Codex::Ed448;

assert_eq!(sign::generate(code).unwrap().len(), 57);

let private_key = hex!("6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6"
"e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b");
let public_key = hex!("5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80"
"e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180");
let message = b"";
let signature = hex!("533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980"
"ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600");

assert_eq!(sign::public_key(code, &private_key).unwrap(), public_key);
assert_eq!(sign::sign(code, &private_key, message).unwrap(), signature);
assert!(sign::verify(code, &public_key, &signature, message).unwrap());
}

#[test]
fn unhappy_paths() {
let code = matter::Codex::SHA3_256;
Expand Down

0 comments on commit 70a8516

Please sign in to comment.