Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded secret key #12

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 78 additions & 50 deletions src/signing_key.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,85 @@
use core::convert::TryFrom;
use core::fmt::Formatter;

use curve25519_dalek::{constants, scalar::Scalar};
use rand_core::{CryptoRng, RngCore};
use serde::de::{self, Visitor};

Check failure on line 6 in src/signing_key.rs

View workflow job for this annotation

GitHub Actions / Build on no_std

failed to resolve: use of undeclared crate or module `serde`

Check failure on line 6 in src/signing_key.rs

View workflow job for this annotation

GitHub Actions / Build on no_std

unresolved import `serde`
use serde::ser::SerializeTuple;

Check failure on line 7 in src/signing_key.rs

View workflow job for this annotation

GitHub Actions / Build on no_std

failed to resolve: use of undeclared crate or module `serde`
use serde::{Deserialize, Deserializer, Serialize, Serializer};

Check failure on line 8 in src/signing_key.rs

View workflow job for this annotation

GitHub Actions / Build on no_std

unresolved import `serde`
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct TupleVisitor;

impl<'de> Visitor<'de> for TupleVisitor {
type Value = SigningKey;

fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
formatter.write_str("an Ed25519 seed key or expanded secret key")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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)
Expand All @@ -62,37 +99,25 @@
}
}

impl AsRef<[u8]> for SigningKey {
fn as_ref(&self) -> &[u8] {
&self.seed[..]
}
}

impl From<SigningKey> 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<SigningKey, Error> {
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];
Expand All @@ -114,7 +139,6 @@
let A = &s * &constants::ED25519_BASEPOINT_TABLE;

SigningKey {
seed,
s,
prefix,
vk: VerificationKey {
Expand All @@ -125,25 +149,29 @@
}
}

impl zeroize::Zeroize for SigningKey {
fn zeroize(&mut self) {
self.seed.zeroize();
self.s.zeroize()
impl From<SigningKey> 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<SerdeHelper> for SigningKey {
fn from(helper: SerdeHelper) -> SigningKey {
helper.0.into()
bytes.into()
}
}

impl From<SigningKey> for SerdeHelper {
fn from(sk: SigningKey) -> Self {
Self(sk.into())
impl zeroize::Zeroize for SigningKey {
fn zeroize(&mut self) {
self.s.zeroize()
}
}

Expand Down
54 changes: 53 additions & 1 deletion tests/rfc8032.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>, pk_bytes: Vec<u8>, sig_bytes: Vec<u8>, msg: Vec<u8>) {
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");

Expand Down Expand Up @@ -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"),
);
}
10 changes: 5 additions & 5 deletions tests/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading