ironfish_reddsa/
verification_key.rs

1// -*- mode: rust; -*-
2//
3// This file is part of reddsa.
4// Copyright (c) 2019-2021 Zcash Foundation
5// See LICENSE for licensing information.
6//
7// Authors:
8// - Deirdre Connolly <deirdre@zfnd.org>
9// - Henry de Valence <hdevalence@hdevalence.ca>
10
11use core::{
12    convert::{TryFrom, TryInto},
13    fmt,
14    hash::Hash,
15    marker::PhantomData,
16};
17
18use group::{cofactor::CofactorGroup, ff::PrimeField, GroupEncoding};
19
20use crate::{hex_if_possible, Error, Randomizer, SigType, Signature, SpendAuth};
21
22/// A refinement type for `[u8; 32]` indicating that the bytes represent
23/// an encoding of a RedDSA verification key.
24///
25/// This is useful for representing a compressed verification key; the
26/// [`VerificationKey`] type in this library holds other decompressed state
27/// used in signature verification.
28#[derive(Copy, Clone, Hash, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct VerificationKeyBytes<T: SigType> {
31    pub(crate) bytes: [u8; 32],
32    pub(crate) _marker: PhantomData<T>,
33}
34
35impl<T: SigType> fmt::Debug for VerificationKeyBytes<T> {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("VerificationKeyBytes")
38            .field("bytes", &hex_if_possible(&self.bytes))
39            .finish()
40    }
41}
42
43impl<T: SigType> From<[u8; 32]> for VerificationKeyBytes<T> {
44    fn from(bytes: [u8; 32]) -> VerificationKeyBytes<T> {
45        VerificationKeyBytes {
46            bytes,
47            _marker: PhantomData,
48        }
49    }
50}
51
52impl<T: SigType> From<VerificationKeyBytes<T>> for [u8; 32] {
53    fn from(refined: VerificationKeyBytes<T>) -> [u8; 32] {
54        refined.bytes
55    }
56}
57
58/// A valid RedDSA verification key.
59///
60/// This type holds decompressed state used in signature verification; if the
61/// verification key may not be used immediately, it is probably better to use
62/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`.
63///
64/// ## Consensus properties
65///
66/// The `TryFrom<VerificationKeyBytes>` conversion performs the following Zcash
67/// consensus rule checks:
68///
69/// 1. The check that the bytes are a canonical encoding of a verification key;
70/// 2. The check that the verification key is not a point of small order.
71#[derive(PartialEq, Copy, Clone, Debug)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes<T>"))]
74#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes<T>"))]
75#[cfg_attr(feature = "serde", serde(bound = "T: SigType"))]
76pub struct VerificationKey<T: SigType> {
77    pub(crate) point: T::Point,
78    pub(crate) bytes: VerificationKeyBytes<T>,
79}
80
81impl<T: SigType> From<VerificationKey<T>> for VerificationKeyBytes<T> {
82    fn from(pk: VerificationKey<T>) -> VerificationKeyBytes<T> {
83        pk.bytes
84    }
85}
86
87impl<T: SigType> From<VerificationKey<T>> for [u8; 32] {
88    fn from(pk: VerificationKey<T>) -> [u8; 32] {
89        pk.bytes.bytes
90    }
91}
92
93impl<T: SigType> TryFrom<VerificationKeyBytes<T>> for VerificationKey<T> {
94    type Error = Error;
95
96    fn try_from(bytes: VerificationKeyBytes<T>) -> Result<Self, Self::Error> {
97        // XXX-jubjub: this should not use CtOption
98        // XXX-jubjub: this takes ownership of bytes, while Fr doesn't.
99        // This checks that the encoding is canonical...
100        let mut repr = <T::Point as GroupEncoding>::Repr::default();
101        repr.as_mut().copy_from_slice(&bytes.bytes);
102        let maybe_point = T::Point::from_bytes(&repr);
103        if maybe_point.is_some().into() {
104            let point = maybe_point.unwrap();
105            // Note that small-order verification keys (including the identity) are not
106            // rejected here. Previously they were rejected, but this was a bug as the
107            // RedDSA specification allows them. Zcash Sapling rejects small-order points
108            // for the RedJubjub spend authorization key rk; this now occurs separately.
109            // Meanwhile, Zcash Orchard uses a prime-order group, so the only small-order
110            // point would be the identity, which is allowed in Orchard.
111            Ok(VerificationKey { point, bytes })
112        } else {
113            Err(Error::MalformedVerificationKey)
114        }
115    }
116}
117
118impl<T: SigType> TryFrom<[u8; 32]> for VerificationKey<T> {
119    type Error = Error;
120
121    fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
122        VerificationKeyBytes::from(bytes).try_into()
123    }
124}
125
126impl<T: SpendAuth> VerificationKey<T> {
127    /// Randomize this verification key with the given `randomizer`.
128    ///
129    /// Randomization is only supported for `SpendAuth` keys.
130    pub fn randomize(&self, randomizer: &Randomizer<T>) -> VerificationKey<T> {
131        let point = self.point + (T::basepoint() * randomizer);
132        let bytes = VerificationKeyBytes {
133            bytes: point.to_bytes().as_ref().try_into().unwrap(),
134            _marker: PhantomData,
135        };
136        VerificationKey { bytes, point }
137    }
138}
139
140impl<T: SigType> VerificationKey<T> {
141    pub(crate) fn from(s: &T::Scalar) -> VerificationKey<T> {
142        let point = T::basepoint() * s;
143        let bytes = VerificationKeyBytes {
144            bytes: point.to_bytes().as_ref().try_into().unwrap(),
145            _marker: PhantomData,
146        };
147        VerificationKey { bytes, point }
148    }
149
150    /// Verify a purported `signature` over `msg` made by this verification key.
151    // This is similar to impl signature::Verifier but without boxed errors
152    pub fn verify(&self, msg: &[u8], signature: &Signature<T>) -> Result<(), Error> {
153        use crate::HStar;
154        let c = HStar::<T>::default()
155            .update(&signature.r_bytes[..])
156            .update(&self.bytes.bytes[..]) // XXX ugly
157            .update(msg)
158            .finalize();
159        self.verify_prehashed(signature, c)
160    }
161
162    /// Verify a purported `signature` with a prehashed challenge.
163    #[allow(non_snake_case)]
164    pub(crate) fn verify_prehashed(
165        &self,
166        signature: &Signature<T>,
167        c: T::Scalar,
168    ) -> Result<(), Error> {
169        let r = {
170            // XXX-jubjub: should not use CtOption here
171            // XXX-jubjub: inconsistent ownership in from_bytes
172            let mut repr = <T::Point as GroupEncoding>::Repr::default();
173            repr.as_mut().copy_from_slice(&signature.r_bytes);
174            let maybe_point = T::Point::from_bytes(&repr);
175            if maybe_point.is_some().into() {
176                maybe_point.unwrap()
177            } else {
178                return Err(Error::InvalidSignature);
179            }
180        };
181
182        let s = {
183            // XXX-jubjub: should not use CtOption here
184            let mut repr = <T::Scalar as PrimeField>::Repr::default();
185            repr.as_mut().copy_from_slice(&signature.s_bytes);
186            let maybe_scalar = T::Scalar::from_repr(repr);
187            if maybe_scalar.is_some().into() {
188                maybe_scalar.unwrap()
189            } else {
190                return Err(Error::InvalidSignature);
191            }
192        };
193
194        // XXX rewrite as normal double scalar mul
195        // Verify check is h * ( - s * B + R  + c * A) == 0
196        //                 h * ( s * B - c * A - R) == 0
197        let sB = T::basepoint() * s;
198        let cA = self.point * c;
199        let check = sB - cA - r;
200
201        if check.is_small_order().into() {
202            Ok(())
203        } else {
204            Err(Error::InvalidSignature)
205        }
206    }
207}