Skip to content

Commit

Permalink
Fork ed25519-zebra to ed25519-consensus.
Browse files Browse the repository at this point in the history
This code is generally useful beyond Zebra, but the original upstream
made uncomfortable dependency choices:

* ZcashFoundation/ed25519-zebra#38
* zcash/zcash#4993 (comment)

This commit forks and renames the library, updates the dependencies, and
removes Zcash-specific language.
  • Loading branch information
hdevalence committed Sep 16, 2021
1 parent 7a2e1f4 commit 8c0007e
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 82 deletions.
37 changes: 22 additions & 15 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
17 changes: 7 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <hdevalence@hdevalence.ca>"]
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" }
Expand Down
79 changes: 42 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -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) = {
Expand All @@ -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
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
4 changes: 2 additions & 2 deletions src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
8 changes: 2 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/verification_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -80,7 +80,7 @@ impl From<VerificationKeyBytes> 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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/batch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rand::thread_rng;

use ed25519_zebra::*;
use ed25519_consensus::*;

#[test]
fn batch_verify() {
Expand Down
2 changes: 1 addition & 1 deletion tests/rfc8032.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! in consensus.rs.
use bincode;
use ed25519_zebra::*;
use ed25519_consensus::*;
use hex;

fn rfc8032_test_case(sk_bytes: Vec<u8>, pk_bytes: Vec<u8>, sig_bytes: Vec<u8>, msg: Vec<u8>) {
Expand Down
2 changes: 1 addition & 1 deletion tests/small_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
4 changes: 2 additions & 2 deletions tests/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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[..]);

Expand Down
4 changes: 2 additions & 2 deletions tests/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(())
Expand Down

0 comments on commit 8c0007e

Please sign in to comment.