├── .gitignore ├── src ├── lib.rs ├── error.rs ├── signature.rs ├── signing_key.rs ├── batch.rs └── verification_key.rs ├── Cargo.toml ├── tests ├── batch.rs ├── unit_tests.rs ├── rfc8032.rs ├── small_order.rs └── util │ └── mod.rs ├── .github └── workflows │ └── main.yml ├── benches └── bench.rs ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_root_url = "https://docs.rs/ed25519-consensus/2.1.0")] 2 | #![doc = include_str!("../README.md")] 3 | #![deny(missing_docs)] 4 | #![cfg_attr(not(feature = "std"), no_std)] 5 | 6 | #[cfg(feature = "std")] 7 | pub mod batch; 8 | mod error; 9 | mod signature; 10 | mod signing_key; 11 | mod verification_key; 12 | 13 | pub use error::Error; 14 | pub use signature::Signature; 15 | pub use signing_key::SigningKey; 16 | pub use verification_key::{VerificationKey, VerificationKeyBytes}; 17 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use thiserror::Error; 3 | 4 | /// An error related to Ed25519 signatures. 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 6 | #[cfg_attr(feature = "std", derive(Error))] 7 | pub enum Error { 8 | /// The encoding of a secret key was malformed. 9 | #[cfg_attr(feature = "std", error("Malformed secret key encoding."))] 10 | MalformedSecretKey, 11 | /// The encoding of a public key was malformed. 12 | #[cfg_attr(feature = "std", error("Malformed public key encoding."))] 13 | MalformedPublicKey, 14 | /// Signature verification failed. 15 | #[cfg_attr(feature = "std", error("Invalid signature."))] 16 | InvalidSignature, 17 | /// A byte slice of the wrong length was supplied during parsing. 18 | #[cfg_attr(feature = "std", error("Invalid length when parsing byte slice."))] 19 | InvalidSliceLength, 20 | } 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ed25519-consensus" 3 | # Before publishing: 4 | # - update CHANGELOG.md 5 | # - update html_root_url 6 | version = "2.1.0" 7 | authors = ["Henry de Valence "] 8 | license = "MIT OR Apache-2.0" 9 | edition = "2018" 10 | repository = "https://github.com/penumbra-zone/ed25519-consensus" 11 | description = "Ed25519 suitable for use in consensus-critical contexts." 12 | resolver = "2" 13 | 14 | [dependencies] 15 | hex = { version = "0.4", default-features = false, features = ["alloc"] } 16 | sha2 = { version = "0.9", default-features = false } 17 | rand_core = { version = "0.6", default-features = false } 18 | curve25519-dalek = { package = "curve25519-dalek-ng", version = "4.1", default-features = false, features = ["u64_backend", "alloc"] } 19 | serde = { version = "1", optional = true, features = ["derive"] } 20 | zeroize = { version = "1.1", default-features = false } 21 | thiserror = { version = "1", optional = true } 22 | 23 | [dev-dependencies] 24 | rand = "0.8" 25 | bincode = "1" 26 | criterion = "0.3" 27 | ed25519-zebra-legacy = { package = "ed25519-zebra", version = "1" } 28 | color-eyre = "0.5" 29 | once_cell = "1.4" 30 | 31 | [features] 32 | std = ["thiserror"] 33 | default = ["serde", "std"] 34 | 35 | [[test]] 36 | name = "rfc8032" 37 | 38 | [[test]] 39 | name = "unit_tests" 40 | 41 | [[test]] 42 | name = "batch" 43 | 44 | [[bench]] 45 | name = "bench" 46 | harness = false 47 | -------------------------------------------------------------------------------- /tests/batch.rs: -------------------------------------------------------------------------------- 1 | use rand::thread_rng; 2 | 3 | use ed25519_consensus::*; 4 | 5 | #[test] 6 | fn batch_verify() { 7 | let mut batch = batch::Verifier::new(); 8 | for _ in 0..32 { 9 | let sk = SigningKey::new(thread_rng()); 10 | let pk_bytes = VerificationKeyBytes::from(&sk); 11 | let msg = b"BatchVerifyTest"; 12 | let sig = sk.sign(&msg[..]); 13 | batch.queue((pk_bytes, sig, msg)); 14 | } 15 | assert!(batch.verify(thread_rng()).is_ok()); 16 | } 17 | 18 | #[test] 19 | fn batch_verify_with_one_bad_sig() { 20 | let bad_index = 10; 21 | let mut batch = batch::Verifier::new(); 22 | let mut items = Vec::new(); 23 | for i in 0..32 { 24 | let sk = SigningKey::new(thread_rng()); 25 | let pk_bytes = VerificationKeyBytes::from(&sk); 26 | let msg = b"BatchVerifyTest"; 27 | let sig = if i != bad_index { 28 | sk.sign(&msg[..]) 29 | } else { 30 | sk.sign(b"badmsg") 31 | }; 32 | let item: batch::Item = (pk_bytes, sig, msg).into(); 33 | items.push(item.clone()); 34 | batch.queue(item); 35 | } 36 | assert!(batch.verify(thread_rng()).is_err()); 37 | for (i, item) in items.drain(..).enumerate() { 38 | if i != bad_index { 39 | assert!(item.verify_single().is_ok()); 40 | } else { 41 | assert!(item.verify_single().is_err()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | 20 | test: 21 | name: Test Suite 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | 34 | fmt: 35 | name: Rustfmt 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions-rs/toolchain@v1 40 | with: 41 | profile: minimal 42 | toolchain: stable 43 | override: true 44 | - run: rustup component add rustfmt 45 | - uses: actions-rs/cargo@v1 46 | with: 47 | command: fmt 48 | args: --all -- --check 49 | 50 | nostd: 51 | name: Build on no_std 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: stable 59 | target: thumbv7em-none-eabihf 60 | override: true 61 | - uses: actions-rs/cargo@v1 62 | with: 63 | command: build 64 | args: --no-default-features --target thumbv7em-none-eabihf 65 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use core::convert::TryFrom; 3 | 4 | /// An Ed25519 signature. 5 | #[derive(Copy, Clone, Eq, PartialEq)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | #[allow(non_snake_case)] 8 | pub struct Signature { 9 | pub(crate) R_bytes: [u8; 32], 10 | pub(crate) s_bytes: [u8; 32], 11 | } 12 | 13 | impl core::fmt::Debug for Signature { 14 | fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { 15 | fmt.debug_struct("Signature") 16 | .field("R_bytes", &hex::encode(&self.R_bytes)) 17 | .field("s_bytes", &hex::encode(&self.s_bytes)) 18 | .finish() 19 | } 20 | } 21 | 22 | impl From<[u8; 64]> for Signature { 23 | #[allow(non_snake_case)] 24 | fn from(bytes: [u8; 64]) -> Signature { 25 | let mut R_bytes = [0; 32]; 26 | R_bytes.copy_from_slice(&bytes[0..32]); 27 | let mut s_bytes = [0; 32]; 28 | s_bytes.copy_from_slice(&bytes[32..64]); 29 | Signature { R_bytes, s_bytes } 30 | } 31 | } 32 | 33 | impl TryFrom<&[u8]> for Signature { 34 | type Error = Error; 35 | 36 | fn try_from(slice: &[u8]) -> Result { 37 | if slice.len() == 64 { 38 | let mut bytes = [0u8; 64]; 39 | bytes[..].copy_from_slice(slice); 40 | Ok(bytes.into()) 41 | } else { 42 | Err(Error::InvalidSliceLength) 43 | } 44 | } 45 | } 46 | 47 | impl From for [u8; 64] { 48 | fn from(sig: Signature) -> [u8; 64] { 49 | sig.to_bytes() 50 | } 51 | } 52 | 53 | impl Signature { 54 | /// Returns the bytes of the signature. 55 | /// 56 | /// This is the same as `.into()`, but does not require type inference. 57 | pub fn to_bytes(&self) -> [u8; 64] { 58 | let mut bytes = [0; 64]; 59 | bytes[0..32].copy_from_slice(&self.R_bytes[..]); 60 | bytes[32..64].copy_from_slice(&self.s_bytes[..]); 61 | bytes 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/unit_tests.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use rand::thread_rng; 4 | 5 | use ed25519_consensus::{Signature, SigningKey, VerificationKey, VerificationKeyBytes}; 6 | 7 | #[test] 8 | fn parsing() { 9 | let sk = SigningKey::new(thread_rng()); 10 | let pk = VerificationKey::from(&sk); 11 | let pkb = VerificationKeyBytes::from(&sk); 12 | let sig = sk.sign(b"test"); 13 | 14 | // Most of these types don't implement Eq, so we check a round trip 15 | // conversion to bytes, using these as the reference points: 16 | 17 | let sk_array: [u8; 32] = sk.clone().into(); 18 | let pk_array: [u8; 32] = pk.into(); 19 | let pkb_array: [u8; 32] = pkb.into(); 20 | let sig_array: [u8; 64] = sig.into(); 21 | 22 | let sk2 = SigningKey::try_from(sk.as_ref()).unwrap(); 23 | let pk2 = VerificationKey::try_from(pk.as_ref()).unwrap(); 24 | let pkb2 = VerificationKeyBytes::try_from(pkb.as_ref()).unwrap(); 25 | let sig2 = Signature::try_from(<[u8; 64]>::from(sig).as_ref()).unwrap(); 26 | 27 | assert_eq!(&sk_array[..], sk2.as_ref()); 28 | assert_eq!(&pk_array[..], pk2.as_ref()); 29 | assert_eq!(&pkb_array[..], pkb2.as_ref()); 30 | assert_eq!(&sig_array[..], <[u8; 64]>::from(sig2).as_ref()); 31 | 32 | let sk3: SigningKey = bincode::deserialize(sk.as_ref()).unwrap(); 33 | let pk3: VerificationKey = bincode::deserialize(pk.as_ref()).unwrap(); 34 | let pkb3: VerificationKeyBytes = bincode::deserialize(pkb.as_ref()).unwrap(); 35 | let sig3: Signature = bincode::deserialize(<[u8; 64]>::from(sig).as_ref()).unwrap(); 36 | 37 | assert_eq!(&sk_array[..], sk3.as_ref()); 38 | assert_eq!(&pk_array[..], pk3.as_ref()); 39 | assert_eq!(&pkb_array[..], pkb3.as_ref()); 40 | assert_eq!(&sig_array[..], <[u8; 64]>::from(sig3).as_ref()); 41 | } 42 | 43 | #[test] 44 | fn sign_and_verify() { 45 | let sk = SigningKey::new(thread_rng()); 46 | let pk = VerificationKey::from(&sk); 47 | 48 | let msg = b"ed25519-consensus test message"; 49 | 50 | let sig = sk.sign(&msg[..]); 51 | 52 | assert_eq!(pk.verify(&sig, &msg[..]), Ok(())) 53 | } 54 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | 3 | use ed25519_consensus::*; 4 | use rand::thread_rng; 5 | use std::convert::TryFrom; 6 | 7 | fn sigs_with_distinct_pubkeys() -> impl Iterator { 8 | std::iter::repeat_with(|| { 9 | let sk = SigningKey::new(thread_rng()); 10 | let pk_bytes = VerificationKeyBytes::from(&sk); 11 | let sig = sk.sign(b""); 12 | (pk_bytes, sig) 13 | }) 14 | } 15 | 16 | fn sigs_with_same_pubkey() -> impl Iterator { 17 | let sk = SigningKey::new(thread_rng()); 18 | let pk_bytes = VerificationKeyBytes::from(&sk); 19 | std::iter::repeat_with(move || { 20 | let sig = sk.sign(b""); 21 | (pk_bytes, sig) 22 | }) 23 | } 24 | 25 | fn bench_batch_verify(c: &mut Criterion) { 26 | let mut group = c.benchmark_group("Batch Verification"); 27 | for n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { 28 | group.throughput(Throughput::Elements(*n as u64)); 29 | let sigs = sigs_with_distinct_pubkeys().take(*n).collect::>(); 30 | group.bench_with_input( 31 | BenchmarkId::new("Unbatched verification", n), 32 | &sigs, 33 | |b, sigs| { 34 | b.iter(|| { 35 | for (vk_bytes, sig) in sigs.iter() { 36 | let _ = 37 | VerificationKey::try_from(*vk_bytes).and_then(|vk| vk.verify(sig, b"")); 38 | } 39 | }) 40 | }, 41 | ); 42 | group.bench_with_input( 43 | BenchmarkId::new("Signatures with Distinct Pubkeys", n), 44 | &sigs, 45 | |b, sigs| { 46 | b.iter(|| { 47 | let mut batch = batch::Verifier::new(); 48 | for (vk_bytes, sig) in sigs.iter().cloned() { 49 | batch.queue((vk_bytes, sig, b"")); 50 | } 51 | batch.verify(thread_rng()) 52 | }) 53 | }, 54 | ); 55 | let sigs = sigs_with_same_pubkey().take(*n).collect::>(); 56 | group.bench_with_input( 57 | BenchmarkId::new("Signatures with the Same Pubkey", n), 58 | &sigs, 59 | |b, sigs| { 60 | b.iter(|| { 61 | let mut batch = batch::Verifier::new(); 62 | for (vk_bytes, sig) in sigs.iter().cloned() { 63 | batch.queue((vk_bytes, sig, b"")); 64 | } 65 | batch.verify(thread_rng()) 66 | }) 67 | }, 68 | ); 69 | } 70 | group.finish(); 71 | } 72 | 73 | criterion_group!(benches, bench_batch_verify); 74 | criterion_main!(benches); 75 | -------------------------------------------------------------------------------- /tests/rfc8032.rs: -------------------------------------------------------------------------------- 1 | //! RFC 8032 test vectors. 2 | //! 3 | //! Note that RFC 8032 does not actually specify validation criteria for Ed25519, 4 | //! so these are basic sanity checks, rather than the more detailed test vectors 5 | //! in consensus.rs. 6 | 7 | use bincode; 8 | use ed25519_consensus::*; 9 | use hex; 10 | 11 | fn rfc8032_test_case(sk_bytes: Vec, pk_bytes: Vec, sig_bytes: Vec, msg: Vec) { 12 | let sk: SigningKey = bincode::deserialize(&sk_bytes).expect("sk should deserialize"); 13 | let pk: VerificationKey = bincode::deserialize(&pk_bytes).expect("pk should deserialize"); 14 | let sig: Signature = bincode::deserialize(&sig_bytes).expect("sig should deserialize"); 15 | 16 | assert!(pk.verify(&sig, &msg).is_ok(), "verification failed"); 17 | 18 | let pk_from_sk = VerificationKey::from(&sk); 19 | assert_eq!( 20 | VerificationKeyBytes::from(pk), 21 | VerificationKeyBytes::from(pk_from_sk), 22 | "regenerated pubkey did not match test vector pubkey" 23 | ); 24 | 25 | let sig_from_sk = sk.sign(&msg); 26 | assert_eq!( 27 | sig, sig_from_sk, 28 | "regenerated signature did not match test vector" 29 | ); 30 | } 31 | 32 | #[test] 33 | fn rfc8032_test_1() { 34 | rfc8032_test_case( 35 | hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") 36 | .expect("hex should decode"), 37 | hex::decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") 38 | .expect("hex should decode"), 39 | hex::decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b") 40 | .expect("hex should decode"), 41 | hex::decode("") 42 | .expect("hex should decode"), 43 | ); 44 | } 45 | 46 | #[test] 47 | fn rfc8032_test_2() { 48 | rfc8032_test_case( 49 | hex::decode("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb") 50 | .expect("hex should decode"), 51 | hex::decode("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c") 52 | .expect("hex should decode"), 53 | hex::decode("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00") 54 | .expect("hex should decode"), 55 | hex::decode("72") 56 | .expect("hex should decode"), 57 | ); 58 | } 59 | 60 | #[test] 61 | fn rfc8032_test_3() { 62 | rfc8032_test_case( 63 | hex::decode("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7") 64 | .expect("hex should decode"), 65 | hex::decode("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025") 66 | .expect("hex should decode"), 67 | hex::decode("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a") 68 | .expect("hex should decode"), 69 | hex::decode("af82") 70 | .expect("hex should decode"), 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | Entries are listed in reverse chronological order. 4 | 5 | # 2.1.0 6 | 7 | * Add `PartialOrd`, `Ord`, `Hash` implementations for `VerificationKey`, 8 | forwarding to the implementations on `VerificationKeyBytes`. 9 | 10 | # 2.0.1 11 | 12 | * Improve `Debug` output for `VerificationKey`. 13 | 14 | # 2.0.0 15 | 16 | * Remove `Copy` implementation from `SigningKey`. 17 | 18 | # 1.2.0 19 | 20 | * Add `to_bytes`/`as_bytes` for `VerificationKey`. 21 | * Add `SigningKey::verification_key()` convenience method (instead of `From`). 22 | 23 | # 1.1.0 24 | 25 | * Support `no_std`. 26 | * Add `to_bytes`/`as_bytes` methods to complement the `Into` implementations (by @0xdeadbeef). 27 | 28 | # 1.0.0 29 | 30 | * Remove Zcash-specific language, update dependencies, and re-release as 31 | `ed25519-consensus`. 32 | 33 | # `ed25519-zebra` changelog entries 34 | 35 | ## 2.2.0 36 | 37 | * Add `PartialOrd`, `Ord` implementations for `VerificationKeyBytes`. While 38 | the derived ordering is not cryptographically meaningful, deriving these 39 | traits is useful because it allows, e.g., using `VerificationKeyBytes` as the 40 | key to a `BTreeMap` (contributed by @cloudhead). 41 | 42 | ## 2.1.2 43 | 44 | * Updates `sha2` version to `0.9` and `curve25519-dalek` version to `3`. 45 | 46 | ## 2.1.1 47 | 48 | * Add a missing multiplication by the cofactor in batch verification and test 49 | that individual and batch verification agree. This corrects an omission that 50 | should have been included in `2.0.0`. 51 | 52 | ## 2.1.0 53 | 54 | * Implements `Clone + Debug` for `batch::Item` and provides 55 | `batch::Item::verify_single` to perform fallback verification in case 56 | of batch failure. 57 | 58 | ## 2.0.0 59 | 60 | * Implements ZIP 215, so that batched and individual verification 61 | agree on whether signatures are valid. 62 | 63 | ## 1.0.0 64 | 65 | * Adds `impl TryFrom<&[u8]>` for all types. 66 | 67 | ## 1.0.0-pre.0 68 | 69 | * Add a note about versioning to handle ZIP 215. 70 | 71 | ## 0.4.1 72 | 73 | * Change `docs.rs` configuration in `Cargo.toml` to not refer to the removed 74 | `batch` feature so that the docs render correctly on `docs.rs`. 75 | 76 | ## 0.4.0 77 | 78 | * The sync batch verification api is changed to remove a dependence on the 79 | message lifetime that made it difficult to use in async contexts. 80 | 81 | ## 0.3.0 82 | 83 | * Change terminology from secret and public keys to signing and verification 84 | keys. 85 | * Remove async batch verification in favor of a sync api; the async approach is 86 | to be developed in another crate. 87 | 88 | ## 0.2.3 89 | 90 | * The previous implementation exactly matched the behavior of `libsodium` 91 | `1.0.15` with the `ED25519_COMPAT` configuration, but this configuration 92 | wasn't used by `zcashd`. This commit changes the validation rules to exactly 93 | match without `ED25519_COMPAT`, and highlights the remaining inconsistencies 94 | with the Zcash specification that were not addressed in the previous spec 95 | fix. 96 | 97 | ## 0.2.2 98 | 99 | * Adds `impl AsRef<[u8]> for PublicKey`. 100 | * Adds `impl AsRef<[u8]> for SecretKey`. 101 | 102 | ## 0.2.1 103 | 104 | * Adds `impl AsRef<[u8]> for PublicKeyBytes`. 105 | 106 | ## 0.2.0 107 | 108 | * Adds experimental futures-based batch verification API, gated by the `batch` feature. 109 | 110 | ## 0.1.0 111 | 112 | Initial release, attempting to match the actual `zcashd` behavior. 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ed25519 for consensus-critical contexts 2 | 3 | This library provides an Ed25519 implementation with validation rules intended 4 | for consensus-critical contexts. 5 | 6 | ```toml 7 | ed25519-consensus = "1" 8 | ``` 9 | 10 | Ed25519 signatures are widely used in consensus-critical contexts (e.g., 11 | blockchains), where different nodes must agree on whether or not a given 12 | signature is valid. However, Ed25519 does not clearly define criteria for 13 | signature validity, and even standards-conformant implementations are not 14 | required to agree on whether a signature is valid. 15 | 16 | Different Ed25519 implementations may not (and in practice, do not) agree on 17 | validation criteria in subtle edge cases. This poses a double risk to the use 18 | of Ed25519 in consensus-critical contexts. First, the presence of multiple 19 | Ed25519 implementations may open the possibility of consensus divergence. 20 | Second, even when a single implementation is used, the protocol implicitly 21 | includes that particular version's validation criteria as part of the consensus 22 | rules. However, if the implementation is not intended to be used in 23 | consensus-critical contexts, it may change validation criteria between releases. 24 | 25 | For instance, the initial implementation of Zcash consensus in zcashd inherited 26 | validity criteria from a then-current version of libsodium (1.0.15). Due to a 27 | bug in libsodium, this was different from the intended criteria documented in 28 | the Zcash protocol specification 3 (before the specification was changed to 29 | match libsodium 1.0.15 in specification version 2020.1.2). Also, libsodium never 30 | guaranteed stable validity criteria, and changed behavior in a later point 31 | release. This forced zcashd to use an older version of the library before 32 | eventually patching a newer version to have consistent validity criteria. To be 33 | compatible, [Zebra] had to implement a special library, `ed25519-zebra`, to 34 | provide Zcash-flavored Ed25519, attempting to match libsodium 1.0.15 exactly. 35 | And the initial attempt to implement `ed25519-zebra` was also incompatible, 36 | because it precisely matched the wrong compile-time configuration of libsodium. 37 | 38 | This problem is fixed by [ZIP215], a specification of a precise set of 39 | validation criteria for Ed25519 signatures. Although originally developed for 40 | Zcash, these rules are of general interest, as they precisely specified and 41 | ensure that batch and individual verification are guaranteed to give the same 42 | results. This library implements these rules; it is a fork of `ed25519-zebra` 43 | with Zcash-specific parts removed. 44 | 45 | More details on this problem and its solution can be found in [*It's 255:19AM. 46 | Do you know what your validation criteria are?*][blog] 47 | 48 | ## Example 49 | 50 | ``` 51 | use std::convert::TryFrom; 52 | use rand::thread_rng; 53 | use ed25519_consensus::*; 54 | 55 | let msg = b"ed25519-consensus"; 56 | 57 | // Signer's context 58 | let (vk_bytes, sig_bytes) = { 59 | // Generate a signing key and sign the message 60 | let sk = SigningKey::new(thread_rng()); 61 | let sig = sk.sign(msg); 62 | 63 | // Types can be converted to raw byte arrays with From/Into 64 | let sig_bytes: [u8; 64] = sig.into(); 65 | let vk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); 66 | 67 | (vk_bytes, sig_bytes) 68 | }; 69 | 70 | // Verify the signature 71 | assert!( 72 | VerificationKey::try_from(vk_bytes) 73 | .and_then(|vk| vk.verify(&sig_bytes.into(), msg)) 74 | .is_ok() 75 | ); 76 | ``` 77 | 78 | [zcash_protocol_jssig]: https://zips.z.cash/protocol/protocol.pdf#concretejssig 79 | [RFC8032]: https://tools.ietf.org/html/rfc8032 80 | [zebra]: https://github.com/ZcashFoundation/zebra 81 | [ZIP215]: https://github.com/zcash/zips/blob/master/zip-0215.rst 82 | [blog]: https://hdevalence.ca/blog/2020-10-04-its-25519am -------------------------------------------------------------------------------- /tests/small_order.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::Report; 2 | use curve25519_dalek::{ 3 | constants::EIGHT_TORSION, edwards::CompressedEdwardsY, scalar::Scalar, traits::IsIdentity, 4 | }; 5 | use once_cell::sync::Lazy; 6 | use sha2::{Digest, Sha512}; 7 | 8 | mod util; 9 | use util::TestCase; 10 | 11 | #[allow(non_snake_case)] 12 | pub static SMALL_ORDER_SIGS: Lazy> = Lazy::new(|| { 13 | let mut tests = Vec::new(); 14 | let s = Scalar::zero(); 15 | 16 | // Use all the canonical encodings of the 8-torsion points, 17 | // and the low-order non-canonical encodings. 18 | let encodings = EIGHT_TORSION 19 | .iter() 20 | .map(|point| point.compress().to_bytes()) 21 | .chain(util::non_canonical_point_encodings().into_iter().take(6)) 22 | .collect::>(); 23 | 24 | /* 25 | for (i, e) in encodings.iter().enumerate() { 26 | println!("{}: {}", i, hex::encode(e)); 27 | } 28 | */ 29 | 30 | for A_bytes in &encodings { 31 | let A = CompressedEdwardsY(*A_bytes).decompress().unwrap(); 32 | for R_bytes in &encodings { 33 | let R = CompressedEdwardsY(*R_bytes).decompress().unwrap(); 34 | let sig_bytes = { 35 | let mut bytes = [0u8; 64]; 36 | bytes[0..32].copy_from_slice(&R_bytes[..]); 37 | bytes[32..64].copy_from_slice(s.as_bytes()); 38 | bytes 39 | }; 40 | let vk_bytes = *A_bytes; 41 | // The verification equation is [8][s]B = [8]R + [8][k]A. 42 | // If R, A are torsion points the LHS is 0, setting s = 0 makes RHS 0. 43 | let valid_zip215 = true; 44 | // In the legacy equation the RHS is 0 and the LHS is R + [k]A. 45 | // This will be valid only if: 46 | // * A is not all zeros. 47 | // * R is not an excluded point 48 | // * R + [k]A = 0 49 | // * R is canonically encoded (because the check recomputes R) 50 | let k = Scalar::from_hash( 51 | Sha512::default() 52 | .chain(&sig_bytes[0..32]) 53 | .chain(vk_bytes) 54 | .chain(b"Zcash"), 55 | ); 56 | let check = R + k * A; 57 | let non_canonical_R = R.compress().as_bytes() != R_bytes; 58 | let valid_legacy = if vk_bytes == [0; 32] 59 | || util::EXCLUDED_POINT_ENCODINGS.contains(R.compress().as_bytes()) 60 | || !check.is_identity() 61 | || non_canonical_R 62 | { 63 | false 64 | } else { 65 | true 66 | }; 67 | 68 | tests.push(TestCase { 69 | vk_bytes, 70 | sig_bytes, 71 | valid_legacy, 72 | valid_zip215, 73 | }) 74 | } 75 | } 76 | tests 77 | }); 78 | 79 | #[test] 80 | fn conformance() -> Result<(), Report> { 81 | for case in SMALL_ORDER_SIGS.iter() { 82 | case.check()?; 83 | } 84 | println!("{:#?}", *SMALL_ORDER_SIGS); 85 | Ok(()) 86 | } 87 | 88 | #[test] 89 | fn individual_matches_batch_verification() -> Result<(), Report> { 90 | use ed25519_consensus::{batch, Signature, VerificationKey, VerificationKeyBytes}; 91 | use std::convert::TryFrom; 92 | for case in SMALL_ORDER_SIGS.iter() { 93 | let msg = b"Zcash"; 94 | let sig = Signature::from(case.sig_bytes); 95 | let vkb = VerificationKeyBytes::from(case.vk_bytes); 96 | let individual_verification = 97 | VerificationKey::try_from(vkb).and_then(|vk| vk.verify(&sig, msg)); 98 | let mut bv = batch::Verifier::new(); 99 | bv.queue((vkb, sig, msg)); 100 | let batch_verification = bv.verify(rand::thread_rng()); 101 | assert_eq!(individual_verification.is_ok(), batch_verification.is_ok()); 102 | } 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /src/signing_key.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use curve25519_dalek::{constants, scalar::Scalar}; 4 | use rand_core::{CryptoRng, RngCore}; 5 | use sha2::{Digest, Sha512}; 6 | 7 | use crate::{Error, Signature, VerificationKey, VerificationKeyBytes}; 8 | 9 | /// An Ed25519 signing key. 10 | /// 11 | /// This is also called a secret key by other implementations. 12 | #[derive(Clone)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | #[cfg_attr(feature = "serde", serde(from = "SerdeHelper"))] 15 | #[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] 16 | pub struct SigningKey { 17 | seed: [u8; 32], 18 | s: Scalar, 19 | prefix: [u8; 32], 20 | vk: VerificationKey, 21 | } 22 | 23 | impl SigningKey { 24 | /// Returns the byte encoding of the signing key. 25 | /// 26 | /// This is the same as `.into()`, but does not require type inference. 27 | pub fn to_bytes(&self) -> [u8; 32] { 28 | self.seed 29 | } 30 | 31 | /// View the byte encoding of the signing key. 32 | pub fn as_bytes(&self) -> &[u8; 32] { 33 | &self.seed 34 | } 35 | 36 | /// Obtain the verification key associated with this signing key. 37 | pub fn verification_key(&self) -> VerificationKey { 38 | self.vk 39 | } 40 | } 41 | 42 | impl core::fmt::Debug for SigningKey { 43 | fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { 44 | fmt.debug_struct("SigningKey") 45 | .field("seed", &hex::encode(&self.seed)) 46 | .field("s", &self.s) 47 | .field("prefix", &hex::encode(&self.prefix)) 48 | .field("vk", &self.vk) 49 | .finish() 50 | } 51 | } 52 | 53 | impl<'a> From<&'a SigningKey> for VerificationKey { 54 | fn from(sk: &'a SigningKey) -> VerificationKey { 55 | sk.vk 56 | } 57 | } 58 | 59 | impl<'a> From<&'a SigningKey> for VerificationKeyBytes { 60 | fn from(sk: &'a SigningKey) -> VerificationKeyBytes { 61 | sk.vk.into() 62 | } 63 | } 64 | 65 | impl AsRef<[u8]> for SigningKey { 66 | fn as_ref(&self) -> &[u8] { 67 | &self.seed[..] 68 | } 69 | } 70 | 71 | impl From for [u8; 32] { 72 | fn from(sk: SigningKey) -> [u8; 32] { 73 | sk.seed 74 | } 75 | } 76 | 77 | impl TryFrom<&[u8]> for SigningKey { 78 | type Error = Error; 79 | fn try_from(slice: &[u8]) -> Result { 80 | if slice.len() == 32 { 81 | let mut bytes = [0u8; 32]; 82 | bytes[..].copy_from_slice(slice); 83 | Ok(bytes.into()) 84 | } else { 85 | Err(Error::InvalidSliceLength) 86 | } 87 | } 88 | } 89 | 90 | impl From<[u8; 32]> for SigningKey { 91 | #[allow(non_snake_case)] 92 | fn from(seed: [u8; 32]) -> SigningKey { 93 | // Expand the seed to a 64-byte array with SHA512. 94 | let h = Sha512::digest(&seed[..]); 95 | 96 | // Convert the low half to a scalar with Ed25519 "clamping" 97 | let s = { 98 | let mut scalar_bytes = [0u8; 32]; 99 | scalar_bytes[..].copy_from_slice(&h.as_slice()[0..32]); 100 | scalar_bytes[0] &= 248; 101 | scalar_bytes[31] &= 127; 102 | scalar_bytes[31] |= 64; 103 | Scalar::from_bits(scalar_bytes) 104 | }; 105 | 106 | // Extract and cache the high half. 107 | let prefix = { 108 | let mut prefix = [0u8; 32]; 109 | prefix[..].copy_from_slice(&h.as_slice()[32..64]); 110 | prefix 111 | }; 112 | 113 | // Compute the public key as A = [s]B. 114 | let A = &s * &constants::ED25519_BASEPOINT_TABLE; 115 | 116 | SigningKey { 117 | seed, 118 | s, 119 | prefix, 120 | vk: VerificationKey { 121 | minus_A: -A, 122 | A_bytes: VerificationKeyBytes(A.compress().to_bytes()), 123 | }, 124 | } 125 | } 126 | } 127 | 128 | impl zeroize::Zeroize for SigningKey { 129 | fn zeroize(&mut self) { 130 | self.seed.zeroize(); 131 | self.s.zeroize() 132 | } 133 | } 134 | 135 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 136 | struct SerdeHelper([u8; 32]); 137 | 138 | impl From for SigningKey { 139 | fn from(helper: SerdeHelper) -> SigningKey { 140 | helper.0.into() 141 | } 142 | } 143 | 144 | impl From for SerdeHelper { 145 | fn from(sk: SigningKey) -> Self { 146 | Self(sk.into()) 147 | } 148 | } 149 | 150 | impl SigningKey { 151 | /// Generate a new signing key. 152 | pub fn new(mut rng: R) -> SigningKey { 153 | let mut bytes = [0u8; 32]; 154 | rng.fill_bytes(&mut bytes[..]); 155 | bytes.into() 156 | } 157 | 158 | /// Create a signature on `msg` using this key. 159 | #[allow(non_snake_case)] 160 | pub fn sign(&self, msg: &[u8]) -> Signature { 161 | let r = Scalar::from_hash(Sha512::default().chain(&self.prefix[..]).chain(msg)); 162 | 163 | let R_bytes = (&r * &constants::ED25519_BASEPOINT_TABLE) 164 | .compress() 165 | .to_bytes(); 166 | 167 | let k = Scalar::from_hash( 168 | Sha512::default() 169 | .chain(&R_bytes[..]) 170 | .chain(&self.vk.A_bytes.0[..]) 171 | .chain(msg), 172 | ); 173 | 174 | let s_bytes = (r + k * self.s).to_bytes(); 175 | 176 | Signature { R_bytes, s_bytes } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/batch.rs: -------------------------------------------------------------------------------- 1 | //! Performs batch Ed25519 signature verification. 2 | //! 3 | //! Batch verification asks whether *all* signatures in some set are valid, 4 | //! rather than asking whether *each* of them is valid. This allows sharing 5 | //! computations among all signature verifications, performing less work overall 6 | //! at the cost of higher latency (the entire batch must complete), complexity of 7 | //! caller code (which must assemble a batch of signatures across work-items), 8 | //! and loss of the ability to easily pinpoint failing signatures. 9 | //! 10 | //! In addition to these general tradeoffs, design flaws in Ed25519 specifically 11 | //! mean that batched verification may not agree with individual verification. 12 | //! Some signatures may verify as part of a batch but not on their own. 13 | //! This problem is fixed by [ZIP215], a precise specification for edge cases 14 | //! in Ed25519 signature validation that ensures that batch verification agrees 15 | //! with individual verification in all cases. 16 | //! 17 | //! This crate implements ZIP215, so batch verification always agrees with 18 | //! individual verification, but this is not guaranteed by other implementations. 19 | //! **Be extremely careful when using Ed25519 in a consensus-critical context 20 | //! like a blockchain.** 21 | //! 22 | //! This batch verification implementation is adaptive in the sense that it 23 | //! detects multiple signatures created with the same verification key and 24 | //! automatically coalesces terms in the final verification equation. In the 25 | //! limiting case where all signatures in the batch are made with the same 26 | //! verification key, coalesced batch verification runs twice as fast as ordinary 27 | //! batch verification. 28 | //! 29 | //! ![benchmark](https://www.zfnd.org/images/coalesced-batch-graph.png) 30 | //! 31 | //! This optimization doesn't help much when public keys are random, 32 | //! but could be useful in proof-of-stake systems where signatures come from a 33 | //! set of validators (provided that system uses the ZIP215 rules). 34 | //! 35 | //! # Example 36 | //! ``` 37 | //! # use ed25519_consensus::*; 38 | //! let mut batch = batch::Verifier::new(); 39 | //! for _ in 0..32 { 40 | //! let sk = SigningKey::new(rand::thread_rng()); 41 | //! let vk_bytes = VerificationKeyBytes::from(&sk); 42 | //! let msg = b"BatchVerifyTest"; 43 | //! let sig = sk.sign(&msg[..]); 44 | //! batch.queue((vk_bytes, sig, &msg[..])); 45 | //! } 46 | //! assert!(batch.verify(rand::thread_rng()).is_ok()); 47 | //! ``` 48 | //! 49 | //! [ZIP215]: https://github.com/zcash/zips/blob/master/zip-0215.rst 50 | 51 | use std::{collections::HashMap, convert::TryFrom}; 52 | 53 | use curve25519_dalek::{ 54 | edwards::{CompressedEdwardsY, EdwardsPoint}, 55 | scalar::Scalar, 56 | traits::{IsIdentity, VartimeMultiscalarMul}, 57 | }; 58 | use rand_core::{CryptoRng, RngCore}; 59 | use sha2::{Digest, Sha512}; 60 | 61 | use crate::{Error, Signature, VerificationKey, VerificationKeyBytes}; 62 | 63 | // Shim to generate a u128 without importing `rand`. 64 | fn gen_u128(mut rng: R) -> u128 { 65 | let mut bytes = [0u8; 16]; 66 | rng.fill_bytes(&mut bytes[..]); 67 | u128::from_le_bytes(bytes) 68 | } 69 | 70 | /// A batch verification item. 71 | /// 72 | /// This struct exists to allow batch processing to be decoupled from the 73 | /// lifetime of the message. This is useful when using the batch verification API 74 | /// in an async context. 75 | #[derive(Clone, Debug)] 76 | pub struct Item { 77 | vk_bytes: VerificationKeyBytes, 78 | sig: Signature, 79 | k: Scalar, 80 | } 81 | 82 | impl<'msg, M: AsRef<[u8]> + ?Sized> From<(VerificationKeyBytes, Signature, &'msg M)> for Item { 83 | fn from(tup: (VerificationKeyBytes, Signature, &'msg M)) -> Self { 84 | let (vk_bytes, sig, msg) = tup; 85 | // Compute k now to avoid dependency on the msg lifetime. 86 | let k = Scalar::from_hash( 87 | Sha512::default() 88 | .chain(&sig.R_bytes[..]) 89 | .chain(&vk_bytes.0[..]) 90 | .chain(msg), 91 | ); 92 | Self { vk_bytes, sig, k } 93 | } 94 | } 95 | 96 | impl Item { 97 | /// Perform non-batched verification of this `Item`. 98 | /// 99 | /// This is useful (in combination with `Item::clone`) for implementing fallback 100 | /// logic when batch verification fails. In contrast to 101 | /// [`VerificationKey::verify`](crate::VerificationKey::verify), which requires 102 | /// borrowing the message data, the `Item` type is unlinked from the lifetime of 103 | /// the message. 104 | pub fn verify_single(self) -> Result<(), Error> { 105 | VerificationKey::try_from(self.vk_bytes) 106 | .and_then(|vk| vk.verify_prehashed(&self.sig, self.k)) 107 | } 108 | } 109 | 110 | /// A batch verification context. 111 | #[derive(Default)] 112 | pub struct Verifier { 113 | /// Signature data queued for verification. 114 | signatures: HashMap>, 115 | /// Caching this count avoids a hash traversal to figure out 116 | /// how much to preallocate. 117 | batch_size: usize, 118 | } 119 | 120 | impl Verifier { 121 | /// Construct a new batch verifier. 122 | pub fn new() -> Verifier { 123 | Verifier::default() 124 | } 125 | 126 | /// Queue a (key, signature, message) tuple for verification. 127 | pub fn queue>(&mut self, item: I) { 128 | let Item { vk_bytes, sig, k } = item.into(); 129 | 130 | self.signatures 131 | .entry(vk_bytes) 132 | // The common case is 1 signature per public key. 133 | // We could also consider using a smallvec here. 134 | .or_insert_with(|| Vec::with_capacity(1)) 135 | .push((k, sig)); 136 | self.batch_size += 1; 137 | } 138 | 139 | /// Perform batch verification, returning `Ok(())` if all signatures were 140 | /// valid and `Err` otherwise. 141 | /// 142 | /// # Warning 143 | /// 144 | /// Ed25519 has different verification rules for batched and non-batched 145 | /// verifications. This function does not have the same verification criteria 146 | /// as individual verification, which may reject some signatures this method 147 | /// accepts. 148 | #[allow(non_snake_case)] 149 | pub fn verify(self, mut rng: R) -> Result<(), Error> { 150 | // The batch verification equation is 151 | // 152 | // [-sum(z_i * s_i)]B + sum([z_i]R_i) + sum([z_i * k_i]A_i) = 0. 153 | // 154 | // where for each signature i, 155 | // - A_i is the verification key; 156 | // - R_i is the signature's R value; 157 | // - s_i is the signature's s value; 158 | // - k_i is the hash of the message and other data; 159 | // - z_i is a random 128-bit Scalar. 160 | // 161 | // Normally n signatures would require a multiscalar multiplication of 162 | // size 2*n + 1, together with 2*n point decompressions (to obtain A_i 163 | // and R_i). However, because we store batch entries in a HashMap 164 | // indexed by the verification key, we can "coalesce" all z_i * k_i 165 | // terms for each distinct verification key into a single coefficient. 166 | // 167 | // For n signatures from m verification keys, this approach instead 168 | // requires a multiscalar multiplication of size n + m + 1 together with 169 | // n + m point decompressions. When m = n, so all signatures are from 170 | // distinct verification keys, this is as efficient as the usual method. 171 | // However, when m = 1 and all signatures are from a single verification 172 | // key, this is nearly twice as fast. 173 | 174 | let m = self.signatures.keys().count(); 175 | 176 | let mut A_coeffs = Vec::with_capacity(m); 177 | let mut As = Vec::with_capacity(m); 178 | let mut R_coeffs = Vec::with_capacity(self.batch_size); 179 | let mut Rs = Vec::with_capacity(self.batch_size); 180 | let mut B_coeff = Scalar::zero(); 181 | 182 | for (vk_bytes, sigs) in self.signatures.iter() { 183 | let A = CompressedEdwardsY(vk_bytes.0) 184 | .decompress() 185 | .ok_or(Error::InvalidSignature)?; 186 | 187 | let mut A_coeff = Scalar::zero(); 188 | 189 | for (k, sig) in sigs.iter() { 190 | let R = CompressedEdwardsY(sig.R_bytes) 191 | .decompress() 192 | .ok_or(Error::InvalidSignature)?; 193 | let s = Scalar::from_canonical_bytes(sig.s_bytes).ok_or(Error::InvalidSignature)?; 194 | let z = Scalar::from(gen_u128(&mut rng)); 195 | B_coeff -= z * s; 196 | Rs.push(R); 197 | R_coeffs.push(z); 198 | A_coeff += z * k; 199 | } 200 | 201 | As.push(A); 202 | A_coeffs.push(A_coeff); 203 | } 204 | 205 | use core::iter::once; 206 | use curve25519_dalek::constants::ED25519_BASEPOINT_POINT as B; 207 | let check = EdwardsPoint::vartime_multiscalar_mul( 208 | once(&B_coeff).chain(A_coeffs.iter()).chain(R_coeffs.iter()), 209 | once(&B).chain(As.iter()).chain(Rs.iter()), 210 | ); 211 | 212 | if check.mul_by_cofactor().is_identity() { 213 | Ok(()) 214 | } else { 215 | Err(Error::InvalidSignature) 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/verification_key.rs: -------------------------------------------------------------------------------- 1 | use core::convert::{TryFrom, TryInto}; 2 | 3 | use curve25519_dalek::{ 4 | edwards::{CompressedEdwardsY, EdwardsPoint}, 5 | scalar::Scalar, 6 | traits::IsIdentity, 7 | }; 8 | use sha2::{Digest, Sha512}; 9 | 10 | use crate::{Error, Signature}; 11 | 12 | /// A refinement type for `[u8; 32]` indicating that the bytes represent an 13 | /// encoding of an Ed25519 verification key. 14 | /// 15 | /// This is useful for representing an encoded verification key, while the 16 | /// [`VerificationKey`] type in this library caches other decoded state used in 17 | /// signature verification. 18 | /// 19 | /// A `VerificationKeyBytes` can be used to verify a single signature using the 20 | /// following idiom: 21 | /// ``` 22 | /// use core::convert::TryFrom; 23 | /// # use rand::thread_rng; 24 | /// # use ed25519_consensus::*; 25 | /// # let msg = b"ed25519-consensus"; 26 | /// # let sk = SigningKey::new(thread_rng()); 27 | /// # let sig = sk.sign(msg); 28 | /// # let vk_bytes = VerificationKeyBytes::from(&sk); 29 | /// VerificationKey::try_from(vk_bytes) 30 | /// .and_then(|vk| vk.verify(&sig, msg)); 31 | /// ``` 32 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 34 | pub struct VerificationKeyBytes(pub(crate) [u8; 32]); 35 | 36 | impl VerificationKeyBytes { 37 | /// Returns the byte encoding of the verification key. 38 | /// 39 | /// This is the same as `.into()`, but does not require type inference. 40 | pub fn to_bytes(&self) -> [u8; 32] { 41 | self.0 42 | } 43 | 44 | /// View the byte encoding of the verification key. 45 | pub fn as_bytes(&self) -> &[u8; 32] { 46 | &self.0 47 | } 48 | } 49 | 50 | impl core::fmt::Debug for VerificationKeyBytes { 51 | fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { 52 | fmt.debug_tuple("VerificationKeyBytes") 53 | .field(&hex::encode(&self.0)) 54 | .finish() 55 | } 56 | } 57 | 58 | impl AsRef<[u8]> for VerificationKeyBytes { 59 | fn as_ref(&self) -> &[u8] { 60 | &self.0[..] 61 | } 62 | } 63 | 64 | impl TryFrom<&[u8]> for VerificationKeyBytes { 65 | type Error = Error; 66 | fn try_from(slice: &[u8]) -> Result { 67 | if slice.len() == 32 { 68 | let mut bytes = [0u8; 32]; 69 | bytes[..].copy_from_slice(slice); 70 | Ok(bytes.into()) 71 | } else { 72 | Err(Error::InvalidSliceLength) 73 | } 74 | } 75 | } 76 | 77 | impl From<[u8; 32]> for VerificationKeyBytes { 78 | fn from(bytes: [u8; 32]) -> VerificationKeyBytes { 79 | VerificationKeyBytes(bytes) 80 | } 81 | } 82 | 83 | impl From for [u8; 32] { 84 | fn from(refined: VerificationKeyBytes) -> [u8; 32] { 85 | refined.0 86 | } 87 | } 88 | 89 | /// A valid Ed25519 verification key. 90 | /// 91 | /// This is also called a public key by other implementations. 92 | /// 93 | /// This type holds decompressed state used in signature verification; if the 94 | /// verification key may not be used immediately, it is probably better to use 95 | /// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. 96 | /// 97 | /// ## Consensus properties 98 | /// 99 | /// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in 100 | /// [ZIP 215]. The verification criteria for an (encoded) verification key `A_bytes` are: 101 | /// 102 | /// * `A_bytes` MUST be an encoding of a point `A` on the twisted Edwards form of 103 | /// Curve25519, and non-canonical encodings MUST be accepted; 104 | /// 105 | /// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519 106 | #[derive(Copy, Clone, Eq, PartialEq)] 107 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 108 | #[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] 109 | #[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] 110 | #[allow(non_snake_case)] 111 | pub struct VerificationKey { 112 | pub(crate) A_bytes: VerificationKeyBytes, 113 | pub(crate) minus_A: EdwardsPoint, 114 | } 115 | 116 | impl PartialOrd for VerificationKey { 117 | fn partial_cmp(&self, other: &Self) -> Option { 118 | Some(self.cmp(other)) 119 | } 120 | } 121 | 122 | impl Ord for VerificationKey { 123 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { 124 | self.A_bytes.cmp(&other.A_bytes) 125 | } 126 | } 127 | 128 | impl core::hash::Hash for VerificationKey { 129 | fn hash(&self, state: &mut H) { 130 | self.A_bytes.hash(state); 131 | } 132 | } 133 | 134 | impl core::fmt::Debug for VerificationKey { 135 | fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { 136 | fmt.debug_tuple("VerificationKey") 137 | .field(&hex::encode(&self.A_bytes.0)) 138 | .finish() 139 | } 140 | } 141 | 142 | impl From for VerificationKeyBytes { 143 | fn from(vk: VerificationKey) -> VerificationKeyBytes { 144 | vk.A_bytes 145 | } 146 | } 147 | 148 | impl AsRef<[u8]> for VerificationKey { 149 | fn as_ref(&self) -> &[u8] { 150 | &self.A_bytes.0[..] 151 | } 152 | } 153 | 154 | impl From for [u8; 32] { 155 | fn from(vk: VerificationKey) -> [u8; 32] { 156 | vk.A_bytes.0 157 | } 158 | } 159 | 160 | impl TryFrom for VerificationKey { 161 | type Error = Error; 162 | #[allow(non_snake_case)] 163 | fn try_from(bytes: VerificationKeyBytes) -> Result { 164 | // * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the 165 | // twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted; 166 | let A = CompressedEdwardsY(bytes.0) 167 | .decompress() 168 | .ok_or(Error::MalformedPublicKey)?; 169 | 170 | Ok(VerificationKey { 171 | A_bytes: bytes, 172 | minus_A: -A, 173 | }) 174 | } 175 | } 176 | 177 | impl TryFrom<&[u8]> for VerificationKey { 178 | type Error = Error; 179 | fn try_from(slice: &[u8]) -> Result { 180 | VerificationKeyBytes::try_from(slice).and_then(|vkb| vkb.try_into()) 181 | } 182 | } 183 | 184 | impl TryFrom<[u8; 32]> for VerificationKey { 185 | type Error = Error; 186 | fn try_from(bytes: [u8; 32]) -> Result { 187 | VerificationKeyBytes::from(bytes).try_into() 188 | } 189 | } 190 | 191 | impl VerificationKey { 192 | /// Returns the byte encoding of the verification key. 193 | /// 194 | /// This is the same as `.into()`, but does not require type inference. 195 | pub fn to_bytes(&self) -> [u8; 32] { 196 | self.A_bytes.0 197 | } 198 | 199 | /// View the byte encoding of the verification key. 200 | pub fn as_bytes(&self) -> &[u8; 32] { 201 | &self.A_bytes.0 202 | } 203 | 204 | /// Verify a purported `signature` on the given `msg`. 205 | /// 206 | /// ## Consensus properties 207 | /// 208 | /// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in 209 | /// [ZIP215]. The verification criteria for an (encoded) signature `(R_bytes, s_bytes)` with 210 | /// (encoded) verification key `A_bytes` are: 211 | /// 212 | /// * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the 213 | /// twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted; 214 | /// 215 | /// * `s_bytes` MUST represent an integer `s` less than `l`, the order of the prime-order 216 | /// subgroup of Curve25519; 217 | /// 218 | /// * the verification equation `[8][s]B = [8]R + [8][k]A` MUST be satisfied; 219 | /// 220 | /// * the alternate verification equation `[s]B = R + [k]A`, allowed by RFC 8032, MUST NOT be 221 | /// used. 222 | /// 223 | /// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519 224 | /// [ZIP215]: https://github.com/zcash/zips/blob/master/zip-0215.rst 225 | pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> { 226 | let k = Scalar::from_hash( 227 | Sha512::default() 228 | .chain(&signature.R_bytes[..]) 229 | .chain(&self.A_bytes.0[..]) 230 | .chain(msg), 231 | ); 232 | self.verify_prehashed(signature, k) 233 | } 234 | 235 | /// Verify a signature with a prehashed `k` value. Note that this is not the 236 | /// same as "prehashing" in RFC8032. 237 | #[allow(non_snake_case)] 238 | pub(crate) fn verify_prehashed(&self, signature: &Signature, k: Scalar) -> Result<(), Error> { 239 | // `s_bytes` MUST represent an integer less than the prime `l`. 240 | let s = Scalar::from_canonical_bytes(signature.s_bytes).ok_or(Error::InvalidSignature)?; 241 | // `R_bytes` MUST be an encoding of a point on the twisted Edwards form of Curve25519. 242 | let R = CompressedEdwardsY(signature.R_bytes) 243 | .decompress() 244 | .ok_or(Error::InvalidSignature)?; 245 | // We checked the encoding of A_bytes when constructing `self`. 246 | 247 | // [8][s]B = [8]R + [8][k]A 248 | // <=> [8]R = [8][s]B - [8][k]A 249 | // <=> 0 = [8](R - ([s]B - [k]A)) 250 | // <=> 0 = [8](R - R') where R' = [s]B - [k]A 251 | let R_prime = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.minus_A, &s); 252 | 253 | if (R - R_prime).mul_by_cofactor().is_identity() { 254 | Ok(()) 255 | } else { 256 | Err(Error::InvalidSignature) 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /tests/util/mod.rs: -------------------------------------------------------------------------------- 1 | // functions are used in small_order but not recognized as such? 2 | #![allow(dead_code)] 3 | 4 | use color_eyre::{eyre::eyre, Report}; 5 | use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; 6 | use ed25519_consensus; 7 | 8 | use std::convert::TryFrom; 9 | pub struct TestCase { 10 | pub vk_bytes: [u8; 32], 11 | pub sig_bytes: [u8; 64], 12 | pub valid_legacy: bool, 13 | pub valid_zip215: bool, 14 | } 15 | 16 | impl core::fmt::Debug for TestCase { 17 | fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { 18 | fmt.debug_struct("TestCase") 19 | .field("vk_bytes", &hex::encode(&self.vk_bytes[..])) 20 | .field("sig_bytes", &hex::encode(&self.sig_bytes[..])) 21 | .field("valid_legacy", &self.valid_legacy) 22 | .field("valid_zip215", &self.valid_zip215) 23 | .finish() 24 | } 25 | } 26 | 27 | impl TestCase { 28 | pub fn check(&self) -> Result<(), Report> { 29 | match (self.valid_legacy, self.check_legacy()) { 30 | (false, Err(_)) => Ok(()), 31 | (true, Ok(())) => Ok(()), 32 | (false, Ok(())) => Err(eyre!( 33 | "legacy-invalid signature case validated under legacy rules" 34 | )), 35 | (true, Err(e)) => { 36 | Err(e.wrap_err("legacy-valid signature case was rejected under legacy rules")) 37 | } 38 | }?; 39 | match (self.valid_zip215, self.check_zip215()) { 40 | (false, Err(_)) => Ok(()), 41 | (true, Ok(())) => Ok(()), 42 | (false, Ok(())) => Err(eyre!( 43 | "zip215-invalid signature case validated under zip215 rules" 44 | )), 45 | (true, Err(e)) => { 46 | Err(e.wrap_err("zip215-valid signature case was rejected under zip215 rules")) 47 | } 48 | } 49 | } 50 | 51 | fn check_legacy(&self) -> Result<(), Report> { 52 | use ed25519_zebra_legacy::{Signature, VerificationKey}; 53 | let sig = Signature::from(self.sig_bytes); 54 | VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; 55 | Ok(()) 56 | } 57 | 58 | fn check_zip215(&self) -> Result<(), Report> { 59 | use ed25519_consensus::{Signature, VerificationKey}; 60 | let sig = Signature::from(self.sig_bytes); 61 | VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; 62 | Ok(()) 63 | } 64 | } 65 | 66 | pub fn non_canonical_field_encodings() -> Vec<[u8; 32]> { 67 | // There are 19 finite field elements which can be represented 68 | // non-canonically as x + p with x + p fitting in 255 bits: 69 | let mut bytes = [ 70 | 237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 71 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 72 | ]; 73 | let mut encodings = Vec::new(); 74 | for i in 0..19u8 { 75 | bytes[0] = 237 + i; 76 | encodings.push(bytes); 77 | } 78 | encodings 79 | } 80 | 81 | // Compute all 25 non-canonical point encodings. The first 5 are low order. 82 | pub fn non_canonical_point_encodings() -> Vec<[u8; 32]> { 83 | // Points are encoded by the y-coordinate and a bit indicating the 84 | // sign of the x-coordinate. There are two ways to construct a 85 | // non-canonical point encoding: 86 | // 87 | // (1) by using a non-canonical encoding of y (cf RFC8032§5.1.3.1) 88 | // (2) by selecting y so that both sign choices give the same x. 89 | // 90 | // Condition (1) can occur only for 19 field elements that can be encoded 91 | // non-canonically as y + p with y + p fitting in 255 bits. 92 | // 93 | // Condition (2) occurs if and only if x = -x, i.e., x = 0. 94 | // The curve equation is ax^2 + y^2 = 1 + dx^2 + y^2 so x = 0 => y^2 = 1. 95 | // This means y = 1 or y = -1. 96 | // 97 | // When y = -1, y can only be canonically encoded, so the encodings of (0,-1) are: 98 | // * enc(-1) || 0 [canonical] 99 | // * enc(-1) || 1 [non-canonical] 100 | // 101 | // When y = 1, y can be non-canonically encoded, so the encodings of (0,1) are: 102 | // * enc(1) || 0 [canonical] 103 | // * enc(1) || 1 [non-canonical] 104 | // * enc(2^255 - 18) || 0 [non-canonical] 105 | // * enc(2^255 - 18) || 1 [non-canonical] 106 | // 107 | // We pick up the latter two in generation of non-canonically encoded field elements, 108 | // and construct the first two explicitly. 109 | // 110 | // RFC8032§5.1.3.4 requires implementations to perform a field element equality check 111 | // on the x value computed inside the decompression routine and abort if x = 0 and 112 | // the sign bit was set. However, no implementations do this, and any implementation 113 | // that did would then be subtly incompatible with others in a new and different way. 114 | // 115 | // (This taxonomy was created with pointers from Sean Bowe and NCC Group). 116 | let mut encodings = Vec::new(); 117 | 118 | // Canonical y with non-canonical sign bits. 119 | let y1_noncanonical_sign_bit = [ 120 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121 | 0, 128, 122 | ]; 123 | encodings.push(y1_noncanonical_sign_bit); 124 | let ym1_noncanonical_sign_bit = [ 125 | 236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 126 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127 | ]; 128 | encodings.push(ym1_noncanonical_sign_bit); 129 | 130 | // Run through non-canonical field elements. 131 | // Not all field elements are x-coordinates of curve points, so check: 132 | for mut x in non_canonical_field_encodings().into_iter() { 133 | if CompressedEdwardsY(x).decompress().is_some() { 134 | encodings.push(x); 135 | } 136 | x[31] |= 128; 137 | if CompressedEdwardsY(x).decompress().is_some() { 138 | encodings.push(x); 139 | } 140 | } 141 | 142 | // Check that all of the non-canonical points are really non-canonical 143 | for &e in &encodings { 144 | assert_ne!( 145 | e, 146 | CompressedEdwardsY(e) 147 | .decompress() 148 | .unwrap() 149 | .compress() 150 | .to_bytes() 151 | ); 152 | } 153 | 154 | encodings 155 | } 156 | 157 | // Running this reveals that only the first 6 entries on the list have low order. 158 | #[test] 159 | fn print_non_canonical_points() { 160 | for encoding in non_canonical_point_encodings().into_iter() { 161 | let point = CompressedEdwardsY(encoding).decompress().unwrap(); 162 | println!( 163 | "encoding {} has order {}", 164 | hex::encode(&encoding[..]), 165 | order(point) 166 | ); 167 | } 168 | } 169 | 170 | pub fn order(point: EdwardsPoint) -> &'static str { 171 | use curve25519_dalek::traits::IsIdentity; 172 | if point.is_small_order() { 173 | let point2 = point + point; 174 | let point4 = point2 + point2; 175 | if point.is_identity() { 176 | "1" 177 | } else if point2.is_identity() { 178 | "2" 179 | } else if point4.is_identity() { 180 | "4" 181 | } else { 182 | "8" 183 | } 184 | } else { 185 | if point.is_torsion_free() { 186 | "p" 187 | } else { 188 | "8p" 189 | } 190 | } 191 | } 192 | 193 | #[test] 194 | fn find_valid_excluded_encodings() { 195 | for (i, encoding) in EXCLUDED_POINT_ENCODINGS.iter().enumerate() { 196 | if let Some(point) = CompressedEdwardsY(*encoding).decompress() { 197 | println!("index {} is valid point of order {}", i, order(point)); 198 | } else { 199 | println!("index {} is not a valid encoding", i); 200 | } 201 | } 202 | } 203 | 204 | /// These point encodings were specifically blacklisted by libsodium 1.0.15, in 205 | /// an apparent (and unsuccessful) attempt to exclude points of low order. 206 | /// 207 | /// To maintain exact compatibility with this version of libsodium, we encode 208 | /// them here, following the Zcash protocol specification. 209 | pub static EXCLUDED_POINT_ENCODINGS: [[u8; 32]; 11] = [ 210 | [ 211 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 212 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 213 | 0x00, 0x00, 214 | ], 215 | [ 216 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 217 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 218 | 0x00, 0x00, 219 | ], 220 | [ 221 | 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 222 | 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 223 | 0xfc, 0x05, 224 | ], 225 | [ 226 | 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 227 | 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 228 | 0x03, 0x7a, 229 | ], 230 | [ 231 | 0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 232 | 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 233 | 0xfc, 0x85, 234 | ], 235 | [ 236 | 0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 237 | 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 238 | 0x03, 0xfa, 239 | ], 240 | [ 241 | 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 242 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 243 | 0xff, 0x7f, 244 | ], 245 | [ 246 | 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 247 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 248 | 0xff, 0x7f, 249 | ], 250 | [ 251 | 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 252 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 253 | 0xff, 0x7f, 254 | ], 255 | [ 256 | 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 257 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 258 | 0xff, 0xff, 259 | ], 260 | [ 261 | 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 262 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 263 | 0xff, 0xff, 264 | ], 265 | ]; 266 | --------------------------------------------------------------------------------