mirror of https://github.com/zcash/halo2.git
commit
1182d8d5a7
|
@ -44,6 +44,10 @@ rev = "d04b532368d05b505e622f8cac4c0693574fbd93"
|
|||
git = "https://github.com/str4d/redjubjub.git"
|
||||
rev = "d5d8c5f3bb704bad8ae88fe4a29ae1f744774cb2"
|
||||
|
||||
[dependencies.zcash_note_encryption]
|
||||
git = "https://github.com/zcash/librustzcash.git"
|
||||
rev = "cc533a9da4f6a7209a7be05f82b12a03969152c9"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
hex = "0.4"
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
|
||||
/// let address = FullViewingKey::from(&sk).default_address();
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Address {
|
||||
d: Diversifier,
|
||||
pk_d: DiversifiedTransmissionKey,
|
||||
|
@ -28,6 +28,10 @@ impl Address {
|
|||
Address { d, pk_d }
|
||||
}
|
||||
|
||||
pub(crate) fn diversifer(&self) -> Diversifier {
|
||||
self.d
|
||||
}
|
||||
|
||||
pub(crate) fn g_d(&self) -> NonIdentityPallasPoint {
|
||||
diversify_hash(self.d.as_array())
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use crate::{
|
|||
FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey, SpendingKey,
|
||||
},
|
||||
note::{Note, TransmittedNoteCiphertext},
|
||||
note_encryption::OrchardNoteEncryption,
|
||||
primitives::redpallas::{self, Binding, SpendAuth},
|
||||
tree::{Anchor, MerklePath},
|
||||
value::{self, NoteValue, OverflowError, ValueCommitTrapdoor, ValueCommitment, ValueSum},
|
||||
|
@ -79,7 +80,7 @@ struct RecipientInfo {
|
|||
ovk: Option<OutgoingViewingKey>,
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
memo: Option<()>,
|
||||
memo: Option<[u8; 512]>,
|
||||
}
|
||||
|
||||
impl RecipientInfo {
|
||||
|
@ -135,21 +136,32 @@ impl ActionInfo {
|
|||
let alpha = pallas::Scalar::random(&mut rng);
|
||||
let rk = ak.randomize(&alpha);
|
||||
|
||||
let note = Note::new(self.output.recipient, self.output.value, nf_old, rng);
|
||||
let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng);
|
||||
let cm_new = note.commitment();
|
||||
let cmx = cm_new.into();
|
||||
|
||||
let encryptor = OrchardNoteEncryption::new(
|
||||
self.output.ovk,
|
||||
note,
|
||||
self.output.recipient,
|
||||
self.output.memo.unwrap_or_else(|| {
|
||||
let mut memo = [0; 512];
|
||||
memo[0] = 0xf6;
|
||||
memo
|
||||
}),
|
||||
);
|
||||
|
||||
// TODO: Note encryption
|
||||
let encrypted_note = TransmittedNoteCiphertext {
|
||||
epk_bytes: [0u8; 32],
|
||||
enc_ciphertext: [0u8; 580],
|
||||
out_ciphertext: [0u8; 80],
|
||||
epk_bytes: encryptor.epk().to_bytes().0,
|
||||
enc_ciphertext: encryptor.encrypt_note_plaintext(),
|
||||
out_ciphertext: encryptor.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut rng),
|
||||
};
|
||||
|
||||
(
|
||||
Action::from_parts(
|
||||
nf_old,
|
||||
rk,
|
||||
cm_new.into(),
|
||||
cmx,
|
||||
encrypted_note,
|
||||
cv_net,
|
||||
SigningMetadata {
|
||||
|
@ -220,7 +232,7 @@ impl Builder {
|
|||
ovk: Option<OutgoingViewingKey>,
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
memo: Option<()>,
|
||||
memo: Option<[u8; 512]>,
|
||||
) -> Result<(), &'static str> {
|
||||
if !self.flags.outputs_enabled() {
|
||||
return Err("Outputs are not enabled for this builder");
|
||||
|
|
192
src/keys.rs
192
src/keys.rs
|
@ -4,12 +4,15 @@ use std::convert::TryInto;
|
|||
use std::mem;
|
||||
|
||||
use aes::Aes256;
|
||||
use blake2b_simd::{Hash as Blake2bHash, Params};
|
||||
use fpe::ff1::{BinaryNumeralString, FF1};
|
||||
use group::GroupEncoding;
|
||||
use halo2::arithmetic::FieldExt;
|
||||
use pasta_curves::pallas;
|
||||
use rand::RngCore;
|
||||
use subtle::ConstantTimeEq;
|
||||
use subtle::CtOption;
|
||||
use zcash_note_encryption::EphemeralKeyBytes;
|
||||
|
||||
use crate::{
|
||||
address::Address,
|
||||
|
@ -20,6 +23,8 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
|
||||
|
||||
/// A spending key, from which all key material is derived.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
|
@ -286,10 +291,14 @@ impl DiversifierKey {
|
|||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Diversifier([u8; 11]);
|
||||
|
||||
impl Diversifier {
|
||||
pub(crate) fn from_bytes(d: [u8; 11]) -> Self {
|
||||
Diversifier(d)
|
||||
}
|
||||
|
||||
/// Returns the byte array corresponding to this diversifier.
|
||||
pub fn as_array(&self) -> &[u8; 11] {
|
||||
&self.0
|
||||
|
@ -334,7 +343,7 @@ impl KeyAgreementPrivateKey {
|
|||
|
||||
/// Returns the payment address for this key corresponding to the given diversifier.
|
||||
fn address(&self, d: Diversifier) -> Address {
|
||||
let pk_d = DiversifiedTransmissionKey::derive(self, &d);
|
||||
let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d);
|
||||
Address::from_parts(d, pk_d)
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +376,16 @@ impl From<&FullViewingKey> for IncomingViewingKey {
|
|||
}
|
||||
|
||||
impl IncomingViewingKey {
|
||||
/// Parses an Orchard incoming viewing key from its raw encoding.
|
||||
pub fn from_bytes(bytes: &[u8; 64]) -> CtOption<Self> {
|
||||
NonZeroPallasBase::from_bytes(bytes[32..].try_into().unwrap()).map(|ivk| {
|
||||
IncomingViewingKey {
|
||||
dk: DiversifierKey(bytes[..32].try_into().unwrap()),
|
||||
ivk: KeyAgreementPrivateKey(ivk.into()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the default payment address for this key.
|
||||
pub fn default_address(&self) -> Address {
|
||||
self.address(self.dk.default_diversifier())
|
||||
|
@ -401,35 +420,145 @@ impl From<&FullViewingKey> for OutgoingViewingKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for OutgoingViewingKey {
|
||||
fn from(ovk: [u8; 32]) -> Self {
|
||||
OutgoingViewingKey(ovk)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The diversified transmission key for a given payment address.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
||||
|
||||
impl DiversifiedTransmissionKey {
|
||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||
///
|
||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||
fn derive(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
|
||||
pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self {
|
||||
Self::derive_inner(&ivk.ivk, d)
|
||||
}
|
||||
|
||||
fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
|
||||
let g_d = diversify_hash(&d.as_array());
|
||||
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
|
||||
}
|
||||
|
||||
/// $abst_P(bytes)$
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
NonIdentityPallasPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
|
||||
}
|
||||
|
||||
/// $repr_P(self)$
|
||||
pub(crate) fn to_bytes(self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/// An ephemeral secret key used to encrypt an output note on-chain.
|
||||
///
|
||||
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
|
||||
/// practice, `esk` is derived deterministically from the note that it is encrypting.
|
||||
///
|
||||
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{Private} := \mathbb{F}^{\ast}_{r_P}$
|
||||
///
|
||||
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
|
||||
///
|
||||
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
|
||||
#[derive(Debug)]
|
||||
pub struct EphemeralSecretKey(pub(crate) NonZeroPallasScalar);
|
||||
|
||||
impl ConstantTimeEq for EphemeralSecretKey {
|
||||
fn ct_eq(&self, other: &Self) -> subtle::Choice {
|
||||
self.0.ct_eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl EphemeralSecretKey {
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
NonZeroPallasScalar::from_bytes(bytes).map(EphemeralSecretKey)
|
||||
}
|
||||
|
||||
pub(crate) fn derive_public(&self, g_d: NonIdentityPallasPoint) -> EphemeralPublicKey {
|
||||
EphemeralPublicKey(ka_orchard(&self.0, &g_d))
|
||||
}
|
||||
|
||||
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
|
||||
SharedSecret(ka_orchard(&self.0, &pk_d.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// An ephemeral public key used to encrypt an output note on-chain.
|
||||
///
|
||||
/// `epk` is "ephemeral" in the sense that each public key is only used once. In practice,
|
||||
/// `epk` is derived deterministically from the note that it is encrypting.
|
||||
///
|
||||
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{Public} := \mathbb{P}^{\ast}$
|
||||
///
|
||||
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
|
||||
///
|
||||
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
|
||||
#[derive(Debug)]
|
||||
pub struct EphemeralPublicKey(NonIdentityPallasPoint);
|
||||
|
||||
impl EphemeralPublicKey {
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
NonIdentityPallasPoint::from_bytes(bytes).map(EphemeralPublicKey)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> EphemeralKeyBytes {
|
||||
EphemeralKeyBytes(self.0.to_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn agree(&self, ivk: &IncomingViewingKey) -> SharedSecret {
|
||||
SharedSecret(ka_orchard(&ivk.ivk.0, &self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{SharedSecret} := \mathbb{P}^{\ast}$
|
||||
///
|
||||
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
|
||||
///
|
||||
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
|
||||
#[derive(Debug)]
|
||||
pub struct SharedSecret(NonIdentityPallasPoint);
|
||||
|
||||
impl SharedSecret {
|
||||
/// For checking test vectors only.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 5.4.5.6: Orchard Key Agreement][concreteorchardkdf].
|
||||
///
|
||||
/// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf
|
||||
pub(crate) fn kdf_orchard(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash {
|
||||
Params::new()
|
||||
.hash_length(32)
|
||||
.personal(KDF_ORCHARD_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(&self.0.to_bytes())
|
||||
.update(&ephemeral_key.0)
|
||||
.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generators for property testing.
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::SpendingKey;
|
||||
use super::{EphemeralSecretKey, SpendingKey};
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed fake note commitment value.
|
||||
|
@ -444,19 +573,64 @@ pub mod testing {
|
|||
key.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed fake note commitment value.
|
||||
pub fn arb_esk()(
|
||||
esk in prop::array::uniform32(prop::num::u8::ANY)
|
||||
.prop_map(|b| EphemeralSecretKey::from_bytes(&b))
|
||||
.prop_filter(
|
||||
"Values must correspond to valid Orchard ephemeral secret keys.",
|
||||
|opt| bool::from(opt.is_some())
|
||||
)
|
||||
) -> EphemeralSecretKey {
|
||||
esk.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ff::PrimeField;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
testing::{arb_esk, arb_spending_key},
|
||||
*,
|
||||
};
|
||||
use crate::{
|
||||
note::{ExtractedNoteCommitment, Nullifier},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
||||
value::NoteValue,
|
||||
Note,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn parsers_reject_invalid() {
|
||||
assert!(bool::from(
|
||||
EphemeralSecretKey::from_bytes(&[0xff; 32]).is_none()
|
||||
));
|
||||
assert!(bool::from(
|
||||
EphemeralPublicKey::from_bytes(&[0xff; 32]).is_none()
|
||||
));
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn key_agreement(
|
||||
sk in arb_spending_key(),
|
||||
esk in arb_esk(),
|
||||
) {
|
||||
let ivk = IncomingViewingKey::from(&(&sk).into());
|
||||
let addr = ivk.default_address();
|
||||
|
||||
let epk = esk.derive_public(addr.g_d());
|
||||
|
||||
assert!(bool::from(
|
||||
esk.agree(addr.pk_d()).0.ct_eq(&epk.agree(&ivk).0)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
for tv in crate::test_vectors::keys::test_vectors() {
|
||||
|
@ -492,7 +666,7 @@ mod tests {
|
|||
addr,
|
||||
NoteValue::from_raw(tv.note_v),
|
||||
rho,
|
||||
tv.note_rseed.into(),
|
||||
RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(),
|
||||
);
|
||||
|
||||
let cmx: ExtractedNoteCommitment = note.commitment().into();
|
||||
|
|
|
@ -23,6 +23,7 @@ mod circuit;
|
|||
mod constants;
|
||||
pub mod keys;
|
||||
pub mod note;
|
||||
mod note_encryption;
|
||||
pub mod primitives;
|
||||
mod spec;
|
||||
mod tree;
|
||||
|
|
75
src/note.rs
75
src/note.rs
|
@ -5,8 +5,8 @@ use rand::RngCore;
|
|||
use subtle::CtOption;
|
||||
|
||||
use crate::{
|
||||
keys::{FullViewingKey, SpendingKey},
|
||||
spec::{to_base, to_scalar, PrfExpand},
|
||||
keys::{EphemeralSecretKey, FullViewingKey, SpendingKey},
|
||||
spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand},
|
||||
value::NoteValue,
|
||||
Address,
|
||||
};
|
||||
|
@ -21,17 +21,26 @@ pub use self::nullifier::Nullifier;
|
|||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RandomSeed([u8; 32]);
|
||||
|
||||
impl From<[u8; 32]> for RandomSeed {
|
||||
fn from(rseed: [u8; 32]) -> Self {
|
||||
RandomSeed(rseed)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomSeed {
|
||||
pub(crate) fn random(rng: &mut impl RngCore) -> Self {
|
||||
let mut bytes = [0; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
RandomSeed(bytes)
|
||||
pub(crate) fn random(rng: &mut impl RngCore, rho: &Nullifier) -> Self {
|
||||
loop {
|
||||
let mut bytes = [0; 32];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
let rseed = RandomSeed::from_bytes(bytes, rho);
|
||||
if rseed.is_some().into() {
|
||||
break rseed.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(rseed: [u8; 32], rho: &Nullifier) -> CtOption<Self> {
|
||||
let rseed = RandomSeed(rseed);
|
||||
let esk = rseed.esk_inner(rho);
|
||||
CtOption::new(rseed, esk.is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
||||
|
@ -44,8 +53,18 @@ impl RandomSeed {
|
|||
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
||||
///
|
||||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||
fn esk(&self, rho: &Nullifier) -> pallas::Scalar {
|
||||
to_scalar(PrfExpand::Esk.with_ad(&self.0, &rho.to_bytes()[..]))
|
||||
fn esk_inner(&self, rho: &Nullifier) -> CtOption<NonZeroPallasScalar> {
|
||||
NonZeroPallasScalar::from_scalar(to_scalar(
|
||||
PrfExpand::Esk.with_ad(&self.0, &rho.to_bytes()[..]),
|
||||
))
|
||||
}
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
||||
///
|
||||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||
fn esk(&self, rho: &Nullifier) -> NonZeroPallasScalar {
|
||||
// We can't construct a RandomSeed for which this unwrap fails.
|
||||
self.esk_inner(rho).unwrap()
|
||||
}
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
|
||||
|
@ -76,8 +95,17 @@ pub struct Note {
|
|||
rseed: RandomSeed,
|
||||
}
|
||||
|
||||
impl PartialEq for Note {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Notes are canonically defined by their commitments.
|
||||
ExtractedNoteCommitment::from(self.commitment())
|
||||
.eq(&ExtractedNoteCommitment::from(other.commitment()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Note {}
|
||||
|
||||
impl Note {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_parts(
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
|
@ -108,7 +136,7 @@ impl Note {
|
|||
recipient,
|
||||
value,
|
||||
rho,
|
||||
rseed: RandomSeed::random(&mut rng),
|
||||
rseed: RandomSeed::random(&mut rng, &rho),
|
||||
};
|
||||
if note.commitment_inner().is_some().into() {
|
||||
break note;
|
||||
|
@ -139,11 +167,26 @@ impl Note {
|
|||
(sk, fvk, note)
|
||||
}
|
||||
|
||||
/// Returns the recipient of this note.
|
||||
pub fn recipient(&self) -> Address {
|
||||
self.recipient
|
||||
}
|
||||
|
||||
/// Returns the value of this note.
|
||||
pub fn value(&self) -> NoteValue {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Derives the ephemeral secret key for this note.
|
||||
pub(crate) fn rseed(&self) -> &RandomSeed {
|
||||
&self.rseed
|
||||
}
|
||||
|
||||
/// Derives the ephemeral secret key for this note.
|
||||
pub(crate) fn esk(&self) -> EphemeralSecretKey {
|
||||
EphemeralSecretKey(self.rseed.esk(&self.rho))
|
||||
}
|
||||
|
||||
/// Derives the commitment to this note.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::iter;
|
|||
use bitvec::{array::BitArray, order::Lsb0};
|
||||
use ff::PrimeFieldBits;
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
use subtle::CtOption;
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
|
||||
use crate::{constants::L_ORCHARD_BASE, primitives::sinsemilla, spec::extract_p, value::NoteValue};
|
||||
|
||||
|
@ -71,3 +71,23 @@ impl std::ops::Deref for ExtractedNoteCommitment {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ExtractedNoteCommitment> for [u8; 32] {
|
||||
fn from(cmx: &ExtractedNoteCommitment) -> Self {
|
||||
cmx.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for ExtractedNoteCommitment {
|
||||
fn ct_eq(&self, other: &Self) -> subtle::Choice {
|
||||
self.0.ct_eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ExtractedNoteCommitment {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ct_eq(other).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ExtractedNoteCommitment {}
|
||||
|
|
|
@ -0,0 +1,410 @@
|
|||
//! In-band secret distribution for Orchard bundles.
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use blake2b_simd::{Hash, Params};
|
||||
use halo2::arithmetic::FieldExt;
|
||||
use zcash_note_encryption::{
|
||||
Domain, EphemeralKeyBytes, NotePlaintextBytes, NoteValidity, OutPlaintextBytes,
|
||||
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
bundle::Action,
|
||||
keys::{
|
||||
DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
|
||||
IncomingViewingKey, OutgoingViewingKey, SharedSecret,
|
||||
},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
||||
spec::diversify_hash,
|
||||
value::{NoteValue, ValueCommitment},
|
||||
Address, Note,
|
||||
};
|
||||
|
||||
const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock";
|
||||
|
||||
/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs].
|
||||
///
|
||||
/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs
|
||||
pub(crate) fn prf_ock_orchard(
|
||||
ovk: &OutgoingViewingKey,
|
||||
cv: &ValueCommitment,
|
||||
cmx_bytes: &[u8; 32],
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
OutgoingCipherKey(
|
||||
Params::new()
|
||||
.hash_length(32)
|
||||
.personal(PRF_OCK_ORCHARD_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(ovk.as_ref())
|
||||
.update(&cv.to_bytes())
|
||||
.update(cmx_bytes)
|
||||
.update(ephemeral_key.as_ref())
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn orchard_parse_note_plaintext_without_memo<F>(
|
||||
domain: &OrchardDomain,
|
||||
plaintext: &[u8],
|
||||
get_validated_pk_d: F,
|
||||
) -> Option<(Note, Address)>
|
||||
where
|
||||
F: FnOnce(&Diversifier) -> Option<DiversifiedTransmissionKey>,
|
||||
{
|
||||
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
|
||||
|
||||
// Check note plaintext version
|
||||
if plaintext[0] != 0x02 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The unwraps below are guaranteed to succeed by the assertion above
|
||||
let diversifier = Diversifier::from_bytes(plaintext[1..12].try_into().unwrap());
|
||||
let value = NoteValue::from_bytes(plaintext[12..20].try_into().unwrap());
|
||||
let rseed = Option::from(RandomSeed::from_bytes(
|
||||
plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(),
|
||||
&domain.rho,
|
||||
))?;
|
||||
|
||||
let pk_d = get_validated_pk_d(&diversifier)?;
|
||||
|
||||
let recipient = Address::from_parts(diversifier, pk_d);
|
||||
let note = Note::from_parts(recipient, value, domain.rho, rseed);
|
||||
Some((note, recipient))
|
||||
}
|
||||
|
||||
/// Orchard-specific note encryption logic.
|
||||
#[derive(Debug)]
|
||||
pub struct OrchardDomain {
|
||||
rho: Nullifier,
|
||||
}
|
||||
|
||||
impl Domain for OrchardDomain {
|
||||
type EphemeralSecretKey = EphemeralSecretKey;
|
||||
type EphemeralPublicKey = EphemeralPublicKey;
|
||||
type SharedSecret = SharedSecret;
|
||||
type SymmetricKey = Hash;
|
||||
type Note = Note;
|
||||
type Recipient = Address;
|
||||
type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
|
||||
type IncomingViewingKey = IncomingViewingKey;
|
||||
type OutgoingViewingKey = OutgoingViewingKey;
|
||||
type ValueCommitment = ValueCommitment;
|
||||
type ExtractedCommitment = ExtractedNoteCommitment;
|
||||
type ExtractedCommitmentBytes = [u8; 32];
|
||||
type Memo = [u8; 512]; // TODO use a more interesting type
|
||||
|
||||
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey> {
|
||||
Some(note.esk())
|
||||
}
|
||||
|
||||
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey {
|
||||
*note.recipient().pk_d()
|
||||
}
|
||||
|
||||
fn ka_derive_public(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> Self::EphemeralPublicKey {
|
||||
esk.derive_public(note.recipient().g_d())
|
||||
}
|
||||
|
||||
fn ka_agree_enc(
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
) -> Self::SharedSecret {
|
||||
esk.agree(pk_d)
|
||||
}
|
||||
|
||||
fn ka_agree_dec(
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
epk: &Self::EphemeralPublicKey,
|
||||
) -> Self::SharedSecret {
|
||||
epk.agree(ivk)
|
||||
}
|
||||
|
||||
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey {
|
||||
secret.kdf_orchard(&ephemeral_key)
|
||||
}
|
||||
|
||||
fn note_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
_: &Self::Recipient,
|
||||
memo: &Self::Memo,
|
||||
) -> NotePlaintextBytes {
|
||||
let mut np = [0; NOTE_PLAINTEXT_SIZE];
|
||||
np[0] = 0x02;
|
||||
np[1..12].copy_from_slice(note.recipient().diversifer().as_array());
|
||||
np[12..20].copy_from_slice(¬e.value().to_bytes());
|
||||
np[20..52].copy_from_slice(note.rseed().to_bytes());
|
||||
np[52..].copy_from_slice(memo);
|
||||
NotePlaintextBytes(np)
|
||||
}
|
||||
|
||||
fn derive_ock(
|
||||
ovk: &Self::OutgoingViewingKey,
|
||||
cv: &Self::ValueCommitment,
|
||||
cmstar_bytes: &Self::ExtractedCommitmentBytes,
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
prf_ock_orchard(ovk, cv, cmstar_bytes, ephemeral_key)
|
||||
}
|
||||
|
||||
fn outgoing_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> OutPlaintextBytes {
|
||||
let mut op = [0; OUT_PLAINTEXT_SIZE];
|
||||
op[..32].copy_from_slice(¬e.recipient().pk_d().to_bytes());
|
||||
op[32..].copy_from_slice(&esk.0.to_bytes());
|
||||
OutPlaintextBytes(op)
|
||||
}
|
||||
|
||||
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes {
|
||||
epk.to_bytes()
|
||||
}
|
||||
|
||||
fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
|
||||
EphemeralPublicKey::from_bytes(&ephemeral_key.0).into()
|
||||
}
|
||||
|
||||
fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
|
||||
note: &Self::Note,
|
||||
check: F,
|
||||
) -> NoteValidity {
|
||||
check(¬e.esk())
|
||||
}
|
||||
|
||||
fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment {
|
||||
note.commitment().into()
|
||||
}
|
||||
|
||||
fn parse_note_plaintext_without_memo_ivk(
|
||||
&self,
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)> {
|
||||
orchard_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| {
|
||||
Some(DiversifiedTransmissionKey::derive(ivk, diversifier))
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_note_plaintext_without_memo_ovk(
|
||||
&self,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)> {
|
||||
orchard_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| {
|
||||
if esk
|
||||
.derive_public(diversify_hash(diversifier.as_array()))
|
||||
.to_bytes()
|
||||
.0
|
||||
== ephemeral_key.0
|
||||
{
|
||||
Some(*pk_d)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo {
|
||||
plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn extract_pk_d(
|
||||
out_plaintext: &[u8; OUT_PLAINTEXT_SIZE],
|
||||
) -> Option<Self::DiversifiedTransmissionKey> {
|
||||
DiversifiedTransmissionKey::from_bytes(out_plaintext[0..32].try_into().unwrap()).into()
|
||||
}
|
||||
|
||||
fn extract_esk(out_plaintext: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::EphemeralSecretKey> {
|
||||
EphemeralSecretKey::from_bytes(out_plaintext[32..OUT_PLAINTEXT_SIZE].try_into().unwrap())
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption<OrchardDomain>;
|
||||
|
||||
impl<T> ShieldedOutput<OrchardDomain> for Action<T> {
|
||||
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||
EphemeralKeyBytes(self.encrypted_note().epk_bytes)
|
||||
}
|
||||
|
||||
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||
self.cmx().to_bytes()
|
||||
}
|
||||
|
||||
fn enc_ciphertext(&self) -> &[u8] {
|
||||
&self.encrypted_note().enc_ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
struct CompactAction {
|
||||
ephemeral_key: EphemeralKeyBytes,
|
||||
cmx: ExtractedNoteCommitment,
|
||||
enc_ciphertext: [u8; 52],
|
||||
}
|
||||
|
||||
impl<T> From<&Action<T>> for CompactAction {
|
||||
fn from(action: &Action<T>) -> Self {
|
||||
CompactAction {
|
||||
ephemeral_key: action.ephemeral_key(),
|
||||
cmx: action.cmx().clone(),
|
||||
enc_ciphertext: action.encrypted_note().enc_ciphertext[..52]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShieldedOutput<OrchardDomain> for CompactAction {
|
||||
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
||||
EphemeralKeyBytes(self.ephemeral_key.0)
|
||||
}
|
||||
|
||||
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||
self.cmx.to_bytes()
|
||||
}
|
||||
|
||||
fn enc_ciphertext(&self) -> &[u8] {
|
||||
&self.enc_ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::rngs::OsRng;
|
||||
use zcash_note_encryption::{
|
||||
try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk,
|
||||
EphemeralKeyBytes,
|
||||
};
|
||||
|
||||
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
|
||||
use crate::{
|
||||
bundle::Action,
|
||||
keys::{
|
||||
DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey,
|
||||
OutgoingViewingKey,
|
||||
},
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext},
|
||||
primitives::redpallas,
|
||||
value::{NoteValue, ValueCommitment},
|
||||
Address, Note,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
let test_vectors = crate::test_vectors::note_encryption::test_vectors();
|
||||
|
||||
for tv in test_vectors {
|
||||
//
|
||||
// Load the test vector components
|
||||
//
|
||||
|
||||
// Recipient key material
|
||||
let ivk = IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap();
|
||||
let ovk = OutgoingViewingKey::from(tv.ovk);
|
||||
let d = Diversifier::from_bytes(tv.default_d);
|
||||
let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap();
|
||||
|
||||
// Received Action
|
||||
let cv_net = ValueCommitment::from_bytes(&tv.cv_net).unwrap();
|
||||
let rho = Nullifier::from_bytes(&tv.rho).unwrap();
|
||||
let cmx = ExtractedNoteCommitment::from_bytes(&tv.cmx).unwrap();
|
||||
|
||||
let esk = EphemeralSecretKey::from_bytes(&tv.esk).unwrap();
|
||||
let ephemeral_key = EphemeralKeyBytes(tv.ephemeral_key);
|
||||
|
||||
// Details about the expected note
|
||||
let value = NoteValue::from_raw(tv.v);
|
||||
let rseed = RandomSeed::from_bytes(tv.rseed, &rho).unwrap();
|
||||
|
||||
//
|
||||
// Test the individual components
|
||||
//
|
||||
|
||||
let shared_secret = esk.agree(&pk_d);
|
||||
assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
|
||||
|
||||
let k_enc = shared_secret.kdf_orchard(&ephemeral_key);
|
||||
assert_eq!(k_enc.as_bytes(), tv.k_enc);
|
||||
|
||||
let ock = prf_ock_orchard(&ovk, &cv_net, &cmx.to_bytes(), &ephemeral_key);
|
||||
assert_eq!(ock.as_ref(), tv.ock);
|
||||
|
||||
let recipient = Address::from_parts(d, pk_d);
|
||||
let note = Note::from_parts(recipient, value, rho, rseed);
|
||||
assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);
|
||||
|
||||
let action = Action::from_parts(
|
||||
// rho is the nullifier in the receiving Action.
|
||||
rho,
|
||||
// We don't need a valid rk for this test.
|
||||
redpallas::VerificationKey::dummy(),
|
||||
cmx.clone(),
|
||||
TransmittedNoteCiphertext {
|
||||
epk_bytes: ephemeral_key.0,
|
||||
enc_ciphertext: tv.c_enc,
|
||||
out_ciphertext: tv.c_out,
|
||||
},
|
||||
cv_net.clone(),
|
||||
(),
|
||||
);
|
||||
|
||||
//
|
||||
// Test decryption
|
||||
// (Tested first because it only requires immutable references.)
|
||||
//
|
||||
|
||||
let domain = OrchardDomain { rho };
|
||||
|
||||
match try_note_decryption(&domain, &ivk, &action) {
|
||||
Some((decrypted_note, decrypted_to, decrypted_memo)) => {
|
||||
assert_eq!(decrypted_note, note);
|
||||
assert_eq!(decrypted_to, recipient);
|
||||
assert_eq!(&decrypted_memo[..], &tv.memo[..]);
|
||||
}
|
||||
None => panic!("Note decryption failed"),
|
||||
}
|
||||
|
||||
match try_compact_note_decryption(&domain, &ivk, &CompactAction::from(&action)) {
|
||||
Some((decrypted_note, decrypted_to)) => {
|
||||
assert_eq!(decrypted_note, note);
|
||||
assert_eq!(decrypted_to, recipient);
|
||||
}
|
||||
None => panic!("Compact note decryption failed"),
|
||||
}
|
||||
|
||||
match try_output_recovery_with_ovk(&domain, &ovk, &action, &cv_net, &tv.c_out) {
|
||||
Some((decrypted_note, decrypted_to, decrypted_memo)) => {
|
||||
assert_eq!(decrypted_note, note);
|
||||
assert_eq!(decrypted_to, recipient);
|
||||
assert_eq!(&decrypted_memo[..], &tv.memo[..]);
|
||||
}
|
||||
None => panic!("Output recovery failed"),
|
||||
}
|
||||
|
||||
//
|
||||
// Test encryption
|
||||
//
|
||||
|
||||
let ne = OrchardNoteEncryption::new_with_esk(esk, Some(ovk), note, recipient, tv.memo);
|
||||
|
||||
assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
|
||||
assert_eq!(
|
||||
&ne.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut OsRng)[..],
|
||||
&tv.c_out[..]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ use std::convert::{TryFrom, TryInto};
|
|||
use pasta_curves::pallas;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
#[cfg(test)]
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
/// A RedPallas signature type.
|
||||
pub trait SigType: reddsa::SigType + private::Sealed {}
|
||||
|
||||
|
@ -93,6 +96,12 @@ impl<T: SigType> PartialEq for VerificationKey<T> {
|
|||
}
|
||||
|
||||
impl VerificationKey<SpendAuth> {
|
||||
/// Used in the note encryption tests.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn dummy() -> Self {
|
||||
VerificationKey((&reddsa::SigningKey::new(OsRng)).into())
|
||||
}
|
||||
|
||||
/// Randomizes this verification key with the given `randomizer`.
|
||||
///
|
||||
/// Randomization is only supported for `SpendAuth` keys.
|
||||
|
|
67
src/spec.rs
67
src/spec.rs
|
@ -4,10 +4,11 @@ use std::iter;
|
|||
use std::ops::Deref;
|
||||
|
||||
use ff::{Field, PrimeField, PrimeFieldBits};
|
||||
use group::GroupEncoding;
|
||||
use group::{Curve, Group};
|
||||
use halo2::arithmetic::{CurveAffine, CurveExt, FieldExt};
|
||||
use pasta_curves::pallas;
|
||||
use subtle::CtOption;
|
||||
use subtle::{ConditionallySelectable, CtOption};
|
||||
|
||||
use crate::{
|
||||
constants::L_ORCHARD_BASE,
|
||||
|
@ -18,9 +19,28 @@ mod prf_expand;
|
|||
pub(crate) use prf_expand::PrfExpand;
|
||||
|
||||
/// A Pallas point that is guaranteed to not be the identity.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct NonIdentityPallasPoint(pallas::Point);
|
||||
|
||||
impl Default for NonIdentityPallasPoint {
|
||||
fn default() -> Self {
|
||||
NonIdentityPallasPoint(pallas::Point::generator())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for NonIdentityPallasPoint {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
|
||||
NonIdentityPallasPoint(pallas::Point::conditional_select(&a.0, &b.0, choice))
|
||||
}
|
||||
}
|
||||
|
||||
impl NonIdentityPallasPoint {
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
pallas::Point::from_bytes(bytes)
|
||||
.and_then(|p| CtOption::new(NonIdentityPallasPoint(p), !p.is_identity()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NonIdentityPallasPoint {
|
||||
type Target = pallas::Point;
|
||||
|
||||
|
@ -30,9 +50,30 @@ impl Deref for NonIdentityPallasPoint {
|
|||
}
|
||||
|
||||
/// An integer in [1..q_P].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct NonZeroPallasBase(pallas::Base);
|
||||
|
||||
impl Default for NonZeroPallasBase {
|
||||
fn default() -> Self {
|
||||
NonZeroPallasBase(pallas::Base::one())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for NonZeroPallasBase {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
|
||||
NonZeroPallasBase(pallas::Base::conditional_select(&a.0, &b.0, choice))
|
||||
}
|
||||
}
|
||||
|
||||
impl NonZeroPallasBase {
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
pallas::Base::from_bytes(bytes).and_then(NonZeroPallasBase::from_base)
|
||||
}
|
||||
|
||||
pub(crate) fn from_base(b: pallas::Base) -> CtOption<Self> {
|
||||
CtOption::new(NonZeroPallasBase(b), !b.ct_is_zero())
|
||||
}
|
||||
|
||||
/// Constructs a wrapper for a base field element that is guaranteed to be non-zero.
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -45,16 +86,36 @@ impl NonZeroPallasBase {
|
|||
}
|
||||
|
||||
/// An integer in [1..r_P].
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct NonZeroPallasScalar(pallas::Scalar);
|
||||
|
||||
impl Default for NonZeroPallasScalar {
|
||||
fn default() -> Self {
|
||||
NonZeroPallasScalar(pallas::Scalar::one())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonZeroPallasBase> for NonZeroPallasScalar {
|
||||
fn from(s: NonZeroPallasBase) -> Self {
|
||||
NonZeroPallasScalar::guaranteed(mod_r_p(s.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for NonZeroPallasScalar {
|
||||
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
|
||||
NonZeroPallasScalar(pallas::Scalar::conditional_select(&a.0, &b.0, choice))
|
||||
}
|
||||
}
|
||||
|
||||
impl NonZeroPallasScalar {
|
||||
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
pallas::Scalar::from_bytes(bytes).and_then(NonZeroPallasScalar::from_scalar)
|
||||
}
|
||||
|
||||
pub(crate) fn from_scalar(s: pallas::Scalar) -> CtOption<Self> {
|
||||
CtOption::new(NonZeroPallasScalar(s), !s.ct_is_zero())
|
||||
}
|
||||
|
||||
/// Constructs a wrapper for a scalar field element that is guaranteed to be non-zero.
|
||||
///
|
||||
/// # Panics
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub(crate) mod commitment_tree;
|
||||
pub(crate) mod keys;
|
||||
pub(crate) mod note_encryption;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -74,6 +74,14 @@ impl NoteValue {
|
|||
NoteValue(value)
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
|
||||
NoteValue(u64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(self) -> [u8; 8] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
|
||||
pub(crate) fn to_le_bits(self) -> BitArray<Lsb0, [u8; 8]> {
|
||||
BitArray::<Lsb0, _>::new(self.0.to_le_bytes())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue