From 8c0007ee372b44fe3aa6928abc7fd908bd6750f5 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 15 Sep 2021 19:37:07 -0700 Subject: [PATCH] Fork `ed25519-zebra` to `ed25519-consensus`. This code is generally useful beyond Zebra, but the original upstream made uncomfortable dependency choices: * https://github.com/ZcashFoundation/ed25519-zebra/pull/38 * https://github.com/zcash/zcash/pull/4993#issuecomment-782757305 This commit forks and renames the library, updates the dependencies, and removes Zcash-specific language. --- CHANGELOG.md | 37 +++++++++++-------- Cargo.toml | 17 ++++----- README.md | 79 ++++++++++++++++++++++------------------- benches/bench.rs | 2 +- src/batch.rs | 4 +-- src/lib.rs | 8 ++--- src/verification_key.rs | 8 ++--- tests/batch.rs | 2 +- tests/rfc8032.rs | 2 +- tests/small_order.rs | 2 +- tests/unit_tests.rs | 4 +-- tests/util/mod.rs | 4 +-- 12 files changed, 87 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b43c03..aa70be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,60 +2,67 @@ Entries are listed in reverse chronological order. -# 2.2.0 +# 1.0.0 + +* Remove Zcash-specific language, update dependencies, and re-release as + `ed25519-consensus`. + +# `ed25519-zebra` changelog entries + +## 2.2.0 * Add `PartialOrd`, `Ord` implementations for `VerificationKeyBytes`. While the derived ordering is not cryptographically meaningful, deriving these traits is useful because it allows, e.g., using `VerificationKeyBytes` as the key to a `BTreeMap` (contributed by @cloudhead). -# 2.1.2 +## 2.1.2 * Updates `sha2` version to `0.9` and `curve25519-dalek` version to `3`. -# 2.1.1 +## 2.1.1 * Add a missing multiplication by the cofactor in batch verification and test that individual and batch verification agree. This corrects an omission that should have been included in `2.0.0`. -# 2.1.0 +## 2.1.0 * Implements `Clone + Debug` for `batch::Item` and provides `batch::Item::verify_single` to perform fallback verification in case of batch failure. -# 2.0.0 +## 2.0.0 * Implements ZIP 215, so that batched and individual verification agree on whether signatures are valid. -# 1.0.0 +## 1.0.0 * Adds `impl TryFrom<&[u8]>` for all types. -# 1.0.0-pre.0 +## 1.0.0-pre.0 * Add a note about versioning to handle ZIP 215. -# 0.4.1 +## 0.4.1 * Change `docs.rs` configuration in `Cargo.toml` to not refer to the removed `batch` feature so that the docs render correctly on `docs.rs`. -# 0.4.0 +## 0.4.0 * The sync batch verification api is changed to remove a dependence on the message lifetime that made it difficult to use in async contexts. -# 0.3.0 +## 0.3.0 * Change terminology from secret and public keys to signing and verification keys. * Remove async batch verification in favor of a sync api; the async approach is to be developed in another crate. -# 0.2.3 +## 0.2.3 * The previous implementation exactly matched the behavior of `libsodium` `1.0.15` with the `ED25519_COMPAT` configuration, but this configuration @@ -64,19 +71,19 @@ Entries are listed in reverse chronological order. with the Zcash specification that were not addressed in the previous spec fix. -# 0.2.2 +## 0.2.2 * Adds `impl AsRef<[u8]> for PublicKey`. * Adds `impl AsRef<[u8]> for SecretKey`. -# 0.2.1 +## 0.2.1 * Adds `impl AsRef<[u8]> for PublicKeyBytes`. -# 0.2.0 +## 0.2.0 * Adds experimental futures-based batch verification API, gated by the `batch` feature. -# 0.1.0 +## 0.1.0 Initial release, attempting to match the actual `zcashd` behavior. diff --git a/Cargo.toml b/Cargo.toml index 2e760ff..d0e2de6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,26 @@ [package] -name = "ed25519-zebra" +name = "ed25519-consensus" # Before publishing: # - update CHANGELOG.md # - update html_root_url -version = "2.2.0" +version = "1.0.0" authors = ["Henry de Valence "] license = "MIT OR Apache-2.0" edition = "2018" -repository = "https://github.com/ZcashFoundation/ed25519-zebra" -description = "Zcash-flavored Ed25519 for use in Zebra." - -[package.metadata.docs.rs] -features = ["nightly"] +repository = "https://github.com/penumbra-zone/ed25519-consensus" +description = "Ed25519 suitable for use in consensus-critical contexts." [dependencies] hex = "0.4" sha2 = "0.9" -rand_core = "0.5" +rand_core = "0.6" thiserror = "1" -curve25519-dalek = { package = "curve25519-dalek-ng", version = "3" } +curve25519-dalek = { package = "curve25519-dalek-ng", version = "4.1" } serde = { version = "1", optional = true, features = ["derive"] } zeroize = "1.1" [dev-dependencies] -rand = "0.7" +rand = "0.8" bincode = "1" criterion = "0.3" ed25519-zebra-legacy = { package = "ed25519-zebra", version = "1" } diff --git a/README.md b/README.md index 14a5879..bd21004 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,58 @@ -Zcash-flavored Ed25519 for use in [Zebra][zebra]. +# Ed25519 for consensus-critical contexts -Zcash uses Ed25519 for [JoinSplit signatures][zcash_protocol_jssig] with -particular validation rules around edge cases in Ed25519 signatures. Ed25519, -as specified in [RFC8032], does not specify behaviour around these edge cases -and so does not require conformant implementations to agree on whether a -signature is valid. For most applications, these edge cases are irrelevant, -but in Zcash, nodes must be able to reach consensus on which signatures would -be valid, so these validation behaviors are *consensus-critical*. +This library provides an Ed25519 implementation with validation rules intended +for consensus-critical contexts. -Because the Ed25519 validation rules are consensus-critical for Zcash, Zebra -requires an Ed25519 library that implements the Zcash-flavored validation rules -specifically, and since it is unreasonable to expect an upstream dependency to -maintain Zcash-specific behavior, this crate provides an Ed25519 implementation -matching the Zcash consensus rules exactly. - -However, this library may be of independent interest, as it implements -ZIP215, a set of precisely specified validation rules for Ed25519 that make -individual verification consistent with batch verification and are -backwards-compatible with all existing Ed25519 signatures. Any non-Zcash users -should use the ZIP215 rules: ```toml -ed25519-zebra = "2" +ed25519-consensus = "1" ``` -## ZIP 215 and changes to Zcash-flavored Ed25519 +Ed25519 signatures are widely used in consensus-critical contexts (e.g., +blockchains), where different nodes must agree on whether or not a given +signature is valid. However, Ed25519 does not clearly define criteria for +signature validity, and even standards-conformant implementations are not +required to agree on whether a signature is valid. -[Zcash Improvement Proposal 215][ZIP215] changes validation criteria for -Ed25519 signatures in Zcash after its activation (currently scheduled for the -Canopy network upgrade at block height 1046400). These changes remove the -dependence on validation rules inherited from a specific point release of -`libsodium` and make individual verification consistent with batch -verification. More details and motivation are available in the text of [ZIP215]. +Different Ed25519 implementations may not (and in practice, do not) agree on +validation criteria in subtle edge cases. This poses a double risk to the use +of Ed25519 in consensus-critical contexts. First, the presence of multiple +Ed25519 implementations may open the possibility of consensus divergence. +Second, even when a single implementation is used, the protocol implicitly +includes that particular version's validation criteria as part of the consensus +rules. However, if the implementation is not intended to be used in +consensus-critical contexts, it may change validation criteria between releases. -The `1.x` series of this crate implements the legacy, pre-ZIP-215 validation -criteria; the `2.x` series of this crate implements the post-ZIP-215 -validation criteria. Users (like Zebra or zcashd) who need to handle the -upgrade can use both versions simultaneously using cargo renaming, e.g., -```toml -ed25519-zebra-legacy = { package = "ed25519-zebra", version = "1" } -ed25519-zebra-zip215 = { package = "ed25519-zebra", version = "2" } -``` +For instance, the initial implementation of Zcash consensus in zcashd inherited +validity criteria from a then-current version of libsodium (1.0.15). Due to a +bug in libsodium, this was different from the intended criteria documented in +the Zcash protocol specification 3 (before the specification was changed to +match libsodium 1.0.15 in specification version 2020.1.2). Also, libsodium never +guaranteed stable validity criteria, and changed behavior in a later point +release. This forced zcashd to use an older version of the library before +eventually patching a newer version to have consistent validity criteria. To be +compatible, [Zebra] had to implement a special library, `ed25519-zebra`, to +provide Zcash-flavored Ed25519, attempting to match libsodium 1.0.15 exactly. +And the initial attempt to implement `ed25519-zebra` was also incompatible, +because it precisely matched the wrong compile-time configuration of libsodium. + +This problem is fixed by [ZIP215], a specification of a precise set of +validation criteria for Ed25519 signatures. Although originally developed for +Zcash, these rules are of general interest, as they precisely specified and +ensure that batch and individual verification are guaranteed to give the same +results. This library implements these rules; it is a fork of `ed25519-zebra` +with Zcash-specific parts removed. + +More details on this problem and its solution can be found in [*It's 255:19AM. +Do you know what your validation criteria are?*][blog] ## Example ``` use std::convert::TryFrom; use rand::thread_rng; -use ed25519_zebra::*; +use ed25519_consensus::*; -let msg = b"Zcash"; +let msg = b"ed25519-consensus"; // Signer's context let (vk_bytes, sig_bytes) = { @@ -75,3 +79,4 @@ assert!( [RFC8032]: https://tools.ietf.org/html/rfc8032 [zebra]: https://github.com/ZcashFoundation/zebra [ZIP215]: https://github.com/zcash/zips/blob/master/zip-0215.rst +[blog]: https://hdevalence.ca/blog/2020-10-04-its-25519am \ No newline at end of file diff --git a/benches/bench.rs b/benches/bench.rs index 65ea345..b51ab63 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use ed25519_zebra::*; +use ed25519_consensus::*; use rand::thread_rng; use std::convert::TryFrom; diff --git a/src/batch.rs b/src/batch.rs index 08b5a6d..9043926 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -28,13 +28,13 @@ //! //! ![benchmark](https://www.zfnd.org/images/coalesced-batch-graph.png) //! -//! This optimization doesn't help much with Zcash, where public keys are random, +//! This optimization doesn't help much when public keys are random, //! but could be useful in proof-of-stake systems where signatures come from a //! set of validators (provided that system uses the ZIP215 rules). //! //! # Example //! ``` -//! # use ed25519_zebra::*; +//! # use ed25519_consensus::*; //! let mut batch = batch::Verifier::new(); //! for _ in 0..32 { //! let sk = SigningKey::new(rand::thread_rng()); diff --git a/src/lib.rs b/src/lib.rs index 5f0341f..650688a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,7 @@ -#![doc(html_root_url = "https://docs.rs/ed25519-zebra/2.2.0")] -#![cfg_attr(feature = "nightly", feature(doc_cfg))] -#![cfg_attr(feature = "nightly", feature(external_doc))] -#![cfg_attr(feature = "nightly", doc(include = "../README.md"))] +#![doc(html_root_url = "https://docs.rs/ed25519-consensus/1.0.0")] +#![doc = include_str!("../README.md")] #![deny(missing_docs)] -//! Docs require the `nightly` feature until RFC 1990 lands. - pub mod batch; mod error; mod signature; diff --git a/src/verification_key.rs b/src/verification_key.rs index 96b4fda..cea3f3f 100644 --- a/src/verification_key.rs +++ b/src/verification_key.rs @@ -21,8 +21,8 @@ use crate::{Error, Signature}; /// ``` /// use std::convert::TryFrom; /// # use rand::thread_rng; -/// # use ed25519_zebra::*; -/// # let msg = b"Zcash"; +/// # use ed25519_consensus::*; +/// # let msg = b"ed25519-consensus"; /// # let sk = SigningKey::new(thread_rng()); /// # let sig = sk.sign(msg); /// # let vk_bytes = VerificationKeyBytes::from(&sk); @@ -80,7 +80,7 @@ impl From for [u8; 32] { /// verification key may not be used immediately, it is probably better to use /// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. /// -/// ## Zcash-specific consensus properties +/// ## Consensus properties /// /// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in /// [ZIP 215]. The verification criteria for an (encoded) verification key `A_bytes` are: @@ -151,7 +151,7 @@ impl TryFrom<[u8; 32]> for VerificationKey { impl VerificationKey { /// Verify a purported `signature` on the given `msg`. /// - /// ## Zcash-specific consensus properties + /// ## Consensus properties /// /// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in /// [ZIP215]. The verification criteria for an (encoded) signature `(R_bytes, s_bytes)` with diff --git a/tests/batch.rs b/tests/batch.rs index 26d27c1..cc70223 100644 --- a/tests/batch.rs +++ b/tests/batch.rs @@ -1,6 +1,6 @@ use rand::thread_rng; -use ed25519_zebra::*; +use ed25519_consensus::*; #[test] fn batch_verify() { diff --git a/tests/rfc8032.rs b/tests/rfc8032.rs index 2894253..0384de3 100644 --- a/tests/rfc8032.rs +++ b/tests/rfc8032.rs @@ -5,7 +5,7 @@ //! in consensus.rs. use bincode; -use ed25519_zebra::*; +use ed25519_consensus::*; use hex; fn rfc8032_test_case(sk_bytes: Vec, pk_bytes: Vec, sig_bytes: Vec, msg: Vec) { diff --git a/tests/small_order.rs b/tests/small_order.rs index d4eae0b..f7f4a34 100644 --- a/tests/small_order.rs +++ b/tests/small_order.rs @@ -87,7 +87,7 @@ fn conformance() -> Result<(), Report> { #[test] fn individual_matches_batch_verification() -> Result<(), Report> { - use ed25519_zebra::{batch, Signature, VerificationKey, VerificationKeyBytes}; + use ed25519_consensus::{batch, Signature, VerificationKey, VerificationKeyBytes}; use std::convert::TryFrom; for case in SMALL_ORDER_SIGS.iter() { let msg = b"Zcash"; diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs index 9403b06..fcd5794 100644 --- a/tests/unit_tests.rs +++ b/tests/unit_tests.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use rand::thread_rng; -use ed25519_zebra::{Signature, SigningKey, VerificationKey, VerificationKeyBytes}; +use ed25519_consensus::{Signature, SigningKey, VerificationKey, VerificationKeyBytes}; #[test] fn parsing() { @@ -45,7 +45,7 @@ fn sign_and_verify() { let sk = SigningKey::new(thread_rng()); let pk = VerificationKey::from(&sk); - let msg = b"ed25519-zebra test message"; + let msg = b"ed25519-consensus test message"; let sig = sk.sign(&msg[..]); diff --git a/tests/util/mod.rs b/tests/util/mod.rs index e44cad3..ff9d78b 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -3,7 +3,7 @@ use color_eyre::{eyre::eyre, Report}; use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; -use ed25519_zebra as ed25519_zebra_zip215; +use ed25519_consensus; use std::convert::TryFrom; pub struct TestCase { @@ -56,7 +56,7 @@ impl TestCase { } fn check_zip215(&self) -> Result<(), Report> { - use ed25519_zebra_zip215::{Signature, VerificationKey}; + use ed25519_consensus::{Signature, VerificationKey}; let sig = Signature::from(self.sig_bytes); VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; Ok(())