From 5d4796bd72568c78ffe473c9d05ecc47365467ab Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Sun, 7 Apr 2024 22:16:49 -0400 Subject: [PATCH 1/4] Expanded secret key --- src/signing_key.rs | 128 +++++++++++++++++++++++++++----------------- tests/rfc8032.rs | 54 ++++++++++++++++++- tests/unit_tests.rs | 10 ++-- 3 files changed, 136 insertions(+), 56 deletions(-) diff --git a/src/signing_key.rs b/src/signing_key.rs index 4c51756..0a5f593 100644 --- a/src/signing_key.rs +++ b/src/signing_key.rs @@ -1,48 +1,85 @@ use core::convert::TryFrom; +use std::fmt::Formatter; use curve25519_dalek::{constants, scalar::Scalar}; use rand_core::{CryptoRng, RngCore}; +use serde::de::{self, Visitor}; +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::{Digest, Sha512}; use crate::{Error, Signature, VerificationKey, VerificationKeyBytes}; /// An Ed25519 signing key. /// -/// This is also called a secret key by other implementations. +/// This is also called an expanded secret key by other implementations. #[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(from = "SerdeHelper"))] -#[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] pub struct SigningKey { - seed: [u8; 32], s: Scalar, prefix: [u8; 32], vk: VerificationKey, } impl SigningKey { - /// Returns the byte encoding of the signing key. - /// - /// This is the same as `.into()`, but does not require type inference. - pub fn to_bytes(&self) -> [u8; 32] { - self.seed + /// Obtain the verification key associated with this signing key. + pub fn verification_key(&self) -> VerificationKey { + self.vk } +} - /// View the byte encoding of the signing key. - pub fn as_bytes(&self) -> &[u8; 32] { - &self.seed +/// Serialize the SigningKey as the expanded secret key +impl Serialize for SigningKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_tuple(64)?; + for s in self.s.as_bytes() { + seq.serialize_element(s)?; + } + for p in &self.prefix { + seq.serialize_element(p)?; + } + seq.end() } +} - /// Obtain the verification key associated with this signing key. - pub fn verification_key(&self) -> VerificationKey { - self.vk +/// Deserialize bytes to SigningKey +impl<'de> Deserialize<'de> for SigningKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TupleVisitor; + + impl<'de> Visitor<'de> for TupleVisitor { + type Value = SigningKey; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("an Ed25519 seed key or expanded secret key") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + let mut bytes = [0u8; 64]; + for i in 0..64 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(i, &self))?; + } + Ok(bytes.into()) + } + } + + deserializer.deserialize_tuple(64, TupleVisitor) } } impl core::fmt::Debug for SigningKey { fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { fmt.debug_struct("SigningKey") - .field("seed", &hex::encode(&self.seed)) .field("s", &self.s) .field("prefix", &hex::encode(&self.prefix)) .field("vk", &self.vk) @@ -62,37 +99,25 @@ impl<'a> From<&'a SigningKey> for VerificationKeyBytes { } } -impl AsRef<[u8]> for SigningKey { - fn as_ref(&self) -> &[u8] { - &self.seed[..] - } -} - -impl From for [u8; 32] { - fn from(sk: SigningKey) -> [u8; 32] { - sk.seed - } -} - impl TryFrom<&[u8]> for SigningKey { type Error = Error; fn try_from(slice: &[u8]) -> Result { + let mut bytes = [0u8; 64]; if slice.len() == 32 { - let mut bytes = [0u8; 32]; + let h = Sha512::digest(slice); + bytes[..].copy_from_slice(h.as_slice()); + } else if slice.len() == 64 { bytes[..].copy_from_slice(slice); - Ok(bytes.into()) } else { - Err(Error::InvalidSliceLength) + return Err(Error::InvalidSliceLength); } + Ok(bytes.into()) } } -impl From<[u8; 32]> for SigningKey { +impl From<[u8; 64]> for SigningKey { #[allow(non_snake_case)] - fn from(seed: [u8; 32]) -> SigningKey { - // Expand the seed to a 64-byte array with SHA512. - let h = Sha512::digest(&seed[..]); - + fn from(h: [u8; 64]) -> SigningKey { // Convert the low half to a scalar with Ed25519 "clamping" let s = { let mut scalar_bytes = [0u8; 32]; @@ -114,7 +139,6 @@ impl From<[u8; 32]> for SigningKey { let A = &s * &constants::ED25519_BASEPOINT_TABLE; SigningKey { - seed, s, prefix, vk: VerificationKey { @@ -125,25 +149,29 @@ impl From<[u8; 32]> for SigningKey { } } -impl zeroize::Zeroize for SigningKey { - fn zeroize(&mut self) { - self.seed.zeroize(); - self.s.zeroize() +impl From for [u8; 64] { + fn from(value: SigningKey) -> Self { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(value.s.as_bytes()); + bytes[32..].copy_from_slice(value.prefix.as_slice()); + bytes } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -struct SerdeHelper([u8; 32]); +impl From<[u8; 32]> for SigningKey { + fn from(seed: [u8; 32]) -> SigningKey { + // Expand the seed to a 64-byte array with SHA512. + let h = Sha512::digest(&seed); + let mut bytes = [0u8; 64]; + bytes[..].copy_from_slice(h.as_slice()); -impl From for SigningKey { - fn from(helper: SerdeHelper) -> SigningKey { - helper.0.into() + bytes.into() } } -impl From for SerdeHelper { - fn from(sk: SigningKey) -> Self { - Self(sk.into()) +impl zeroize::Zeroize for SigningKey { + fn zeroize(&mut self) { + self.s.zeroize() } } diff --git a/tests/rfc8032.rs b/tests/rfc8032.rs index 0384de3..560cdc1 100644 --- a/tests/rfc8032.rs +++ b/tests/rfc8032.rs @@ -7,9 +7,19 @@ use bincode; use ed25519_consensus::*; use hex; +use sha2::{Digest, Sha512}; +use std::convert::TryInto; fn rfc8032_test_case(sk_bytes: Vec, pk_bytes: Vec, sig_bytes: Vec, msg: Vec) { - let sk: SigningKey = bincode::deserialize(&sk_bytes).expect("sk should deserialize"); + let sk: SigningKey; + if sk_bytes.len() == 32 { + let sk_bytes: [u8; 32] = sk_bytes.as_slice().try_into().unwrap(); + sk = SigningKey::from(sk_bytes); + } else if sk_bytes.len() == 64 { + sk = bincode::deserialize(&sk_bytes).expect("sk should deserialize"); + } else { + panic!("invalid sk_bytes length"); + } let pk: VerificationKey = bincode::deserialize(&pk_bytes).expect("pk should deserialize"); let sig: Signature = bincode::deserialize(&sig_bytes).expect("sig should deserialize"); @@ -70,3 +80,45 @@ fn rfc8032_test_3() { .expect("hex should decode"), ); } + +#[test] +fn rfc8032_test_1_expanded() { + rfc8032_test_case( + Sha512::digest(hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") + .expect("hex should decode").as_slice()).to_vec(), + hex::decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") + .expect("hex should decode"), + hex::decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b") + .expect("hex should decode"), + hex::decode("") + .expect("hex should decode"), + ); +} + +#[test] +fn rfc8032_test_2_expanded() { + rfc8032_test_case( + Sha512::digest(hex::decode("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb") + .expect("hex should decode").as_slice()).to_vec(), + hex::decode("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c") + .expect("hex should decode"), + hex::decode("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00") + .expect("hex should decode"), + hex::decode("72") + .expect("hex should decode"), + ); +} + +#[test] +fn rfc8032_test_3_expanded() { + rfc8032_test_case( + Sha512::digest(hex::decode("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7") + .expect("hex should decode").as_slice()).to_vec(), + hex::decode("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025") + .expect("hex should decode"), + hex::decode("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a") + .expect("hex should decode"), + hex::decode("af82") + .expect("hex should decode"), + ); +} diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs index 720b711..d89af9f 100644 --- a/tests/unit_tests.rs +++ b/tests/unit_tests.rs @@ -14,27 +14,27 @@ fn parsing() { // Most of these types don't implement Eq, so we check a round trip // conversion to bytes, using these as the reference points: - let sk_array: [u8; 32] = sk.clone().into(); + let sk_array: [u8; 64] = sk.clone().into(); let pk_array: [u8; 32] = pk.into(); let pkb_array: [u8; 32] = pkb.into(); let sig_array: [u8; 64] = sig.into(); - let sk2 = SigningKey::try_from(sk.as_ref()).unwrap(); + let sk2 = SigningKey::try_from(sk.clone()).unwrap(); let pk2 = VerificationKey::try_from(pk.as_ref()).unwrap(); let pkb2 = VerificationKeyBytes::try_from(pkb.as_ref()).unwrap(); let sig2 = Signature::try_from(<[u8; 64]>::from(sig).as_ref()).unwrap(); - assert_eq!(&sk_array[..], sk2.as_ref()); + assert_eq!(sk_array, Into::<[u8; 64]>::into(sk2)); assert_eq!(&pk_array[..], pk2.as_ref()); assert_eq!(&pkb_array[..], pkb2.as_ref()); assert_eq!(&sig_array[..], <[u8; 64]>::from(sig2).as_ref()); - let sk3: SigningKey = bincode::deserialize(sk.as_ref()).unwrap(); + let sk3: SigningKey = bincode::deserialize(Into::<[u8; 64]>::into(sk).as_ref()).unwrap(); let pk3: VerificationKey = bincode::deserialize(pk.as_ref()).unwrap(); let pkb3: VerificationKeyBytes = bincode::deserialize(pkb.as_ref()).unwrap(); let sig3: Signature = bincode::deserialize(<[u8; 64]>::from(sig).as_ref()).unwrap(); - assert_eq!(&sk_array[..], sk3.as_ref()); + assert_eq!(sk_array, Into::<[u8; 64]>::into(sk3)); assert_eq!(&pk_array[..], pk3.as_ref()); assert_eq!(&pkb_array[..], pkb3.as_ref()); assert_eq!(&sig_array[..], <[u8; 64]>::from(sig3).as_ref()); From 09780abfee8e1b7b22daf17f09b7f77016659d98 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Sun, 7 Apr 2024 22:45:12 -0400 Subject: [PATCH 2/4] no_std compatibility restored --- Cargo.toml | 4 ++-- src/signing_key.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71583b1..9fad45f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ hex = { version = "0.4", default-features = false, features = ["alloc"] } sha2 = { version = "0.9", default-features = false } rand_core = { version = "0.6", default-features = false } curve25519-dalek = { package = "curve25519-dalek-ng", version = "4.1", default-features = false, features = ["u64_backend", "alloc"] } -serde = { version = "1", optional = true, features = ["derive"] } +serde = { version = "1", default-features = false, features = ["derive"] } zeroize = { version = "1.1", default-features = false } thiserror = { version = "1", optional = true } @@ -30,7 +30,7 @@ once_cell = "1.4" [features] std = ["thiserror"] -default = ["serde", "std"] +default = ["std"] [[test]] name = "rfc8032" diff --git a/src/signing_key.rs b/src/signing_key.rs index 0a5f593..f1cffa7 100644 --- a/src/signing_key.rs +++ b/src/signing_key.rs @@ -1,5 +1,5 @@ use core::convert::TryFrom; -use std::fmt::Formatter; +use core::fmt::Formatter; use curve25519_dalek::{constants, scalar::Scalar}; use rand_core::{CryptoRng, RngCore}; @@ -55,7 +55,7 @@ impl<'de> Deserialize<'de> for SigningKey { impl<'de> Visitor<'de> for TupleVisitor { type Value = SigningKey; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { formatter.write_str("an Ed25519 seed key or expanded secret key") } From cac1761e8e73e0e61cc9f49ae19bad195a49f1ad Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Sun, 7 Apr 2024 22:57:11 -0400 Subject: [PATCH 3/4] Revert "no_std compatibility restored" This reverts commit 09780abfee8e1b7b22daf17f09b7f77016659d98. --- Cargo.toml | 4 ++-- src/signing_key.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9fad45f..71583b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ hex = { version = "0.4", default-features = false, features = ["alloc"] } sha2 = { version = "0.9", default-features = false } rand_core = { version = "0.6", default-features = false } curve25519-dalek = { package = "curve25519-dalek-ng", version = "4.1", default-features = false, features = ["u64_backend", "alloc"] } -serde = { version = "1", default-features = false, features = ["derive"] } +serde = { version = "1", optional = true, features = ["derive"] } zeroize = { version = "1.1", default-features = false } thiserror = { version = "1", optional = true } @@ -30,7 +30,7 @@ once_cell = "1.4" [features] std = ["thiserror"] -default = ["std"] +default = ["serde", "std"] [[test]] name = "rfc8032" diff --git a/src/signing_key.rs b/src/signing_key.rs index f1cffa7..0a5f593 100644 --- a/src/signing_key.rs +++ b/src/signing_key.rs @@ -1,5 +1,5 @@ use core::convert::TryFrom; -use core::fmt::Formatter; +use std::fmt::Formatter; use curve25519_dalek::{constants, scalar::Scalar}; use rand_core::{CryptoRng, RngCore}; @@ -55,7 +55,7 @@ impl<'de> Deserialize<'de> for SigningKey { impl<'de> Visitor<'de> for TupleVisitor { type Value = SigningKey; - fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { formatter.write_str("an Ed25519 seed key or expanded secret key") } From 9a4e09091e02bd91849e1d894f449271217a3bb5 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Sun, 7 Apr 2024 22:58:27 -0400 Subject: [PATCH 4/4] removed fmt libraries --- src/signing_key.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/signing_key.rs b/src/signing_key.rs index 0a5f593..f1cffa7 100644 --- a/src/signing_key.rs +++ b/src/signing_key.rs @@ -1,5 +1,5 @@ use core::convert::TryFrom; -use std::fmt::Formatter; +use core::fmt::Formatter; use curve25519_dalek::{constants, scalar::Scalar}; use rand_core::{CryptoRng, RngCore}; @@ -55,7 +55,7 @@ impl<'de> Deserialize<'de> for SigningKey { impl<'de> Visitor<'de> for TupleVisitor { type Value = SigningKey; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { formatter.write_str("an Ed25519 seed key or expanded secret key") }