mirror of
https://github.com/saymrwulf/risc0-curve25519-dalek-source.git
synced 2026-09-10 21:00:41 +00:00
Merge branch 'release/1.0.0-pre.3'
This commit is contained in:
commit
36a51acbf0
11 changed files with 741 additions and 290 deletions
68
Cargo.toml
68
Cargo.toml
|
|
@ -1,6 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ed25519-dalek"
|
name = "ed25519-dalek"
|
||||||
version = "1.0.0-pre.2"
|
version = "1.0.0-pre.3"
|
||||||
|
edition = "2018"
|
||||||
authors = ["isis lovecruft <isis@patternsinthevoid.net>"]
|
authors = ["isis lovecruft <isis@patternsinthevoid.net>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
|
|
@ -15,45 +16,25 @@ exclude = [ ".gitignore", "TESTVECTORS", "res/*" ]
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"}
|
travis-ci = { repository = "dalek-cryptography/ed25519-dalek", branch = "master"}
|
||||||
|
|
||||||
[dependencies.curve25519-dalek]
|
[package.metadata.docs.rs]
|
||||||
version = "1"
|
# Disabled for now since this is borked; tracking https://github.com/rust-lang/docs.rs/issues/302
|
||||||
default-features = false
|
# rustdoc-args = ["--html-in-header", ".cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-0.13.2/rustdoc-include-katex-header.html"]
|
||||||
|
features = ["nightly", "batch"]
|
||||||
|
|
||||||
[dependencies.rand_core]
|
[dependencies]
|
||||||
version = "0.3"
|
clear_on_drop = { version = "0.2" }
|
||||||
default-features = false
|
curve25519-dalek = { version = "2", default-features = false }
|
||||||
|
merlin = { version = "1", default-features = false, optional = true, git = "https://github.com/isislovecruft/merlin", branch = "develop" }
|
||||||
[dependencies.rand]
|
rand = { version = "0.7", default-features = false, optional = true }
|
||||||
version = "0.6"
|
rand_core = { version = "0.5", default-features = false, optional = true }
|
||||||
features = ["i128_support"]
|
serde = { version = "1.0", optional = true }
|
||||||
default-features = false
|
sha2 = { version = "0.8", default-features = false }
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.rand_os]
|
|
||||||
version = "0.1"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.serde]
|
|
||||||
version = "^1.0"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.sha2]
|
|
||||||
version = "^0.8"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.failure]
|
|
||||||
version = "^0.1.1"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dependencies.clear_on_drop]
|
|
||||||
version = "0.2"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "^0.3"
|
hex = "^0.4"
|
||||||
bincode = "^0.9"
|
bincode = "^0.9"
|
||||||
criterion = "0.2"
|
criterion = "0.3"
|
||||||
rand = "0.6"
|
rand = "0.7"
|
||||||
rand_os = "0.1"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "ed25519_benchmarks"
|
name = "ed25519_benchmarks"
|
||||||
|
|
@ -64,12 +45,15 @@ harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std", "u64_backend"]
|
default = ["std", "u64_backend"]
|
||||||
std = ["curve25519-dalek/std", "rand_os", "sha2/std"]
|
std = ["curve25519-dalek/std", "sha2/std", "rand/std"]
|
||||||
alloc = ["curve25519-dalek/alloc", "rand_os"]
|
alloc = ["curve25519-dalek/alloc", "rand/alloc"]
|
||||||
nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly"]
|
nightly = ["curve25519-dalek/nightly", "clear_on_drop/nightly", "rand/nightly"]
|
||||||
batch = ["rand"]
|
batch = ["merlin", "rand"]
|
||||||
|
# This feature enables deterministic batch verification.
|
||||||
|
batch_deterministic = ["merlin", "rand", "rand_core"]
|
||||||
asm = ["sha2/asm"]
|
asm = ["sha2/asm"]
|
||||||
yolocrypto = ["curve25519-dalek/yolocrypto"]
|
# This features turns off stricter checking for scalar malleability in signatures
|
||||||
|
legacy_compatibility = []
|
||||||
u64_backend = ["curve25519-dalek/u64_backend"]
|
u64_backend = ["curve25519-dalek/u64_backend"]
|
||||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
u32_backend = ["curve25519-dalek/u32_backend"]
|
||||||
avx2_backend = ["curve25519-dalek/avx2_backend"]
|
simd_backend = ["curve25519-dalek/simd_backend"]
|
||||||
|
|
|
||||||
153
README.md
153
README.md
|
|
@ -7,6 +7,15 @@ verification in Rust.
|
||||||
|
|
||||||
Documentation is available [here](https://docs.rs/ed25519-dalek).
|
Documentation is available [here](https://docs.rs/ed25519-dalek).
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
To install, add the following to your project's `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies.ed25519-dalek]
|
||||||
|
version = "1"
|
||||||
|
```
|
||||||
|
|
||||||
# Benchmarks
|
# Benchmarks
|
||||||
|
|
||||||
On an Intel Skylake i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves
|
On an Intel Skylake i9-7900X running at 3.30 GHz, without TurboBoost, this code achieves
|
||||||
|
|
@ -89,14 +98,20 @@ can read qhasm, making it more readily and more easily auditable. We're of
|
||||||
the opinion that, ultimately, these features—combined with speed—are more
|
the opinion that, ultimately, these features—combined with speed—are more
|
||||||
valuable than simply cycle counts alone.
|
valuable than simply cycle counts alone.
|
||||||
|
|
||||||
### A Note on Signature Malleability
|
# A Note on Signature Malleability
|
||||||
|
|
||||||
The signatures produced by this library are malleable, as discussed in
|
The signatures produced by this library are malleable, as discussed in
|
||||||
[the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf):
|
[the original paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf):
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
We could eliminate the malleability property by multiplying by the curve
|
While the scalar component of our `Signature` struct is strictly *not*
|
||||||
|
malleable, because reduction checks are put in place upon `Signature`
|
||||||
|
deserialisation from bytes, for all types of signatures in this crate,
|
||||||
|
there is still the question of potential malleability due to the group
|
||||||
|
element components.
|
||||||
|
|
||||||
|
We could eliminate the latter malleability property by multiplying by the curve
|
||||||
cofactor, however, this would cause our implementation to *not* match the
|
cofactor, however, this would cause our implementation to *not* match the
|
||||||
behaviour of every other implementation in existence. As of this writing,
|
behaviour of every other implementation in existence. As of this writing,
|
||||||
[RFC 8032](https://tools.ietf.org/html/rfc8032), "Edwards-Curve Digital
|
[RFC 8032](https://tools.ietf.org/html/rfc8032), "Edwards-Curve Digital
|
||||||
|
|
@ -105,30 +120,98 @@ While we agree that the stronger check should be done, it is our opinion that
|
||||||
one shouldn't get to change the definition of "ed25519 verification" a decade
|
one shouldn't get to change the definition of "ed25519 verification" a decade
|
||||||
after the fact, breaking compatibility with every other implementation.
|
after the fact, breaking compatibility with every other implementation.
|
||||||
|
|
||||||
In short, if malleable signatures are bad for your protocol, don't use them.
|
However, if you require this, please see the documentation for the
|
||||||
Consider using a curve25519-based Verifiable Random Function (VRF), such as
|
`verify_strict()` function, which does the full checks for the group elements.
|
||||||
[Trevor Perrin's VXEdDSA](https://www.whispersystems.org/docs/specifications/xeddsa/),
|
This functionality is available by default.
|
||||||
instead. We
|
|
||||||
[plan](https://github.com/dalek-cryptography/curve25519-dalek/issues/9) to
|
|
||||||
eventually support VXEdDSA in curve25519-dalek.
|
|
||||||
|
|
||||||
# Installation
|
If for some reason—although we strongely advise you not to—you need to conform
|
||||||
|
to the original specification of ed25519 signatures as in the excerpt from the
|
||||||
|
paper above, you can disable scalar malleability checking via
|
||||||
|
`--features='legacy_compatibility'`. **WE STRONGLY ADVISE AGAINST THIS.**
|
||||||
|
|
||||||
To install, add the following to your project's `Cargo.toml`:
|
## The `legacy_compatibility` Feature
|
||||||
|
|
||||||
```toml
|
By default, this library performs a stricter check for malleability in the
|
||||||
[dependencies.ed25519-dalek]
|
scalar component of a signature, upon signature deserialisation. This stricter
|
||||||
version = "1"
|
check, that `s < \ell` where `\ell` is the order of the basepoint, is
|
||||||
```
|
[mandated by RFC8032](https://tools.ietf.org/html/rfc8032#section-5.1.7).
|
||||||
|
However, that RFC was standardised a decade after the original paper, which, as
|
||||||
|
described above, (usually, falsely) stated that malleability was inconsequential.
|
||||||
|
|
||||||
Then, in your library or executable source, add:
|
Because of this, most ed25519 implementations only perform a limited, hackier
|
||||||
|
check that the most significant three bits of the scalar are unset. If you need
|
||||||
|
compatibility with legacy implementations, including:
|
||||||
|
|
||||||
```rust
|
* ed25519-donna
|
||||||
extern crate ed25519_dalek;
|
* Golang's /x/crypto ed25519
|
||||||
```
|
* libsodium (only when built with `-DED25519_COMPAT`)
|
||||||
|
* NaCl's "ref" implementation
|
||||||
|
* probably a bunch of others
|
||||||
|
|
||||||
|
then enable `ed25519-dalek`'s `legacy_compatibility` feature. Please note and
|
||||||
|
be forewarned that doing so allows for signature malleability, meaning that
|
||||||
|
there may be two different and "valid" signatures with the same key for the same
|
||||||
|
message, which is obviously incredibly dangerous in a number of contexts,
|
||||||
|
including—but not limited to—identification protocols and cryptocurrency
|
||||||
|
transactions.
|
||||||
|
|
||||||
|
## The `verify_strict()` Function
|
||||||
|
|
||||||
|
The scalar component of a signature is not the only source of signature
|
||||||
|
malleability, however. Both the public key used for signature verification and
|
||||||
|
the group element component of the signature are malleable, as they may contain
|
||||||
|
a small torsion component as a consquence of the curve25519 group not being of
|
||||||
|
prime order, but having a small cofactor of 8.
|
||||||
|
|
||||||
|
If you wish to also eliminate this source of signature malleability, please
|
||||||
|
review the
|
||||||
|
[documentation for the `verify_strict()` function](https://doc.dalek.rs/ed25519_dalek/struct.PublicKey.html#method.verify_strict).
|
||||||
|
|
||||||
|
# A Note on Randomness Generation
|
||||||
|
|
||||||
|
The original paper's specification and the standarisation of RFC8032 do not
|
||||||
|
specify precisely how randomness is to be generated, other than using a CSPRNG
|
||||||
|
(Cryptographically Secure Random Number Generator). Particularly in the case of
|
||||||
|
signature verification, where the security proof _relies_ on the uniqueness of
|
||||||
|
the blinding factors/nonces, it is paramount that these samples of randomness be
|
||||||
|
unguessable to an adversary. Because of this, a current growing belief among
|
||||||
|
cryptographers is that it is safer to prefer _synthetic randomness_.
|
||||||
|
|
||||||
|
To explain synthetic randomness, we should first explain how `ed25519-dalek`
|
||||||
|
handles generation of _deterministic randomness_. This mode is disabled by
|
||||||
|
default due to a tiny-but-not-nonexistent chance that this mode will open users
|
||||||
|
up to fault attacks, wherein an adversary who controls all of the inputs to
|
||||||
|
batch verification (i.e. the public keys, signatures, and messages) can craft
|
||||||
|
them in a specialised manner such as to induce a fault (e.g. causing a
|
||||||
|
mistakenly flipped bit in RAM, overheating a processor, etc.). In the
|
||||||
|
deterministic mode, we seed the PRNG which generates our blinding factors/nonces
|
||||||
|
by creating
|
||||||
|
[a PRNG based on the Fiat-Shamir transform of the public inputs](https://merlin.cool/transcript/rng.html).
|
||||||
|
This mode is potentially useful to protocols which require strong auditability
|
||||||
|
guarantees, as well as those which do not have access to secure system-/chip-
|
||||||
|
provided randomness. This feature can be enabled via
|
||||||
|
`--features='batch_deterministic'`. Note that we _do not_ support deterministic
|
||||||
|
signing, due to the numerous pitfalls therein, including a re-used nonce
|
||||||
|
accidentally revealing the secret key.
|
||||||
|
|
||||||
|
In the default mode, we do as above in the fully deterministic mode, but we
|
||||||
|
ratchet the underlying keccak-f1600 function (used for the provided
|
||||||
|
transcript-based PRNG) forward additionally based on some system-/chip- provided
|
||||||
|
randomness. This provides _synthetic randomness_, that is, randomness based on
|
||||||
|
both deterministic and undeterinistic data. The reason for doing this is to
|
||||||
|
prevent badly seeded system RNGs from ruining the security of the signature
|
||||||
|
verification scheme.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
## #![no_std]
|
||||||
|
|
||||||
|
This library aims to be `#![no_std]` compliant. If batch verification is
|
||||||
|
required (`--features='batch'`), please enable either of the `std` or `alloc`
|
||||||
|
features.
|
||||||
|
|
||||||
|
## Nightly Compilers
|
||||||
|
|
||||||
To cause your application to build `ed25519-dalek` with the nightly feature
|
To cause your application to build `ed25519-dalek` with the nightly feature
|
||||||
enabled by default, instead do:
|
enabled by default, instead do:
|
||||||
|
|
||||||
|
|
@ -147,19 +230,31 @@ to the `Cargo.toml`:
|
||||||
nightly = ["ed25519-dalek/nightly"]
|
nightly = ["ed25519-dalek/nightly"]
|
||||||
```
|
```
|
||||||
|
|
||||||
To enable [serde](https://serde.rs) support, build `ed25519-dalek` with:
|
## Serde
|
||||||
|
|
||||||
```toml
|
To enable [serde](https://serde.rs) support, build `ed25519-dalek` with the
|
||||||
[dependencies.ed25519-dalek]
|
`serde` feature.
|
||||||
version = "1"
|
|
||||||
features = ["serde"]
|
## (Micro)Architecture Specific Backends
|
||||||
```
|
|
||||||
|
|
||||||
By default, `ed25519-dalek` builds against `curve25519-dalek`'s `u64_backend`
|
By default, `ed25519-dalek` builds against `curve25519-dalek`'s `u64_backend`
|
||||||
feature, which uses Rust's `i128` feature to achieve roughly double the speed as
|
feature, which uses Rust's `i128` feature to achieve roughly double the speed as
|
||||||
the `u32_backend` feature. When targetting 32-bit systems, however, you'll
|
the `u32_backend` feature. When targetting 32-bit systems, however, you'll
|
||||||
likely want to compile with
|
likely want to compile with `cargo build --no-default-features
|
||||||
`cargo build --no-default-features --features="u32_backend"`.
|
--features="u32_backend"`. If you're building for a machine with avx2
|
||||||
If you're building for a machine with avx2 instructions, there's also the
|
instructions, there's also the experimental `simd_backend`s, currently
|
||||||
experimental `avx2_backend`. To use it, compile with
|
comprising either avx2 or avx512 backends. To use them, compile with
|
||||||
`RUSTFLAGS="-C target_cpu=native" cargo build --no-default-features --features="avx2_backend"`
|
`RUSTFLAGS="-C target_cpu=native" cargo build --no-default-features
|
||||||
|
--features="simd_backend"`
|
||||||
|
|
||||||
|
## Batch Signature Verification
|
||||||
|
|
||||||
|
The standard variants of batch signature verification (i.e. many signatures made
|
||||||
|
with potentially many different public keys over potentially many different
|
||||||
|
message) is available via the `batch` feature. It uses synthetic randomness, as
|
||||||
|
noted above.
|
||||||
|
|
||||||
|
### Deterministic Batch Signature Verification
|
||||||
|
|
||||||
|
The same notion of batch signature verification as above, but with purely
|
||||||
|
deterministic randomness can be enabled via the `batch_deterministic` feature.
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,17 @@ mod ed25519_benches {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_strict(c: &mut Criterion) {
|
||||||
|
let mut csprng: ThreadRng = thread_rng();
|
||||||
|
let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
|
let msg: &[u8] = b"";
|
||||||
|
let sig: Signature = keypair.sign(msg);
|
||||||
|
|
||||||
|
c.bench_function("Ed25519 strict signature verification", move |b| {
|
||||||
|
b.iter(| | keypair.verify_strict(msg, &sig))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn verify_batch_signatures(c: &mut Criterion) {
|
fn verify_batch_signatures(c: &mut Criterion) {
|
||||||
static BATCH_SIZES: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256];
|
static BATCH_SIZES: [usize; 8] = [4, 8, 16, 32, 64, 96, 128, 256];
|
||||||
|
|
||||||
|
|
@ -90,6 +101,7 @@ mod ed25519_benches {
|
||||||
sign,
|
sign,
|
||||||
sign_expanded_key,
|
sign_expanded_key,
|
||||||
verify,
|
verify,
|
||||||
|
verify_strict,
|
||||||
verify_batch_signatures,
|
verify_batch_signatures,
|
||||||
key_generation,
|
key_generation,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
225
src/batch.rs
Normal file
225
src/batch.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
// -*- mode: rust; -*-
|
||||||
|
//
|
||||||
|
// This file is part of ed25519-dalek.
|
||||||
|
// Copyright (c) 2017-2019 isis lovecruft
|
||||||
|
// See LICENSE for licensing information.
|
||||||
|
//
|
||||||
|
// Authors:
|
||||||
|
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||||
|
|
||||||
|
//! Batch signature verification.
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use core::iter::once;
|
||||||
|
|
||||||
|
use curve25519_dalek::constants;
|
||||||
|
use curve25519_dalek::edwards::EdwardsPoint;
|
||||||
|
use curve25519_dalek::scalar::Scalar;
|
||||||
|
use curve25519_dalek::traits::IsIdentity;
|
||||||
|
use curve25519_dalek::traits::VartimeMultiscalarMul;
|
||||||
|
|
||||||
|
pub use curve25519_dalek::digest::Digest;
|
||||||
|
|
||||||
|
use merlin::Transcript;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
#[cfg(all(feature = "batch", not(feature = "batch_deterministic")))]
|
||||||
|
use rand::thread_rng;
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
use rand_core;
|
||||||
|
|
||||||
|
use sha2::Sha512;
|
||||||
|
|
||||||
|
use crate::errors::InternalError;
|
||||||
|
use crate::errors::SignatureError;
|
||||||
|
use crate::public::PublicKey;
|
||||||
|
use crate::signature::Signature;
|
||||||
|
|
||||||
|
trait BatchTranscript {
|
||||||
|
fn append_hrams(&mut self, hrams: &Vec<Scalar>);
|
||||||
|
fn append_message_lengths(&mut self, message_lengths: &Vec<usize>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BatchTranscript for Transcript {
|
||||||
|
/// Add all the computed `H(R||A||M)`s to the protocol transcript.
|
||||||
|
///
|
||||||
|
/// Each is also prefixed with their index in the vector.
|
||||||
|
fn append_hrams(&mut self, hrams: &Vec<Scalar>) {
|
||||||
|
for (i, hram) in hrams.iter().enumerate() {
|
||||||
|
// XXX add message length into transcript
|
||||||
|
self.append_u64(b"", i as u64);
|
||||||
|
self.append_message(b"hram", hram.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_message_lengths(&mut self, message_lengths: &Vec<usize>) {
|
||||||
|
for (i, len) in message_lengths.iter().enumerate() {
|
||||||
|
self.append_u64(b"", i as u64);
|
||||||
|
self.append_u64(b"mlen", *len as u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An implementation of `rand_core::RngCore` which does nothing, to provide
|
||||||
|
/// purely deterministic transcript-based nonces, rather than synthetically
|
||||||
|
/// random nonces.
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
struct ZeroRng {}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
impl rand_core::RngCore for ZeroRng {
|
||||||
|
fn next_u32(&mut self) -> u32 {
|
||||||
|
rand_core::impls::next_u32_via_fill(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_u64(&mut self) -> u64 {
|
||||||
|
rand_core::impls::next_u64_via_fill(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A no-op function which leaves the destination bytes for randomness unchanged.
|
||||||
|
///
|
||||||
|
/// In this case, the internal merlin code is initialising the destination
|
||||||
|
/// by doing `[0u8; …]`, which means that when we call
|
||||||
|
/// `merlin::TranscriptRngBuilder.finalize()`, rather than rekeying the
|
||||||
|
/// STROBE state based on external randomness, we're doing an
|
||||||
|
/// `ENC_{state}(00000000000000000000000000000000)` operation, which is
|
||||||
|
/// identical to the STROBE `MAC` operation.
|
||||||
|
fn fill_bytes(&mut self, _dest: &mut [u8]) { }
|
||||||
|
|
||||||
|
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||||
|
self.fill_bytes(dest);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
impl rand_core::CryptoRng for ZeroRng {}
|
||||||
|
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
fn zero_rng() -> ZeroRng {
|
||||||
|
ZeroRng {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify a batch of `signatures` on `messages` with their respective `public_keys`.
|
||||||
|
///
|
||||||
|
/// # Inputs
|
||||||
|
///
|
||||||
|
/// * `messages` is a slice of byte slices, one per signed message.
|
||||||
|
/// * `signatures` is a slice of `Signature`s.
|
||||||
|
/// * `public_keys` is a slice of `PublicKey`s.
|
||||||
|
/// * `csprng` is an implementation of `Rng + CryptoRng`.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a
|
||||||
|
/// `SignatureError` containing a description of the internal error which
|
||||||
|
/// occured.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// extern crate ed25519_dalek;
|
||||||
|
/// extern crate rand;
|
||||||
|
///
|
||||||
|
/// use ed25519_dalek::verify_batch;
|
||||||
|
/// use ed25519_dalek::Keypair;
|
||||||
|
/// use ed25519_dalek::PublicKey;
|
||||||
|
/// use ed25519_dalek::Signature;
|
||||||
|
/// use rand::rngs::OsRng;
|
||||||
|
///
|
||||||
|
/// # fn main() {
|
||||||
|
/// let mut csprng = OsRng{};
|
||||||
|
/// let keypairs: Vec<Keypair> = (0..64).map(|_| Keypair::generate(&mut csprng)).collect();
|
||||||
|
/// let msg: &[u8] = b"They're good dogs Brant";
|
||||||
|
/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect();
|
||||||
|
/// let signatures: Vec<Signature> = keypairs.iter().map(|key| key.sign(&msg)).collect();
|
||||||
|
/// let public_keys: Vec<PublicKey> = keypairs.iter().map(|key| key.public).collect();
|
||||||
|
///
|
||||||
|
/// let result = verify_batch(&messages[..], &signatures[..], &public_keys[..]);
|
||||||
|
/// assert!(result.is_ok());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(all(any(feature = "batch", feature = "batch_deterministic"),
|
||||||
|
any(feature = "alloc", feature = "std")))]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn verify_batch(
|
||||||
|
messages: &[&[u8]],
|
||||||
|
signatures: &[Signature],
|
||||||
|
public_keys: &[PublicKey],
|
||||||
|
) -> Result<(), SignatureError>
|
||||||
|
{
|
||||||
|
// Return an Error if any of the vectors were not the same size as the others.
|
||||||
|
if signatures.len() != messages.len() ||
|
||||||
|
signatures.len() != public_keys.len() ||
|
||||||
|
public_keys.len() != messages.len() {
|
||||||
|
return Err(SignatureError(InternalError::ArrayLengthError{
|
||||||
|
name_a: "signatures", length_a: signatures.len(),
|
||||||
|
name_b: "messages", length_b: messages.len(),
|
||||||
|
name_c: "public_keys", length_c: public_keys.len(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute H(R || A || M) for each (signature, public_key, message) triplet
|
||||||
|
let hrams: Vec<Scalar> = (0..signatures.len()).map(|i| {
|
||||||
|
let mut h: Sha512 = Sha512::default();
|
||||||
|
h.input(signatures[i].R.as_bytes());
|
||||||
|
h.input(public_keys[i].as_bytes());
|
||||||
|
h.input(&messages[i]);
|
||||||
|
Scalar::from_hash(h)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// Collect the message lengths to add into the transcript.
|
||||||
|
let message_lengths: Vec<usize> = messages.iter().map(|i| i.len()).collect();
|
||||||
|
|
||||||
|
// Build a PRNG based on a transcript of the H(R || A || M)s seen thus far.
|
||||||
|
// This provides synthethic randomness in the default configuration, and
|
||||||
|
// purely deterministic in the case of compiling with the
|
||||||
|
// "batch_deterministic" feature.
|
||||||
|
let mut transcript: Transcript = Transcript::new(b"ed25519 batch verification");
|
||||||
|
|
||||||
|
transcript.append_hrams(&hrams);
|
||||||
|
transcript.append_message_lengths(&message_lengths);
|
||||||
|
|
||||||
|
#[cfg(all(feature = "batch", not(feature = "batch_deterministic")))]
|
||||||
|
let mut prng = transcript.build_rng().finalize(&mut thread_rng());
|
||||||
|
#[cfg(all(not(feature = "batch"), feature = "batch_deterministic"))]
|
||||||
|
let mut prng = transcript.build_rng().finalize(&mut zero_rng());
|
||||||
|
|
||||||
|
// Select a random 128-bit scalar for each signature.
|
||||||
|
let zs: Vec<Scalar> = signatures
|
||||||
|
.iter()
|
||||||
|
.map(|_| Scalar::from(prng.gen::<u128>()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
|
// Compute the basepoint coefficient, ∑ s[i]z[i] (mod l)
|
||||||
|
let B_coefficient: Scalar = signatures
|
||||||
|
.iter()
|
||||||
|
.map(|sig| sig.s)
|
||||||
|
.zip(zs.iter())
|
||||||
|
.map(|(s, z)| z * s)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
// Multiply each H(R || A || M) by the random value
|
||||||
|
let zhrams = hrams.iter().zip(zs.iter()).map(|(hram, z)| hram * z);
|
||||||
|
|
||||||
|
let Rs = signatures.iter().map(|sig| sig.R.decompress());
|
||||||
|
let As = public_keys.iter().map(|pk| Some(pk.1));
|
||||||
|
let B = once(Some(constants::ED25519_BASEPOINT_POINT));
|
||||||
|
|
||||||
|
// Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0
|
||||||
|
let id = EdwardsPoint::optional_multiscalar_mul(
|
||||||
|
once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams),
|
||||||
|
B.chain(Rs).chain(As),
|
||||||
|
).ok_or_else(|| SignatureError(InternalError::VerifyError))?;
|
||||||
|
|
||||||
|
if id.is_identity() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(SignatureError(InternalError::VerifyError))
|
||||||
|
}
|
||||||
|
}
|
||||||
229
src/ed25519.rs
229
src/ed25519.rs
|
|
@ -7,151 +7,32 @@
|
||||||
// Authors:
|
// Authors:
|
||||||
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
// - isis agora lovecruft <isis@patternsinthevoid.net>
|
||||||
|
|
||||||
//! ed25519 keypairs and batch verification.
|
//! ed25519 keypairs.
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use core::default::Default;
|
use core::default::Default;
|
||||||
|
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::de::Error as SerdeError;
|
use serde::de::Error as SerdeError;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use serde::{Deserializer, Serializer};
|
|
||||||
|
|
||||||
pub use sha2::Sha512;
|
pub use sha2::Sha512;
|
||||||
|
|
||||||
use curve25519_dalek::digest::generic_array::typenum::U64;
|
use curve25519_dalek::digest::generic_array::typenum::U64;
|
||||||
pub use curve25519_dalek::digest::Digest;
|
pub use curve25519_dalek::digest::Digest;
|
||||||
|
|
||||||
#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))]
|
#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc")))]
|
||||||
use curve25519_dalek::constants;
|
pub use crate::batch::*;
|
||||||
#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))]
|
|
||||||
use curve25519_dalek::edwards::EdwardsPoint;
|
|
||||||
#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))]
|
|
||||||
use curve25519_dalek::scalar::Scalar;
|
|
||||||
|
|
||||||
pub use crate::constants::*;
|
pub use crate::constants::*;
|
||||||
pub use crate::errors::*;
|
pub use crate::errors::*;
|
||||||
pub use crate::public::*;
|
pub use crate::public::*;
|
||||||
pub use crate::secret::*;
|
pub use crate::secret::*;
|
||||||
pub use crate::signature::*;
|
pub use crate::signature::*;
|
||||||
|
|
||||||
/// Verify a batch of `signatures` on `messages` with their respective `public_keys`.
|
|
||||||
///
|
|
||||||
/// # Inputs
|
|
||||||
///
|
|
||||||
/// * `messages` is a slice of byte slices, one per signed message.
|
|
||||||
/// * `signatures` is a slice of `Signature`s.
|
|
||||||
/// * `public_keys` is a slice of `PublicKey`s.
|
|
||||||
/// * `csprng` is an implementation of `Rng + CryptoRng`.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This function will panic if the `messages, `signatures`, and `public_keys`
|
|
||||||
/// slices are not equal length.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * A `Result` whose `Ok` value is an emtpy tuple and whose `Err` value is a
|
|
||||||
/// `SignatureError` containing a description of the internal error which
|
|
||||||
/// occured.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// extern crate ed25519_dalek;
|
|
||||||
/// extern crate rand_os;
|
|
||||||
///
|
|
||||||
/// use ed25519_dalek::verify_batch;
|
|
||||||
/// use ed25519_dalek::Keypair;
|
|
||||||
/// use ed25519_dalek::PublicKey;
|
|
||||||
/// use ed25519_dalek::Signature;
|
|
||||||
/// use rand_os::OsRng;
|
|
||||||
///
|
|
||||||
/// # fn main() {
|
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
|
||||||
/// let keypairs: Vec<Keypair> = (0..64).map(|_| Keypair::generate(&mut csprng)).collect();
|
|
||||||
/// let msg: &[u8] = b"They're good dogs Brant";
|
|
||||||
/// let messages: Vec<&[u8]> = (0..64).map(|_| msg).collect();
|
|
||||||
/// let signatures: Vec<Signature> = keypairs.iter().map(|key| key.sign(&msg)).collect();
|
|
||||||
/// let public_keys: Vec<PublicKey> = keypairs.iter().map(|key| key.public).collect();
|
|
||||||
///
|
|
||||||
/// let result = verify_batch(&messages[..], &signatures[..], &public_keys[..]);
|
|
||||||
/// assert!(result.is_ok());
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
#[cfg(all(feature = "batch", any(feature = "alloc", feature = "std")))]
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn verify_batch(
|
|
||||||
messages: &[&[u8]],
|
|
||||||
signatures: &[Signature],
|
|
||||||
public_keys: &[PublicKey],
|
|
||||||
) -> Result<(), SignatureError>
|
|
||||||
{
|
|
||||||
const ASSERT_MESSAGE: &'static str = "The number of messages, signatures, and public keys must be equal.";
|
|
||||||
assert!(signatures.len() == messages.len(), ASSERT_MESSAGE);
|
|
||||||
assert!(signatures.len() == public_keys.len(), ASSERT_MESSAGE);
|
|
||||||
assert!(public_keys.len() == messages.len(), ASSERT_MESSAGE);
|
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
use core::iter::once;
|
|
||||||
use rand::{Rng, thread_rng};
|
|
||||||
|
|
||||||
use curve25519_dalek::traits::IsIdentity;
|
|
||||||
use curve25519_dalek::traits::VartimeMultiscalarMul;
|
|
||||||
|
|
||||||
// Select a random 128-bit scalar for each signature.
|
|
||||||
let zs: Vec<Scalar> = signatures
|
|
||||||
.iter()
|
|
||||||
.map(|_| Scalar::from(thread_rng().gen::<u128>()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Compute the basepoint coefficient, ∑ s[i]z[i] (mod l)
|
|
||||||
let B_coefficient: Scalar = signatures
|
|
||||||
.iter()
|
|
||||||
.map(|sig| sig.s)
|
|
||||||
.zip(zs.iter())
|
|
||||||
.map(|(s, z)| z * s)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
// Compute H(R || A || M) for each (signature, public_key, message) triplet
|
|
||||||
let hrams = (0..signatures.len()).map(|i| {
|
|
||||||
let mut h: Sha512 = Sha512::default();
|
|
||||||
h.input(signatures[i].R.as_bytes());
|
|
||||||
h.input(public_keys[i].as_bytes());
|
|
||||||
h.input(&messages[i]);
|
|
||||||
Scalar::from_hash(h)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Multiply each H(R || A || M) by the random value
|
|
||||||
let zhrams = hrams.zip(zs.iter()).map(|(hram, z)| hram * z);
|
|
||||||
|
|
||||||
let Rs = signatures.iter().map(|sig| sig.R.decompress());
|
|
||||||
let As = public_keys.iter().map(|pk| Some(pk.1));
|
|
||||||
let B = once(Some(constants::ED25519_BASEPOINT_POINT));
|
|
||||||
|
|
||||||
// Compute (-∑ z[i]s[i] (mod l)) B + ∑ z[i]R[i] + ∑ (z[i]H(R||A||M)[i] (mod l)) A[i] = 0
|
|
||||||
let id = EdwardsPoint::optional_multiscalar_mul(
|
|
||||||
once(-B_coefficient).chain(zs.iter().cloned()).chain(zhrams),
|
|
||||||
B.chain(Rs).chain(As),
|
|
||||||
).ok_or_else(|| SignatureError(InternalError::VerifyError))?;
|
|
||||||
|
|
||||||
if id.is_identity() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SignatureError(InternalError::VerifyError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An ed25519 keypair.
|
/// An ed25519 keypair.
|
||||||
#[derive(Debug, Default)] // we derive Default in order to use the clear() method in Drop
|
#[derive(Debug, Default)] // we derive Default in order to use the clear() method in Drop
|
||||||
pub struct Keypair {
|
pub struct Keypair {
|
||||||
|
|
@ -216,19 +97,17 @@ impl Keypair {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// extern crate rand_core;
|
/// extern crate rand;
|
||||||
/// extern crate rand_os;
|
|
||||||
/// extern crate ed25519_dalek;
|
/// extern crate ed25519_dalek;
|
||||||
///
|
///
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
///
|
///
|
||||||
/// use rand_core::{CryptoRng, RngCore};
|
/// use rand::rngs::OsRng;
|
||||||
/// use rand_os::OsRng;
|
|
||||||
/// use ed25519_dalek::Keypair;
|
/// use ed25519_dalek::Keypair;
|
||||||
/// use ed25519_dalek::Signature;
|
/// use ed25519_dalek::Signature;
|
||||||
///
|
///
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
///
|
///
|
||||||
/// # }
|
/// # }
|
||||||
|
|
@ -283,17 +162,17 @@ impl Keypair {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// extern crate ed25519_dalek;
|
/// extern crate ed25519_dalek;
|
||||||
/// extern crate rand_os;
|
/// extern crate rand;
|
||||||
///
|
///
|
||||||
/// use ed25519_dalek::Digest;
|
/// use ed25519_dalek::Digest;
|
||||||
/// use ed25519_dalek::Keypair;
|
/// use ed25519_dalek::Keypair;
|
||||||
/// use ed25519_dalek::Sha512;
|
/// use ed25519_dalek::Sha512;
|
||||||
/// use ed25519_dalek::Signature;
|
/// use ed25519_dalek::Signature;
|
||||||
/// use rand_os::OsRng;
|
/// use rand::rngs::OsRng;
|
||||||
///
|
///
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let mut csprng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||||
///
|
///
|
||||||
|
|
@ -330,17 +209,17 @@ impl Keypair {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate ed25519_dalek;
|
/// # extern crate ed25519_dalek;
|
||||||
/// # extern crate rand_os;
|
/// # extern crate rand;
|
||||||
/// #
|
/// #
|
||||||
/// # use ed25519_dalek::Digest;
|
/// # use ed25519_dalek::Digest;
|
||||||
/// # use ed25519_dalek::Keypair;
|
/// # use ed25519_dalek::Keypair;
|
||||||
/// # use ed25519_dalek::Signature;
|
/// # use ed25519_dalek::Signature;
|
||||||
/// # use ed25519_dalek::Sha512;
|
/// # use ed25519_dalek::Sha512;
|
||||||
/// # use rand_os::OsRng;
|
/// # use rand::rngs::OsRng;
|
||||||
/// #
|
/// #
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// # let mut csprng: OsRng = OsRng::new().unwrap();
|
/// # let mut csprng = OsRng{};
|
||||||
/// # let keypair: Keypair = Keypair::generate(&mut csprng);
|
/// # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
/// # let message: &[u8] = b"All I want is to pet all of the dogs.";
|
/// # let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||||
/// # let mut prehashed: Sha512 = Sha512::new();
|
/// # let mut prehashed: Sha512 = Sha512::new();
|
||||||
|
|
@ -360,7 +239,7 @@ impl Keypair {
|
||||||
pub fn sign_prehashed<D>(
|
pub fn sign_prehashed<D>(
|
||||||
&self,
|
&self,
|
||||||
prehashed_message: D,
|
prehashed_message: D,
|
||||||
context: Option<&'static [u8]>,
|
context: Option<&[u8]>,
|
||||||
) -> Signature
|
) -> Signature
|
||||||
where
|
where
|
||||||
D: Digest<OutputSize = U64>,
|
D: Digest<OutputSize = U64>,
|
||||||
|
|
@ -401,17 +280,17 @@ impl Keypair {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// extern crate ed25519_dalek;
|
/// extern crate ed25519_dalek;
|
||||||
/// extern crate rand_os;
|
/// extern crate rand;
|
||||||
///
|
///
|
||||||
/// use ed25519_dalek::Digest;
|
/// use ed25519_dalek::Digest;
|
||||||
/// use ed25519_dalek::Keypair;
|
/// use ed25519_dalek::Keypair;
|
||||||
/// use ed25519_dalek::Signature;
|
/// use ed25519_dalek::Signature;
|
||||||
/// use ed25519_dalek::Sha512;
|
/// use ed25519_dalek::Sha512;
|
||||||
/// use rand_os::OsRng;
|
/// use rand::rngs::OsRng;
|
||||||
///
|
///
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
/// let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||||
///
|
///
|
||||||
|
|
@ -447,6 +326,78 @@ impl Keypair {
|
||||||
{
|
{
|
||||||
self.public.verify_prehashed(prehashed_message, context, signature)
|
self.public.verify_prehashed(prehashed_message, context, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strictly verify a signature on a message with this keypair's public key.
|
||||||
|
///
|
||||||
|
/// # On The (Multiple) Sources of Malleability in Ed25519 Signatures
|
||||||
|
///
|
||||||
|
/// This version of verification is technically non-RFC8032 compliant. The
|
||||||
|
/// following explains why.
|
||||||
|
///
|
||||||
|
/// 1. Scalar Malleability
|
||||||
|
///
|
||||||
|
/// The authors of the RFC explicitly stated that verification of an ed25519
|
||||||
|
/// signature must fail if the scalar `s` is not properly reduced mod \ell:
|
||||||
|
///
|
||||||
|
/// > To verify a signature on a message M using public key A, with F
|
||||||
|
/// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or
|
||||||
|
/// > Ed25519ph is being used, C being the context, first split the
|
||||||
|
/// > signature into two 32-octet halves. Decode the first half as a
|
||||||
|
/// > point R, and the second half as an integer S, in the range
|
||||||
|
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||||
|
/// > decodings fail (including S being out of range), the signature is
|
||||||
|
/// > invalid.)
|
||||||
|
///
|
||||||
|
/// All `verify_*()` functions within ed25519-dalek perform this check.
|
||||||
|
///
|
||||||
|
/// 2. Point malleability
|
||||||
|
///
|
||||||
|
/// The authors of the RFC added in a malleability check to step #3 in
|
||||||
|
/// §5.1.7, for small torsion components in the `R` value of the signature,
|
||||||
|
/// *which is not strictly required*, as they state:
|
||||||
|
///
|
||||||
|
/// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's
|
||||||
|
/// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'.
|
||||||
|
///
|
||||||
|
/// # History of Malleability Checks
|
||||||
|
///
|
||||||
|
/// As originally defined (cf. the "Malleability" section in the README of
|
||||||
|
/// this repo), ed25519 signatures didn't consider *any* form of
|
||||||
|
/// malleability to be an issue. Later the scalar malleability was
|
||||||
|
/// considered important. Still later, particularly with interests in
|
||||||
|
/// cryptocurrency design and in unique identities (e.g. for Signal users,
|
||||||
|
/// Tor onion services, etc.), the group element malleability became a
|
||||||
|
/// concern.
|
||||||
|
///
|
||||||
|
/// However, libraries had already been created to conform to the original
|
||||||
|
/// definition. One well-used library in particular even implemented the
|
||||||
|
/// group element malleability check, *but only for batch verification*!
|
||||||
|
/// Which meant that even using the same library, a single signature could
|
||||||
|
/// verify fine individually, but suddenly, when verifying it with a bunch
|
||||||
|
/// of other signatures, the whole batch would fail!
|
||||||
|
///
|
||||||
|
/// # "Strict" Verification
|
||||||
|
///
|
||||||
|
/// This method performs *both* of the above signature malleability checks.
|
||||||
|
///
|
||||||
|
/// It must be done as a separate method because one doesn't simply get to
|
||||||
|
/// change the definition of a cryptographic primitive ten years
|
||||||
|
/// after-the-fact with zero consideration for backwards compatibility in
|
||||||
|
/// hardware and protocols which have it already have the older definition
|
||||||
|
/// baked in.
|
||||||
|
///
|
||||||
|
/// # Return
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn verify_strict(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Result<(), SignatureError>
|
||||||
|
{
|
||||||
|
self.public.verify_strict(message, signature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
/// Internal errors. Most application-level developers will likely not
|
/// Internal errors. Most application-level developers will likely not
|
||||||
/// need to pay any attention to these.
|
/// need to pay any attention to these.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
|
|
@ -33,6 +36,11 @@ pub(crate) enum InternalError {
|
||||||
},
|
},
|
||||||
/// The verification equation wasn't satisfied
|
/// The verification equation wasn't satisfied
|
||||||
VerifyError,
|
VerifyError,
|
||||||
|
/// Two arrays did not match in size, making the called signature
|
||||||
|
/// verification method impossible.
|
||||||
|
ArrayLengthError{ name_a: &'static str, length_a: usize,
|
||||||
|
name_b: &'static str, length_b: usize,
|
||||||
|
name_c: &'static str, length_c: usize, },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for InternalError {
|
impl Display for InternalError {
|
||||||
|
|
@ -46,11 +54,17 @@ impl Display for InternalError {
|
||||||
=> write!(f, "{} must be {} bytes in length", n, l),
|
=> write!(f, "{} must be {} bytes in length", n, l),
|
||||||
InternalError::VerifyError
|
InternalError::VerifyError
|
||||||
=> write!(f, "Verification equation was not satisfied"),
|
=> write!(f, "Verification equation was not satisfied"),
|
||||||
|
InternalError::ArrayLengthError{ name_a: na, length_a: la,
|
||||||
|
name_b: nb, length_b: lb,
|
||||||
|
name_c: nc, length_c: lc, }
|
||||||
|
=> write!(f, "Arrays must be the same length: {} has length {},
|
||||||
|
{} has length {}, {} has length {}.", na, la, nb, lb, nc, lc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::failure::Fail for InternalError {}
|
#[cfg(feature = "std")]
|
||||||
|
impl Error for InternalError { }
|
||||||
|
|
||||||
/// Errors which may occur while processing signatures and keypairs.
|
/// Errors which may occur while processing signatures and keypairs.
|
||||||
///
|
///
|
||||||
|
|
@ -75,8 +89,9 @@ impl Display for SignatureError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::failure::Fail for SignatureError {
|
#[cfg(feature = "std")]
|
||||||
fn cause(&self) -> Option<&dyn (::failure::Fail)> {
|
impl Error for SignatureError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
Some(&self.0)
|
Some(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
src/lib.rs
78
src/lib.rs
|
|
@ -19,18 +19,16 @@
|
||||||
//! the operating system's builtin PRNG:
|
//! the operating system's builtin PRNG:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! extern crate rand_core;
|
//! extern crate rand;
|
||||||
//! extern crate rand_os;
|
|
||||||
//! extern crate ed25519_dalek;
|
//! extern crate ed25519_dalek;
|
||||||
//!
|
//!
|
||||||
//! # #[cfg(feature = "std")]
|
//! # #[cfg(feature = "std")]
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! use rand_core::RngCore;
|
//! use rand::rngs::OsRng;
|
||||||
//! use rand_os::OsRng;
|
|
||||||
//! use ed25519_dalek::Keypair;
|
//! use ed25519_dalek::Keypair;
|
||||||
//! use ed25519_dalek::Signature;
|
//! use ed25519_dalek::Signature;
|
||||||
//!
|
//!
|
||||||
//! let mut csprng: OsRng = OsRng::new().unwrap();
|
//! let mut csprng = OsRng{};
|
||||||
//! let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # }
|
//! # }
|
||||||
//! #
|
//! #
|
||||||
|
|
@ -41,15 +39,13 @@
|
||||||
//! We can now use this `keypair` to sign a message:
|
//! We can now use this `keypair` to sign a message:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::Keypair;
|
//! # use ed25519_dalek::Keypair;
|
||||||
//! # use ed25519_dalek::Signature;
|
//! # use ed25519_dalek::Signature;
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! let signature: Signature = keypair.sign(message);
|
//! let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -60,15 +56,13 @@
|
||||||
//! that `message`:
|
//! that `message`:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::Keypair;
|
//! # use ed25519_dalek::Keypair;
|
||||||
//! # use ed25519_dalek::Signature;
|
//! # use ed25519_dalek::Signature;
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature: Signature = keypair.sign(message);
|
//! # let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -80,16 +74,14 @@
|
||||||
//! verify this signature:
|
//! verify this signature:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::Keypair;
|
//! # use ed25519_dalek::Keypair;
|
||||||
//! # use ed25519_dalek::Signature;
|
//! # use ed25519_dalek::Signature;
|
||||||
//! use ed25519_dalek::PublicKey;
|
//! use ed25519_dalek::PublicKey;
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature: Signature = keypair.sign(message);
|
//! # let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -108,15 +100,13 @@
|
||||||
//! verify your signatures!)
|
//! verify your signatures!)
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
||||||
//! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
//! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature: Signature = keypair.sign(message);
|
//! # let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -132,15 +122,13 @@
|
||||||
//! And similarly, decoded from bytes with `::from_bytes()`:
|
//! And similarly, decoded from bytes with `::from_bytes()`:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::{Keypair, Signature, PublicKey, SecretKey, SignatureError};
|
//! # use ed25519_dalek::{Keypair, Signature, PublicKey, SecretKey, SignatureError};
|
||||||
//! # use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
//! # use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
||||||
//! # fn do_test() -> Result<(SecretKey, PublicKey, Keypair, Signature), SignatureError> {
|
//! # fn do_test() -> Result<(SecretKey, PublicKey, Keypair, Signature), SignatureError> {
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair_orig: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair_orig: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature_orig: Signature = keypair_orig.sign(message);
|
//! # let signature_orig: Signature = keypair_orig.sign(message);
|
||||||
|
|
@ -175,8 +163,7 @@
|
||||||
//! For example, using [bincode](https://github.com/TyOverby/bincode):
|
//! For example, using [bincode](https://github.com/TyOverby/bincode):
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # #[cfg(feature = "serde")]
|
//! # #[cfg(feature = "serde")]
|
||||||
//! extern crate serde;
|
//! extern crate serde;
|
||||||
|
|
@ -185,11 +172,10 @@
|
||||||
//!
|
//!
|
||||||
//! # #[cfg(feature = "serde")]
|
//! # #[cfg(feature = "serde")]
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
||||||
//! use bincode::{serialize, Infinite};
|
//! use bincode::{serialize, Infinite};
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature: Signature = keypair.sign(message);
|
//! # let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -207,8 +193,7 @@
|
||||||
//! recipient may deserialise them and verify:
|
//! recipient may deserialise them and verify:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # extern crate rand_core;
|
//! # extern crate rand;
|
||||||
//! # extern crate rand_os;
|
|
||||||
//! # extern crate ed25519_dalek;
|
//! # extern crate ed25519_dalek;
|
||||||
//! # #[cfg(feature = "serde")]
|
//! # #[cfg(feature = "serde")]
|
||||||
//! # extern crate serde;
|
//! # extern crate serde;
|
||||||
|
|
@ -217,13 +202,12 @@
|
||||||
//! #
|
//! #
|
||||||
//! # #[cfg(feature = "serde")]
|
//! # #[cfg(feature = "serde")]
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # use rand_core::RngCore;
|
//! # use rand::rngs::OsRng;
|
||||||
//! # use rand_os::OsRng;
|
|
||||||
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
//! # use ed25519_dalek::{Keypair, Signature, PublicKey};
|
||||||
//! # use bincode::{serialize, Infinite};
|
//! # use bincode::{serialize, Infinite};
|
||||||
//! use bincode::{deserialize};
|
//! use bincode::{deserialize};
|
||||||
//!
|
//!
|
||||||
//! # let mut csprng = OsRng::new().unwrap();
|
//! # let mut csprng = OsRng{};
|
||||||
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
//! # let keypair: Keypair = Keypair::generate(&mut csprng);
|
||||||
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
//! let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||||
//! # let signature: Signature = keypair.sign(message);
|
//! # let signature: Signature = keypair.sign(message);
|
||||||
|
|
@ -247,8 +231,6 @@
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![warn(future_incompatible)]
|
#![warn(future_incompatible)]
|
||||||
#![warn(rust_2018_compatibility)]
|
|
||||||
#![warn(rust_2018_idioms)]
|
|
||||||
#![deny(missing_docs)] // refuse to compile if documentation is missing
|
#![deny(missing_docs)] // refuse to compile if documentation is missing
|
||||||
|
|
||||||
#[cfg(any(feature = "std", test))]
|
#[cfg(any(feature = "std", test))]
|
||||||
|
|
@ -259,16 +241,16 @@ extern crate std;
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate clear_on_drop;
|
extern crate clear_on_drop;
|
||||||
extern crate curve25519_dalek;
|
extern crate curve25519_dalek;
|
||||||
extern crate failure;
|
#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))]
|
||||||
#[cfg(all(feature = "batch", any(feature = "std", feature = "alloc", test)))]
|
extern crate merlin;
|
||||||
|
#[cfg(any(feature = "batch", feature = "std", feature = "alloc", test))]
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
#[cfg(any(feature = "std", test))]
|
|
||||||
extern crate rand_os;
|
|
||||||
extern crate rand_core;
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate sha2;
|
extern crate sha2;
|
||||||
|
|
||||||
|
#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))]
|
||||||
|
mod batch;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod ed25519;
|
mod ed25519;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
@ -278,3 +260,5 @@ mod signature;
|
||||||
|
|
||||||
// Export everything public in ed25519.
|
// Export everything public in ed25519.
|
||||||
pub use crate::ed25519::*;
|
pub use crate::ed25519::*;
|
||||||
|
#[cfg(all(any(feature = "batch", feature = "batch_deterministic"), any(feature = "std", feature = "alloc")))]
|
||||||
|
pub use crate::batch::*;
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,105 @@ impl PublicKey {
|
||||||
Err(SignatureError(InternalError::VerifyError))
|
Err(SignatureError(InternalError::VerifyError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strictly verify a signature on a message with this keypair's public key.
|
||||||
|
///
|
||||||
|
/// # On The (Multiple) Sources of Malleability in Ed25519 Signatures
|
||||||
|
///
|
||||||
|
/// This version of verification is technically non-RFC8032 compliant. The
|
||||||
|
/// following explains why.
|
||||||
|
///
|
||||||
|
/// 1. Scalar Malleability
|
||||||
|
///
|
||||||
|
/// The authors of the RFC explicitly stated that verification of an ed25519
|
||||||
|
/// signature must fail if the scalar `s` is not properly reduced mod \ell:
|
||||||
|
///
|
||||||
|
/// > To verify a signature on a message M using public key A, with F
|
||||||
|
/// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or
|
||||||
|
/// > Ed25519ph is being used, C being the context, first split the
|
||||||
|
/// > signature into two 32-octet halves. Decode the first half as a
|
||||||
|
/// > point R, and the second half as an integer S, in the range
|
||||||
|
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||||
|
/// > decodings fail (including S being out of range), the signature is
|
||||||
|
/// > invalid.)
|
||||||
|
///
|
||||||
|
/// All `verify_*()` functions within ed25519-dalek perform this check.
|
||||||
|
///
|
||||||
|
/// 2. Point malleability
|
||||||
|
///
|
||||||
|
/// The authors of the RFC added in a malleability check to step #3 in
|
||||||
|
/// §5.1.7, for small torsion components in the `R` value of the signature,
|
||||||
|
/// *which is not strictly required*, as they state:
|
||||||
|
///
|
||||||
|
/// > Check the group equation \[8\]\[S\]B = \[8\]R + \[8\]\[k\]A'. It's
|
||||||
|
/// > sufficient, but not required, to instead check \[S\]B = R + \[k\]A'.
|
||||||
|
///
|
||||||
|
/// # History of Malleability Checks
|
||||||
|
///
|
||||||
|
/// As originally defined (cf. the "Malleability" section in the README of
|
||||||
|
/// this repo), ed25519 signatures didn't consider *any* form of
|
||||||
|
/// malleability to be an issue. Later the scalar malleability was
|
||||||
|
/// considered important. Still later, particularly with interests in
|
||||||
|
/// cryptocurrency design and in unique identities (e.g. for Signal users,
|
||||||
|
/// Tor onion services, etc.), the group element malleability became a
|
||||||
|
/// concern.
|
||||||
|
///
|
||||||
|
/// However, libraries had already been created to conform to the original
|
||||||
|
/// definition. One well-used library in particular even implemented the
|
||||||
|
/// group element malleability check, *but only for batch verification*!
|
||||||
|
/// Which meant that even using the same library, a single signature could
|
||||||
|
/// verify fine individually, but suddenly, when verifying it with a bunch
|
||||||
|
/// of other signatures, the whole batch would fail!
|
||||||
|
///
|
||||||
|
/// # "Strict" Verification
|
||||||
|
///
|
||||||
|
/// This method performs *both* of the above signature malleability checks.
|
||||||
|
///
|
||||||
|
/// It must be done as a separate method because one doesn't simply get to
|
||||||
|
/// change the definition of a cryptographic primitive ten years
|
||||||
|
/// after-the-fact with zero consideration for backwards compatibility in
|
||||||
|
/// hardware and protocols which have it already have the older definition
|
||||||
|
/// baked in.
|
||||||
|
///
|
||||||
|
/// # Return
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn verify_strict(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Result<(), SignatureError>
|
||||||
|
{
|
||||||
|
let mut h: Sha512 = Sha512::new();
|
||||||
|
let R: EdwardsPoint;
|
||||||
|
let k: Scalar;
|
||||||
|
let minus_A: EdwardsPoint = -self.1;
|
||||||
|
let signature_R: EdwardsPoint;
|
||||||
|
|
||||||
|
match signature.R.decompress() {
|
||||||
|
None => return Err(SignatureError(InternalError::VerifyError)),
|
||||||
|
Some(x) => signature_R = x,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logical OR is fine here as we're not trying to be constant time.
|
||||||
|
if signature_R.is_small_order() || self.1.is_small_order() {
|
||||||
|
return Err(SignatureError(InternalError::VerifyError));
|
||||||
|
}
|
||||||
|
|
||||||
|
h.input(signature.R.as_bytes());
|
||||||
|
h.input(self.as_bytes());
|
||||||
|
h.input(&message);
|
||||||
|
|
||||||
|
k = Scalar::from_hash(h);
|
||||||
|
R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s);
|
||||||
|
|
||||||
|
if R == signature_R {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(SignatureError(InternalError::VerifyError))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use curve25519_dalek::digest::Digest;
|
||||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||||
use curve25519_dalek::scalar::Scalar;
|
use curve25519_dalek::scalar::Scalar;
|
||||||
|
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
|
|
||||||
|
|
@ -125,18 +125,18 @@ impl SecretKey {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// extern crate rand_os;
|
/// extern crate rand;
|
||||||
/// extern crate ed25519_dalek;
|
/// extern crate ed25519_dalek;
|
||||||
///
|
///
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// #
|
/// #
|
||||||
/// use rand_os::OsRng;
|
/// use rand::rngs::OsRng;
|
||||||
/// use ed25519_dalek::PublicKey;
|
/// use ed25519_dalek::PublicKey;
|
||||||
/// use ed25519_dalek::SecretKey;
|
/// use ed25519_dalek::SecretKey;
|
||||||
/// use ed25519_dalek::Signature;
|
/// use ed25519_dalek::Signature;
|
||||||
///
|
///
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -147,17 +147,17 @@ impl SecretKey {
|
||||||
/// Afterwards, you can generate the corresponding public:
|
/// Afterwards, you can generate the corresponding public:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate rand_os;
|
/// # extern crate rand;
|
||||||
/// # extern crate ed25519_dalek;
|
/// # extern crate ed25519_dalek;
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// #
|
/// #
|
||||||
/// # use rand_os::OsRng;
|
/// # use rand::rngs::OsRng;
|
||||||
/// # use ed25519_dalek::PublicKey;
|
/// # use ed25519_dalek::PublicKey;
|
||||||
/// # use ed25519_dalek::SecretKey;
|
/// # use ed25519_dalek::SecretKey;
|
||||||
/// # use ed25519_dalek::Signature;
|
/// # use ed25519_dalek::Signature;
|
||||||
/// #
|
/// #
|
||||||
/// # let mut csprng = OsRng::new().unwrap();
|
/// # let mut csprng = OsRng{};
|
||||||
/// # let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
/// # let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
///
|
///
|
||||||
/// let public_key: PublicKey = (&secret_key).into();
|
/// let public_key: PublicKey = (&secret_key).into();
|
||||||
|
|
@ -270,18 +270,16 @@ impl<'a> From<&'a SecretKey> for ExpandedSecretKey {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate rand_core;
|
/// # extern crate rand;
|
||||||
/// # extern crate rand_os;
|
|
||||||
/// # extern crate sha2;
|
/// # extern crate sha2;
|
||||||
/// # extern crate ed25519_dalek;
|
/// # extern crate ed25519_dalek;
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// #
|
/// #
|
||||||
/// use rand_core::RngCore;
|
/// use rand::rngs::OsRng;
|
||||||
/// use rand_os::OsRng;
|
|
||||||
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
||||||
///
|
///
|
||||||
/// let mut csprng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
||||||
/// # }
|
/// # }
|
||||||
|
|
@ -318,17 +316,17 @@ impl ExpandedSecretKey {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate rand_os;
|
/// # extern crate rand;
|
||||||
/// # extern crate sha2;
|
/// # extern crate sha2;
|
||||||
/// # extern crate ed25519_dalek;
|
/// # extern crate ed25519_dalek;
|
||||||
/// #
|
/// #
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// #
|
/// #
|
||||||
/// use rand_os::OsRng;
|
/// use rand::rngs::OsRng;
|
||||||
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
||||||
///
|
///
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
||||||
/// let expanded_secret_key_bytes: [u8; 64] = expanded_secret_key.to_bytes();
|
/// let expanded_secret_key_bytes: [u8; 64] = expanded_secret_key.to_bytes();
|
||||||
|
|
@ -358,7 +356,7 @@ impl ExpandedSecretKey {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate rand_os;
|
/// # extern crate rand;
|
||||||
/// # extern crate sha2;
|
/// # extern crate sha2;
|
||||||
/// # extern crate ed25519_dalek;
|
/// # extern crate ed25519_dalek;
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -367,11 +365,11 @@ impl ExpandedSecretKey {
|
||||||
/// # #[cfg(feature = "std")]
|
/// # #[cfg(feature = "std")]
|
||||||
/// # fn do_test() -> Result<ExpandedSecretKey, SignatureError> {
|
/// # fn do_test() -> Result<ExpandedSecretKey, SignatureError> {
|
||||||
/// #
|
/// #
|
||||||
/// use rand_os::OsRng;
|
/// use rand::rngs::OsRng;
|
||||||
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
/// use ed25519_dalek::{SecretKey, ExpandedSecretKey};
|
||||||
/// use ed25519_dalek::SignatureError;
|
/// use ed25519_dalek::SignatureError;
|
||||||
///
|
///
|
||||||
/// let mut csprng: OsRng = OsRng::new().unwrap();
|
/// let mut csprng = OsRng{};
|
||||||
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
/// let secret_key: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
/// let expanded_secret_key: ExpandedSecretKey = ExpandedSecretKey::from(&secret_key);
|
||||||
/// let bytes: [u8; 64] = expanded_secret_key.to_bytes();
|
/// let bytes: [u8; 64] = expanded_secret_key.to_bytes();
|
||||||
|
|
@ -454,11 +452,11 @@ impl ExpandedSecretKey {
|
||||||
///
|
///
|
||||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn sign_prehashed<D>(
|
pub fn sign_prehashed<'a, D>(
|
||||||
&self,
|
&self,
|
||||||
prehashed_message: D,
|
prehashed_message: D,
|
||||||
public_key: &PublicKey,
|
public_key: &PublicKey,
|
||||||
context: Option<&'static [u8]>,
|
context: Option<&'a [u8]>,
|
||||||
) -> Signature
|
) -> Signature
|
||||||
where
|
where
|
||||||
D: Digest<OutputSize = U64>,
|
D: Digest<OutputSize = U64>,
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,43 @@ impl Debug for Signature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy_compatibility")]
|
||||||
|
#[inline(always)]
|
||||||
|
fn check_scalar(bytes: [u8; 32]) -> Result<Scalar, SignatureError> {
|
||||||
|
// The highest 3 bits must not be set. No other checking for the
|
||||||
|
// remaining 2^253 - 2^252 + 27742317777372353535851937790883648493
|
||||||
|
// potential non-reduced scalars is performed.
|
||||||
|
//
|
||||||
|
// This is compatible with ed25519-donna and libsodium when
|
||||||
|
// -DED25519_COMPAT is NOT specified.
|
||||||
|
if bytes[31] & 224 != 0 {
|
||||||
|
return Err(SignatureError(InternalError::ScalarFormatError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Scalar::from_bits(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "legacy_compatibility"))]
|
||||||
|
#[inline(always)]
|
||||||
|
fn check_scalar(bytes: [u8; 32]) -> Result<Scalar, SignatureError> {
|
||||||
|
// Since this is only used in signature deserialisation (i.e. upon
|
||||||
|
// verification), we can do a "succeed fast" trick by checking that the most
|
||||||
|
// significant 4 bits are unset. If they are unset, we can succeed fast
|
||||||
|
// because we are guaranteed that the scalar is fully reduced. However, if
|
||||||
|
// the 4th most significant bit is set, we must do the full reduction check,
|
||||||
|
// as the order of the basepoint is roughly a 2^(252.5) bit number.
|
||||||
|
//
|
||||||
|
// This succeed-fast trick should succeed for roughly half of all scalars.
|
||||||
|
if bytes[31] & 240 == 0 {
|
||||||
|
return Ok(Scalar::from_bits(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
match Scalar::from_canonical_bytes(bytes) {
|
||||||
|
None => return Err(SignatureError(InternalError::ScalarFormatError)),
|
||||||
|
Some(x) => return Ok(x),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
/// Convert this `Signature` to a byte array.
|
/// Convert this `Signature` to a byte array.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -83,6 +120,55 @@ impl Signature {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a `Signature` from a slice of bytes.
|
/// Construct a `Signature` from a slice of bytes.
|
||||||
|
///
|
||||||
|
/// # Scalar Malleability Checking
|
||||||
|
///
|
||||||
|
/// As originally specified in the ed25519 paper (cf. the "Malleability"
|
||||||
|
/// section of the README in this repo), no checks whatsoever were performed
|
||||||
|
/// for signature malleability.
|
||||||
|
///
|
||||||
|
/// Later, a semi-functional, hacky check was added to most libraries to
|
||||||
|
/// "ensure" that the scalar portion, `s`, of the signature was reduced `mod
|
||||||
|
/// \ell`, the order of the basepoint:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// if signature.s[31] & 224 != 0 {
|
||||||
|
/// return Err();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This bit-twiddling ensures that the most significant three bits of the
|
||||||
|
/// scalar are not set:
|
||||||
|
///
|
||||||
|
/// ```python,ignore
|
||||||
|
/// >>> 0b00010000 & 224
|
||||||
|
/// 0
|
||||||
|
/// >>> 0b00100000 & 224
|
||||||
|
/// 32
|
||||||
|
/// >>> 0b01000000 & 224
|
||||||
|
/// 64
|
||||||
|
/// >>> 0b10000000 & 224
|
||||||
|
/// 128
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// However, this check is hacky and insufficient to check that the scalar is
|
||||||
|
/// fully reduced `mod \ell = 2^252 + 27742317777372353535851937790883648493` as
|
||||||
|
/// it leaves us with a guanteed bound of 253 bits. This means that there are
|
||||||
|
/// `2^253 - 2^252 + 2774231777737235353585193779088364849311` remaining scalars
|
||||||
|
/// which could cause malleabilllity.
|
||||||
|
///
|
||||||
|
/// RFC8032 [states](https://tools.ietf.org/html/rfc8032#section-5.1.7):
|
||||||
|
///
|
||||||
|
/// > To verify a signature on a message M using public key A, [...]
|
||||||
|
/// > first split the signature into two 32-octet halves. Decode the first
|
||||||
|
/// > half as a point R, and the second half as an integer S, in the range
|
||||||
|
/// > 0 <= s < L. Decode the public key A as point A'. If any of the
|
||||||
|
/// > decodings fail (including S being out of range), the signature is
|
||||||
|
/// > invalid.
|
||||||
|
///
|
||||||
|
/// However, by the time this was standardised, most libraries in use were
|
||||||
|
/// only checking the most significant three bits. (See also the
|
||||||
|
/// documentation for `PublicKey.verify_strict`.)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Signature, SignatureError> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Signature, SignatureError> {
|
||||||
if bytes.len() != SIGNATURE_LENGTH {
|
if bytes.len() != SIGNATURE_LENGTH {
|
||||||
|
|
@ -97,13 +183,16 @@ impl Signature {
|
||||||
lower.copy_from_slice(&bytes[..32]);
|
lower.copy_from_slice(&bytes[..32]);
|
||||||
upper.copy_from_slice(&bytes[32..]);
|
upper.copy_from_slice(&bytes[32..]);
|
||||||
|
|
||||||
if upper[31] & 224 != 0 {
|
let s: Scalar;
|
||||||
return Err(SignatureError(InternalError::ScalarFormatError));
|
|
||||||
|
match check_scalar(upper) {
|
||||||
|
Ok(x) => s = x,
|
||||||
|
Err(x) => return Err(x),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Signature {
|
Ok(Signature {
|
||||||
R: CompressedEdwardsY(lower),
|
R: CompressedEdwardsY(lower),
|
||||||
s: Scalar::from_bits(upper),
|
s: s,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,13 @@
|
||||||
extern crate bincode;
|
extern crate bincode;
|
||||||
extern crate ed25519_dalek;
|
extern crate ed25519_dalek;
|
||||||
extern crate hex;
|
extern crate hex;
|
||||||
extern crate rand_os;
|
|
||||||
extern crate sha2;
|
extern crate sha2;
|
||||||
|
extern crate rand;
|
||||||
|
|
||||||
use ed25519_dalek::*;
|
use ed25519_dalek::*;
|
||||||
|
|
||||||
use hex::FromHex;
|
use hex::FromHex;
|
||||||
|
|
||||||
use rand_os::OsRng;
|
|
||||||
|
|
||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -113,6 +111,7 @@ mod vectors {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod integrations {
|
mod integrations {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sign_verify() { // TestSignVerify
|
fn sign_verify() { // TestSignVerify
|
||||||
|
|
@ -123,7 +122,7 @@ mod integrations {
|
||||||
let good: &[u8] = "test message".as_bytes();
|
let good: &[u8] = "test message".as_bytes();
|
||||||
let bad: &[u8] = "wrong message".as_bytes();
|
let bad: &[u8] = "wrong message".as_bytes();
|
||||||
|
|
||||||
let mut csprng: OsRng = OsRng::new().unwrap();
|
let mut csprng = OsRng{};
|
||||||
|
|
||||||
keypair = Keypair::generate(&mut csprng);
|
keypair = Keypair::generate(&mut csprng);
|
||||||
good_sig = keypair.sign(&good);
|
good_sig = keypair.sign(&good);
|
||||||
|
|
@ -146,7 +145,7 @@ mod integrations {
|
||||||
let good: &[u8] = b"test message";
|
let good: &[u8] = b"test message";
|
||||||
let bad: &[u8] = b"wrong message";
|
let bad: &[u8] = b"wrong message";
|
||||||
|
|
||||||
let mut csprng: OsRng = OsRng::new().unwrap();
|
let mut csprng = OsRng{};
|
||||||
|
|
||||||
// ugh… there's no `impl Copy for Sha512`… i hope we can all agree these are the same hashes
|
// ugh… there's no `impl Copy for Sha512`… i hope we can all agree these are the same hashes
|
||||||
let mut prehashed_good1: Sha512 = Sha512::default();
|
let mut prehashed_good1: Sha512 = Sha512::default();
|
||||||
|
|
@ -186,7 +185,7 @@ mod integrations {
|
||||||
b"Fuck dumbin' it down, spit ice, skip jewellery: Molotov cocktails on me like accessories.",
|
b"Fuck dumbin' it down, spit ice, skip jewellery: Molotov cocktails on me like accessories.",
|
||||||
b"Hey, I never cared about your bucks, so if I run up with a mask on, probably got a gas can too.",
|
b"Hey, I never cared about your bucks, so if I run up with a mask on, probably got a gas can too.",
|
||||||
b"And I'm not here to fill 'er up. Nope, we came to riot, here to incite, we don't want any of your stuff.", ];
|
b"And I'm not here to fill 'er up. Nope, we came to riot, here to incite, we don't want any of your stuff.", ];
|
||||||
let mut csprng: OsRng = OsRng::new().unwrap();
|
let mut csprng = OsRng{};
|
||||||
let mut keypairs: Vec<Keypair> = Vec::new();
|
let mut keypairs: Vec<Keypair> = Vec::new();
|
||||||
let mut signatures: Vec<Signature> = Vec::new();
|
let mut signatures: Vec<Signature> = Vec::new();
|
||||||
|
|
||||||
|
|
@ -204,7 +203,7 @@ mod integrations {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pubkey_from_secret_and_expanded_secret() {
|
fn pubkey_from_secret_and_expanded_secret() {
|
||||||
let mut csprng = OsRng::new().unwrap();
|
let mut csprng = OsRng{};
|
||||||
let secret: SecretKey = SecretKey::generate(&mut csprng);
|
let secret: SecretKey = SecretKey::generate(&mut csprng);
|
||||||
let expanded_secret: ExpandedSecretKey = (&secret).into();
|
let expanded_secret: ExpandedSecretKey = (&secret).into();
|
||||||
let public_from_secret: PublicKey = (&secret).into(); // XXX eww
|
let public_from_secret: PublicKey = (&secret).into(); // XXX eww
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue