2020-07-06 19:05:51 -07:00
|
|
|
use std::convert::{TryFrom, TryInto};
|
2020-01-22 14:57:26 -08:00
|
|
|
|
2020-01-22 16:58:18 -08:00
|
|
|
use curve25519_dalek::{
|
|
|
|
edwards::{CompressedEdwardsY, EdwardsPoint},
|
|
|
|
scalar::Scalar,
|
|
|
|
};
|
|
|
|
use sha2::{Digest, Sha512};
|
2020-01-22 14:57:26 -08:00
|
|
|
|
2020-06-15 13:27:36 -07:00
|
|
|
use crate::{constants, Error, Signature};
|
2020-01-22 14:57:26 -08:00
|
|
|
|
2020-01-24 12:55:43 -08:00
|
|
|
/// A refinement type for `[u8; 32]` indicating that the bytes represent an
|
2020-06-15 20:45:25 -07:00
|
|
|
/// encoding of an Ed25519 verification key.
|
2020-01-22 14:57:26 -08:00
|
|
|
///
|
2020-06-15 20:45:25 -07:00
|
|
|
/// This is useful for representing an encoded verification key, while the
|
|
|
|
/// [`VerificationKey`] type in this library caches other decoded state used in
|
2020-01-24 12:55:43 -08:00
|
|
|
/// signature verification.
|
|
|
|
///
|
2020-06-15 20:45:25 -07:00
|
|
|
/// A `VerificationKeyBytes` can be used to verify a single signature using the
|
2020-01-24 12:55:43 -08:00
|
|
|
/// following idiom:
|
|
|
|
/// ```
|
|
|
|
/// use std::convert::TryFrom;
|
|
|
|
/// # use rand::thread_rng;
|
|
|
|
/// # use ed25519_zebra::*;
|
|
|
|
/// # let msg = b"Zcash";
|
2020-06-15 20:45:25 -07:00
|
|
|
/// # let sk = SigningKey::new(thread_rng());
|
2020-01-24 12:55:43 -08:00
|
|
|
/// # let sig = sk.sign(msg);
|
2020-06-15 20:45:25 -07:00
|
|
|
/// # let vk_bytes = VerificationKeyBytes::from(&sk);
|
|
|
|
/// VerificationKey::try_from(vk_bytes)
|
|
|
|
/// .and_then(|vk| vk.verify(&sig, msg));
|
2020-01-24 12:55:43 -08:00
|
|
|
/// ```
|
2020-06-09 17:06:17 -07:00
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
2020-01-22 14:57:26 -08:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2020-06-15 20:45:25 -07:00
|
|
|
pub struct VerificationKeyBytes(pub(crate) [u8; 32]);
|
2020-01-22 14:57:26 -08:00
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl core::fmt::Debug for VerificationKeyBytes {
|
2020-06-09 17:06:17 -07:00
|
|
|
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
2020-06-15 20:45:25 -07:00
|
|
|
fmt.debug_tuple("VerificationKeyBytes")
|
2020-06-09 17:06:17 -07:00
|
|
|
.field(&hex::encode(&self.0))
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl AsRef<[u8]> for VerificationKeyBytes {
|
2020-04-01 18:50:56 -07:00
|
|
|
fn as_ref(&self) -> &[u8] {
|
|
|
|
&self.0[..]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 19:05:51 -07:00
|
|
|
impl TryFrom<&[u8]> for VerificationKeyBytes {
|
|
|
|
type Error = Error;
|
|
|
|
fn try_from(slice: &[u8]) -> Result<VerificationKeyBytes, Error> {
|
|
|
|
if slice.len() == 32 {
|
|
|
|
let mut bytes = [0u8; 32];
|
|
|
|
bytes[..].copy_from_slice(slice);
|
|
|
|
Ok(bytes.into())
|
|
|
|
} else {
|
|
|
|
Err(Error::InvalidSliceLength)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl From<[u8; 32]> for VerificationKeyBytes {
|
|
|
|
fn from(bytes: [u8; 32]) -> VerificationKeyBytes {
|
|
|
|
VerificationKeyBytes(bytes)
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl From<VerificationKeyBytes> for [u8; 32] {
|
|
|
|
fn from(refined: VerificationKeyBytes) -> [u8; 32] {
|
2020-01-22 14:57:26 -08:00
|
|
|
refined.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
/// A valid Ed25519 verification key.
|
|
|
|
///
|
|
|
|
/// This is also called a public key by other implementations.
|
2020-01-22 14:57:26 -08:00
|
|
|
///
|
|
|
|
/// This type holds decompressed state used in signature verification; if the
|
2020-06-15 20:45:25 -07:00
|
|
|
/// verification key may not be used immediately, it is probably better to use
|
|
|
|
/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`.
|
2020-01-22 14:57:26 -08:00
|
|
|
///
|
2020-01-24 12:33:51 -08:00
|
|
|
/// ## Zcash-specific consensus properties
|
2020-01-22 14:57:26 -08:00
|
|
|
///
|
2020-01-24 12:33:51 -08:00
|
|
|
/// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol
|
|
|
|
/// specification. However, it is not clear that the protocol specification
|
|
|
|
/// matches the implementation in `libsodium` `1.0.15` used by `zcashd`. Note
|
|
|
|
/// that the precise version is important because `libsodium` changed validation
|
|
|
|
/// rules in point releases.
|
2020-01-22 14:57:26 -08:00
|
|
|
///
|
2020-06-15 20:45:25 -07:00
|
|
|
/// The spec says that a verification key `A` is
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
|
|
|
/// > a point of order `l` on the Ed25519 curve, in the encoding specified by…
|
|
|
|
///
|
|
|
|
/// but `libsodium` `1.0.15` does not check this. Instead it only checks whether
|
|
|
|
/// the encoding of `A` is an encoding of a point on the curve and that the
|
|
|
|
/// encoding is not all zeros. This implementation matches the `libsodium`
|
|
|
|
/// behavior. This has implications for signature verification behaviour, as noted
|
2020-06-15 20:45:25 -07:00
|
|
|
/// in the [`VerificationKey::verify`] documentation.
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
|
|
|
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concretejssig
|
2020-01-22 14:57:26 -08:00
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2020-06-15 20:45:25 -07:00
|
|
|
#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))]
|
|
|
|
#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))]
|
2020-01-22 17:02:45 -08:00
|
|
|
#[allow(non_snake_case)]
|
2020-06-15 20:45:25 -07:00
|
|
|
pub struct VerificationKey {
|
|
|
|
pub(crate) A_bytes: VerificationKeyBytes,
|
2020-01-22 17:02:45 -08:00
|
|
|
pub(crate) minus_A: EdwardsPoint,
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl From<VerificationKey> for VerificationKeyBytes {
|
|
|
|
fn from(vk: VerificationKey) -> VerificationKeyBytes {
|
|
|
|
vk.A_bytes
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl AsRef<[u8]> for VerificationKey {
|
2020-04-01 19:11:29 -07:00
|
|
|
fn as_ref(&self) -> &[u8] {
|
|
|
|
&self.A_bytes.0[..]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl From<VerificationKey> for [u8; 32] {
|
|
|
|
fn from(vk: VerificationKey) -> [u8; 32] {
|
|
|
|
vk.A_bytes.0
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl TryFrom<VerificationKeyBytes> for VerificationKey {
|
2020-01-22 14:57:26 -08:00
|
|
|
type Error = Error;
|
2020-01-22 17:02:45 -08:00
|
|
|
#[allow(non_snake_case)]
|
2020-06-15 20:45:25 -07:00
|
|
|
fn try_from(bytes: VerificationKeyBytes) -> Result<Self, Self::Error> {
|
2020-06-15 13:27:36 -07:00
|
|
|
// libsodium behavior: public key bytes must not be all 0
|
|
|
|
// libsodium 1.0.15 crypto_sign/ed25519/ref10/open.c:138-143
|
|
|
|
// Note: this is different from the description in the spec.
|
2020-01-24 12:33:51 -08:00
|
|
|
if bytes.0 == [0; 32] {
|
|
|
|
return Err(Error::MalformedPublicKey);
|
|
|
|
}
|
|
|
|
|
2020-01-22 17:02:45 -08:00
|
|
|
let A = CompressedEdwardsY(bytes.0)
|
2020-01-22 14:57:26 -08:00
|
|
|
.decompress()
|
|
|
|
.ok_or(Error::MalformedPublicKey)?;
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
Ok(VerificationKey {
|
2020-01-22 17:02:45 -08:00
|
|
|
A_bytes: bytes,
|
|
|
|
minus_A: -A,
|
|
|
|
})
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 19:05:51 -07:00
|
|
|
impl TryFrom<&[u8]> for VerificationKey {
|
|
|
|
type Error = Error;
|
|
|
|
fn try_from(slice: &[u8]) -> Result<VerificationKey, Error> {
|
|
|
|
VerificationKeyBytes::try_from(slice).and_then(|vkb| vkb.try_into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl TryFrom<[u8; 32]> for VerificationKey {
|
2020-01-22 14:57:26 -08:00
|
|
|
type Error = Error;
|
|
|
|
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
|
2020-06-15 20:45:25 -07:00
|
|
|
VerificationKeyBytes::from(bytes).try_into()
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:45:25 -07:00
|
|
|
impl VerificationKey {
|
2020-01-22 14:57:26 -08:00
|
|
|
/// Verify a purported `signature` on the given `msg`.
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
|
|
|
/// ## Zcash-specific consensus properties
|
|
|
|
///
|
|
|
|
/// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol
|
2020-06-15 13:27:36 -07:00
|
|
|
/// specification. Ed25519 validation is not well-specified, and the original
|
|
|
|
/// implementation of JoinSplit signatures for `zcashd` inherited its precise
|
|
|
|
/// validation rules from a specific build configuration of `libsodium`
|
|
|
|
/// `1.0.15`. Note that the precise version is important because `libsodium`
|
|
|
|
/// changed validation rules in point releases.
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
2020-06-15 13:27:36 -07:00
|
|
|
/// The additional validation checks are that:
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
2020-06-15 13:27:36 -07:00
|
|
|
/// * `s` MUST represent an integer less than the prime `l`, per `libsodium`
|
|
|
|
/// `1.0.15` `crypto_sign/ed25519/ref10/open.c:126`;
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
2020-06-15 13:27:36 -07:00
|
|
|
/// * `R` MUST NOT be one of the excluded encodings, per `libsodium` `1.0.15`
|
|
|
|
/// `crypto_sign/ed25519/ref10/open.c:127`;
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
2020-06-15 13:27:36 -07:00
|
|
|
/// * The public key bytes must not be all 0, per `libsodium` `1.0.15`
|
|
|
|
/// `crypto_sign/ed25519/ref10/open.c:138-143`, which we maintain as an
|
2020-06-15 20:45:25 -07:00
|
|
|
/// invariant on the `VerificationKey` type.
|
2020-01-24 12:33:51 -08:00
|
|
|
///
|
|
|
|
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concretejssig
|
2020-01-22 16:58:18 -08:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
|
2020-01-24 12:33:51 -08:00
|
|
|
// Zcash consensus rule: `s` MUST represent an integer less than the prime `l`.
|
2020-06-15 13:27:36 -07:00
|
|
|
// libsodium 1.0.15 crypto_sign/ed25519/ref10/open.c:126
|
2020-01-22 16:58:18 -08:00
|
|
|
let s = Scalar::from_canonical_bytes(signature.s_bytes).ok_or(Error::InvalidSignature)?;
|
|
|
|
|
2020-06-15 13:27:36 -07:00
|
|
|
// Zcash consensus rule: `R` MUST NOT be one of the encodings excluded by libsodium 1.0.15
|
|
|
|
// libsodium 1.0.15 crypto_sign/ed25519/ref10/open.c:127
|
|
|
|
for excluded in &constants::EXCLUDED_POINT_ENCODINGS {
|
|
|
|
if &signature.R_bytes == excluded {
|
|
|
|
return Err(Error::InvalidSignature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// libsodium behavior: public key bytes must not be all 0
|
|
|
|
// libsodium 1.0.15 crypto_sign/ed25519/ref10/open.c:138-143
|
|
|
|
// Note: this is different from the description in the spec.
|
|
|
|
// No-op, since we maintain this invariant in the PublicKey type.
|
|
|
|
|
2020-01-22 16:58:18 -08:00
|
|
|
let k = Scalar::from_hash(
|
|
|
|
Sha512::default()
|
|
|
|
.chain(&signature.R_bytes[..])
|
2020-01-22 17:02:45 -08:00
|
|
|
.chain(&self.A_bytes.0[..])
|
2020-01-22 16:58:18 -08:00
|
|
|
.chain(msg),
|
|
|
|
);
|
|
|
|
|
2020-01-22 17:02:45 -08:00
|
|
|
// We expect to recompute R as [s]B - [k]A = [k](-A) + [s]B.
|
2020-01-24 12:33:51 -08:00
|
|
|
let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.minus_A, &s);
|
2020-01-22 16:58:18 -08:00
|
|
|
|
2020-01-24 12:33:51 -08:00
|
|
|
if R.compress().to_bytes() == signature.R_bytes {
|
2020-01-22 16:58:18 -08:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::InvalidSignature)
|
|
|
|
}
|
2020-01-22 14:57:26 -08:00
|
|
|
}
|
|
|
|
}
|