From 70a8516622c026fda7ff4fb74291e35369ba84cb Mon Sep 17 00:00:00 2001 From: Jason Colburne Date: Mon, 6 Mar 2023 15:24:19 -0400 Subject: [PATCH] ed448 --- Cargo.toml | 1 + src/core/cigar.rs | 7 +-- src/core/matter/tables.rs | 20 ++++---- src/core/signer.rs | 29 +++++++++-- src/core/verfer.rs | 4 +- src/crypto/sign.rs | 103 +++++++++++++++++++++++++++++++++++++- 6 files changed, 141 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5e75a7..e8c7ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/core/cigar.rs b/src/core/cigar.rs index d43f69c..c18b1f4 100644 --- a/src/core/cigar.rs +++ b/src/core/cigar.rs @@ -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())); diff --git a/src/core/matter/tables.rs b/src/core/matter/tables.rs index f986ade..99e2c55 100644 --- a/src/core/matter/tables.rs +++ b/src/core/matter/tables.rs @@ -25,8 +25,6 @@ pub(crate) fn sizage(s: &str) -> Result { "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 }, @@ -43,10 +41,12 @@ pub(crate) fn sizage(s: &str) -> Result { "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 }, @@ -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 @@ -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) @@ -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)] @@ -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)] @@ -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")] @@ -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")] diff --git a/src/core/signer.rs b/src/core/signer.rs index a5983b0..181223e 100644 --- a/src/core/signer.rs +++ b/src/core/signer.rs @@ -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())); @@ -70,11 +67,13 @@ fn derive_verfer(code: &str, private_key: &[u8], transferable: bool) -> Result 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())), }, }; @@ -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())), }; @@ -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())), } }; @@ -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())), } }; @@ -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)] diff --git a/src/core/verfer.rs b/src/core/verfer.rs index 563b8b8..9242d7a 100644 --- a/src/core/verfer.rs +++ b/src/core/verfer.rs @@ -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) { diff --git a/src/crypto/sign.rs b/src/crypto/sign.rs index e179b20..decf501 100644 --- a/src/crypto/sign.rs +++ b/src/crypto/sign.rs @@ -11,6 +11,10 @@ pub(crate) fn generate(code: &str) -> Result> { | 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())), } } @@ -25,6 +29,10 @@ pub(crate) fn public_key(code: &str, private_key: &[u8]) -> Result> { | 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())), } } @@ -39,6 +47,10 @@ pub(crate) fn sign(code: &str, private_key: &[u8], ser: &[u8]) -> Result | 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())), } } @@ -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())), } } @@ -134,14 +150,79 @@ 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> { + let key = PrivateKey::new(&mut OsRng); + Ok(key.as_bytes().to_vec()) + } + + pub(crate) fn public_key(private_key: &[u8]) -> Result> { + 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> { + 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 { + 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(); @@ -149,6 +230,26 @@ mod test { 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;