additional

This commit is contained in:
Cassandra Heart 2025-01-08 18:31:02 -06:00
parent 5ccd96e47b
commit c76c07874f
No known key found for this signature in database
GPG Key ID: 6352152859385958
22 changed files with 2328 additions and 28 deletions

16
Cargo.lock generated
View File

@ -171,6 +171,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.22.1"
@ -355,7 +361,7 @@ name = "channel"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"base64 0.22.1",
"criterion 0.4.0",
"ed448-goldilocks-plus",
"hex 0.4.3",
@ -375,7 +381,7 @@ name = "channelwasm"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"base64 0.22.1",
"channel",
"ed448-goldilocks-plus",
"ed448-rust",
@ -724,10 +730,10 @@ dependencies = [
[[package]]
name = "ed448-rust"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428b9b8138c483798ceb6a21004de0f24e3a2311195f13829ce1d059037d703"
version = "0.1.2"
dependencies = [
"base64 0.13.1",
"hex 0.4.3",
"lazy_static",
"num-bigint",
"num-integer",

View File

@ -18,6 +18,7 @@ members = [
"crates/channel-wasm",
"crates/classgroup",
"crates/bls48581",
"crates/ed448-rust",
"crates/rpm",
]

View File

@ -15,7 +15,7 @@ serde = "1.0.208"
serde_json = "1.0.117"
base64 = "0.22.1"
ed448-goldilocks-plus = "0.11.2"
ed448-rust = "0.1.1"
ed448-rust = { path = "../ed448-rust", version = "0.1.2" }
rand = "0.8.5"
sha2 = "0.10.8"
hkdf = "0.12.4"

View File

@ -81,6 +81,22 @@ pub struct SealedInboxMessageEncryptRequest {
pub plaintext: Vec<u8>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ResizeRequest {
pub ratchet_state: String,
pub other: String,
pub id: usize,
pub total: usize,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct TripleRatchetStateAndPoint {
pub ratchet_state: String,
pub point: String,
pub index: usize,
}
fn encrypt(plaintext: &[u8], key: &[u8])
-> Result<MessageCiphertext, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
@ -413,7 +429,6 @@ pub fn js_verify_ed448(public_key: &str, message: &str, signature: &str) -> Stri
}
let pub_bytes: [u8; 57] = key.try_into().unwrap();
let pub_key = ed448_rust::PublicKey::from(pub_bytes);
let signature = pub_key.verify(&maybe_message.unwrap(), &maybe_signature.unwrap(), None);
@ -572,3 +587,48 @@ pub fn js_triple_ratchet_decrypt(params: &str) -> String {
}
}
#[wasm_bindgen]
pub fn js_triple_ratchet_resize(params: &str) -> String {
let json: Result<ResizeRequest, serde_json::Error> = serde_json::from_str(params);
match json {
Ok(request) => {
return serde_json::to_string(&triple_ratchet_resize(request.ratchet_state, request.other, request.id, request.total)).unwrap_or_else(|e| e.to_string());
}
Err(e) => {
return e.to_string();
}
}
}
#[wasm_bindgen]
pub fn js_verify_point(params: &str) -> String {
let json: Result<TripleRatchetStateAndPoint, serde_json::Error> = serde_json::from_str(params);
match json {
Ok(request) => {
let verify = triple_ratchet_verify_point(request.ratchet_state, request.point, request.index);
return match verify {
Ok(result) => serde_json::to_string(&result).unwrap_or_else(|e| e.to_string()),
Err(e) => serde_json::to_string(&e.to_string()).unwrap_or_else(|e| e.to_string())
}
}
Err(e) => {
return e.to_string();
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use ed448_goldilocks_plus::{Scalar, elliptic_curve::Group, EdwardsPoint};
#[test]
fn test_verify() {
let priv_key = ed448_rust::PrivateKey::new(&mut rand::thread_rng());
let pub_key = ed448_rust::PublicKey::from(&priv_key);
let sig = js_sign_ed448(&BASE64_STANDARD.encode(priv_key.as_bytes()).to_string(), "AQAB");
assert_eq!(js_verify_ed448(&BASE64_STANDARD.encode(pub_key.as_byte()).to_string(), "AQAB", &serde_json::from_str::<String>(&sig.to_string()).unwrap()), "true")
}
}

View File

@ -1,6 +1,6 @@
use base64::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, io::Read};
use std::{collections::HashMap, error::Error, io::Read};
use ed448_goldilocks_plus::{elliptic_curve::group::GroupEncoding, CompressedEdwardsY, EdwardsPoint, Scalar};
use protocols::{doubleratchet::{DoubleRatchetParticipant, P2PChannelEnvelope}, tripleratchet::{PeerInfo, TripleRatchetParticipant}, x3dh};
@ -662,6 +662,44 @@ pub fn triple_ratchet_decrypt(ratchet_state_and_envelope: TripleRatchetStateAndE
};
}
pub fn triple_ratchet_resize(ratchet_state: String, other: String, id: usize, total: usize) -> Vec<Vec<u8>> {
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return vec![vec![1]];
}
let other_bytes = hex::decode(other);
if other_bytes.is_err() {
return vec![other_bytes.unwrap_err().to_string().as_bytes().to_vec()];
}
let result = tr.unwrap().ratchet_resize(other_bytes.unwrap(), id, total);
if result.is_err() {
return vec![result.unwrap_err().to_string().as_bytes().to_vec()];
}
return result.unwrap();
}
pub fn triple_ratchet_verify_point(ratchet_state: String, point: String, id: usize) -> Result<bool, Box<dyn Error>> {
let tr = TripleRatchetParticipant::from_json(&ratchet_state);
if tr.is_err() {
return Err(tr.unwrap_err());
}
let point_bytes = hex::decode(point);
if point_bytes.is_err() {
return Err(Box::new(point_bytes.unwrap_err()));
}
let result = tr.unwrap().point_verify(point_bytes.unwrap(), id);
if result.is_err() {
return Err(result.unwrap_err());
}
return Ok(result.unwrap());
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;

View File

@ -16,7 +16,7 @@ pub enum FeldmanError {
CryptoError(String),
}
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, PartialEq, Debug)]
enum FeldmanRound {
Uninitialized,
Initialized,
@ -25,6 +25,7 @@ enum FeldmanRound {
Reconstructed,
}
#[derive(Debug)]
pub struct Feldman {
threshold: usize,
total: usize,
@ -226,25 +227,13 @@ impl Feldman {
return Err(FeldmanError::WrongRound);
}
let mut coeffs = vec![self.secret];
for _ in 1..self.threshold {
coeffs.push(Scalar::random(rng));
}
let samples = Feldman::construct_polynomial_samples(rng, self.secret, self.threshold, self.total);
for i in 1..=self.total {
let mut result = coeffs[0];
let x = Scalar::from(i as u32);
for j in 1..self.threshold {
let term = coeffs[j] * Scalar::from(i.pow(j as u32) as u32);
result += term;
}
if i == self.id {
self.scalar = Some(result);
self.scalar = Some(samples[i-1]);
} else {
self.frags_for_counterparties.insert(i, result.to_bytes().to_vec());
self.frags_for_counterparties.insert(i, samples[i-1].to_bytes().to_vec());
}
}
@ -252,6 +241,29 @@ impl Feldman {
Ok(())
}
fn construct_polynomial_samples<R: RngCore + CryptoRng>(rng: &mut R, secret: Scalar, threshold: usize, total: usize) -> Vec<Scalar> {
let mut coeffs = vec![secret];
for _ in 1..threshold {
coeffs.push(Scalar::random(rng));
}
let mut samples = Vec::<Scalar>::new();
for i in 1..=total {
let mut result = coeffs[0];
let x = Scalar::from(i as u32);
for j in 1..threshold {
let term = coeffs[j] * Scalar::from(i.pow(j as u32) as u32);
result += term;
}
samples.push(result);
}
return samples;
}
pub fn scalar(&self) -> Option<&Scalar> {
self.scalar.as_ref()
}
@ -481,5 +493,51 @@ impl Feldman {
pub fn public_key_bytes(&self) -> Vec<u8> {
self.public_key.to_bytes().to_vec()
}
}
pub fn redistribute<R: RngCore + CryptoRng>(rng: &mut R, shares: Vec<Vec<u8>>, ids: &[usize], threshold: usize, total: usize) -> Result<Vec<Vec<u8>>, FeldmanError> {
if shares.len() != ids.len() {
return Err(FeldmanError::InvalidData("mismatch of shares and ids len".to_string()));
}
let mut points = HashMap::<usize, Scalar>::new();
for (i, share) in shares.iter().enumerate() {
let point = Scalar::from_bytes(&(*share).clone().try_into().unwrap());
if point.is_zero().into() {
return Err(FeldmanError::InvalidData(format!("invalid pubkey for {}", ids[i]).to_string()));
}
points.insert(ids[i], point);
}
let mut reconstructed_sum = Scalar::ZERO;
for j in ids {
let mut num = Scalar::ONE;
let mut den = Scalar::ONE;
for k in ids {
if j != k {
let j_scalar = Scalar::from(*j as u32);
let k_scalar = Scalar::from(*k as u32);
num *= k_scalar;
den *= k_scalar - j_scalar;
}
}
let den_inv = den.invert();
let reconstructed_fragment = points[&j] * (num * den_inv);
reconstructed_sum += reconstructed_fragment;
}
return Ok(Feldman::construct_polynomial_samples(rng, reconstructed_sum, threshold, total).iter().map(|s| s.to_bytes().to_vec()).collect())
}
pub fn get_scalar(&self) -> Scalar {
return self.scalar.unwrap();
}
pub fn get_id(&self) -> usize {
return self.id;
}
}

View File

@ -48,6 +48,7 @@ pub struct PeerInfo {
pub(crate) signed_pre_public_key: Vec<u8>,
}
#[derive(Debug)]
pub struct TripleRatchetParticipant {
peer_key: Scalar,
sending_ephemeral_private_key: Scalar,
@ -694,6 +695,63 @@ impl TripleRatchetParticipant {
Ok((plaintext, should_dkg_ratchet))
}
pub fn ratchet_resize(&mut self, other: Vec<u8>, id: usize, total: usize) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
if !self.async_dkg_ratchet {
return Err(Box::new(TripleRatchetError::CryptoError("cannot use non-async triple ratchet for resize".to_owned())));
}
if self.threshold != 2 {
return Err(Box::new(TripleRatchetError::CryptoError("cannot use larger threshold size than two for resize".to_owned())));
}
let rescaled = Feldman::redistribute(&mut OsRng, vec![self.dkg_ratchet.get_scalar().to_bytes().to_vec(), other], &vec![self.dkg_ratchet.get_id(), id], 2, total);
if rescaled.is_err() {
return Err(Box::new(rescaled.unwrap_err()));
}
return Ok(rescaled.unwrap());
}
pub fn point_verify(&mut self, point: Vec<u8>, id: usize) -> Result<bool, Box<dyn std::error::Error>> {
if !self.async_dkg_ratchet {
return Err(Box::new(TripleRatchetError::CryptoError("cannot use non-async triple ratchet for point verify".to_owned())));
}
if self.threshold != 2 {
return Err(Box::new(TripleRatchetError::CryptoError("cannot use larger threshold size than two for point verify".to_owned())));
}
if id == 0 {
return Err(Box::new(TripleRatchetError::CryptoError("invalid id".to_owned())))
}
let ours = (self.dkg_ratchet.get_scalar() * EdwardsPoint::generator()).compress();
let ours_bytes = ours.as_bytes();
let mut shares = Vec::<&[u8]>::new();
let mut ids = Vec::<usize>::new();
if self.dkg_ratchet.get_id() > id {
ids.push(id);
shares.push(&point);
ids.push(self.dkg_ratchet.get_id());
shares.push(ours_bytes);
} else {
ids.push(self.dkg_ratchet.get_id());
shares.push(ours_bytes);
ids.push(id);
shares.push(&point);
}
let result = self.dkg_ratchet.combine_mul_share(shares, ids.as_slice());
match result {
Ok(pubkey) => Ok(pubkey == self.dkg_ratchet.public_key_bytes()),
Err(e) => Err(Box::new(e))
}
}
fn ratchet_sender_ephemeral_keys(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let receiving_group_key = self.receiving_group_key.as_ref().ok_or_else(|| TripleRatchetError::CryptoError("Receiving group key not set".into()))?;
self.sending_ephemeral_private_key = Scalar::random(&mut OsRng);
@ -823,16 +881,16 @@ impl TripleRatchetParticipant {
let current_header_key = rkck[32..64].to_vec();
match self.decrypt(ciphertext, &current_header_key, None) {
Ok(header) => Ok((header, true, true)),
Err(e) => Err(Box::new(e)),
Err(e) => Err(Box::new(TripleRatchetError::CryptoError(format!("header: {}", e.to_string())))),
}
} else {
match self.decrypt(ciphertext, &self.next_header_key, None) {
Ok(header) => Ok((header, true, false)),
Err(e) => Err(Box::new(e)),
Err(e) => Err(Box::new(TripleRatchetError::CryptoError(format!("header: {}", e.to_string())))),
}
}
},
Err(e) => Err(Box::new(e)),
Err(e) => Err(Box::new(TripleRatchetError::CryptoError(format!("header: {}", e.to_string())))),
}
}

View File

@ -0,0 +1,10 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4

View File

@ -0,0 +1,28 @@
name: Security audit
on:
schedule:
# Launch everyday, at midnight
- cron: '0 0 * * *'
push:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
pull_request:
# Allow to manually trigger the check
workflow_dispatch:
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
# Checkout Git repository
- name: Checkout sources
uses: actions/checkout@v2
# Security scan all dependencies, create a Pull Request if anyone found
- name: Security audit used dependencies
uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,171 @@
name: Ci
# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches:
- main
- dev
pull_request:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
check:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout sources
uses: actions/checkout@v2
# Install stable toolchain
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
# Runs the compilation checks
- name: Run cargo check with defaults feature
uses: actions-rs/cargo@v1
with:
command: check
# - name: Run cargo check with all features
# uses: actions-rs/cargo@v1
# with:
# command: check
# args: --all-features
clippy_and_fmt:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout sources
uses: actions/checkout@v2
# Install nightly toolchain
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: default
toolchain: nightly
override: true
components: clippy, rustfmt
# Checks Rust code formatting
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
# Checks Rust code with Clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
continue-on-error: true # note: the compiler unexpectedly panicked. this is a bug.
with:
command: clippy
args: --all-features -- -D warnings
test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout sources
uses: actions/checkout@v2
# Install nightly toolchain
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: llvm-tools-preview
# Build the binary
- name: Build binary
uses: actions-rs/cargo@v1
with:
command: build
args: --all-features
env:
RUSTFLAGS: "-Zinstrument-coverage"
# Runs the units tests in Debug mode
- name: Execute tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
env:
RUSTFLAGS: "-Zinstrument-coverage"
LLVM_PROFILE_FILE: "your_name-%p-%m.profraw"
RUST_LOG: "ed448_rust"
- name: Install grcov
run: curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-linux-x86_64.tar.bz2 | tar jxf -
- name: Gather coverage data
run: ./grcov $GITHUB_WORKSPACE --source-dir $GITHUB_WORKSPACE --llvm --commit-sha $GITHUB_SHA --binary-path $GITHUB_WORKSPACE/target/debug/ --output-type lcov --branch --ignore-not-existing --ignore "/*" --output-path $LCOV_DESTINATION --service-name $GITHUB_WORKFLOW
env:
LCOV_DESTINATION: ${{ runner.temp }}/lcov.info
- name: Codecov upload
uses: codecov/codecov-action@v1
with:
#token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: ${{ runner.temp }}/lcov.info
name: codecov-ed448-rust # optional
fail_ci_if_error: true # optional (default = false)
build:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
# The type of runner that the job will run on
runs-on: ${{ matrix.os }}
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout sources
uses: actions/checkout@v2
# Install stable toolchain
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
# Build the project
- name: Build the project
uses: actions-rs/cargo@v1
with:
command: build
args: --release

4
crates/ed448-rust/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
Cargo.lock
default.profraw
.DS_Store

View File

@ -0,0 +1,14 @@
## v0.1.2 - upcoming
* A feature was added to include [README.md](./README.md) during the tests to check examples in the usage.
## v0.1.1
* Add clippy directives
* Fix some documentation
* Add better documentation for the `PublicKey` struct
## v0.1.0
Initial release

View File

@ -0,0 +1,55 @@
[package]
name = "ed448-rust"
version = "0.1.2"
authors = ["Lolo_32 <lol.b@free.fr>"]
edition = "2018"
description = "Implementation of Edwards-Curve Digital Signature Algorithm (EdDSA) for ed448 only."
license = "MIT/Apache-2.0"
repository = "https://github.com/lolo32/ed448-rust"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
docinclude = [] # Used only for activating `doc(include="...")` on stable.
[dependencies.lazy_static]
version = "1.4.0"
[dependencies.num-bigint]
version = "0.4.0"
[dependencies.num-integer]
version = "0.1.44"
[dependencies.num-traits]
version = "0.2.14"
[dependencies.opaque-debug]
version = "0.3.0"
[dependencies.rand_core]
version = "0.6.2"
default-features = false
features = ["alloc"]
[dependencies.sha3]
version = "0.9.1"
[dependencies.subtle]
version = "2.4.0"
default-features = false
features = ["std"]
[dev-dependencies.base64]
version = "0.13.0"
[dev-dependencies.hex]
version = "0.4.3"
[dev-dependencies.rand_core]
version = "0.6.2"
features = ["getrandom"]
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
features = ["docinclude"] # Activate `docinclude` during docs.rs build.

View File

@ -0,0 +1,175 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Lolo_32
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,68 @@
# Ed448-Rust
[![Ci](https://github.com/lolo32/ed448-rust/actions/workflows/ci.yml/badge.svg)](https://github.com/lolo32/ed448-rust/actions/workflows/ci.yml)
[![Security audit](https://github.com/lolo32/ed448-rust/actions/workflows/audit.yml/badge.svg)](https://github.com/lolo32/ed448-rust/actions/workflows/audit.yml)
[![codecov](https://codecov.io/gh/lolo32/ed448-rust/branch/main/graph/badge.svg?token=V206OZ48AA)](https://codecov.io/gh/lolo32/ed448-rust)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docs.rs](https://docs.rs/ed448-rust/badge.svg)](https://docs.rs/ed448-rust/)
[![Crates.io](https://img.shields.io/crates/v/ed448-rust)](https://crates.io/crates/ed448-rust)
This is an implementation of Edwards-Curve Digital Signature Algorithm (EdDSA)
from the [RFC8032](https://tools.ietf.org/html/rfc8032) in pure Rust,
but only the ed448 support is implemented.
It's a EdDSA for ed448 signing/verifying.
_This is direct port of the Python code in the RFC, so it's the same warning
as it:_
> _**Note: This code is not intended for production. Although it should**_
> _**produce correct results for every input, it is slow and makes no**_
> _**attempt to avoid side-channel attacks.**_
## Usage
```rust
use core::convert::TryFrom;
use rand_core::OsRng;
use ed448_rust::{PrivateKey, PublicKey};
fn main () {
// Generate a new random private key
let private_key = PrivateKey::new(&mut OsRng);
// Store the key
let pkey_stored = private_key.as_bytes();
// Load a stored key before using it, or generating the public key
let private_key = PrivateKey::try_from(pkey_stored).unwrap();
// Extract associated public key
let public_key = PublicKey::from(&private_key);
// Store the public key
let pubkey_stored = public_key.as_byte();
// Sign a message without context
let signature = private_key.sign(b"Message to sign", None).unwrap();
// Sign a message with a context
let signature_ctx = private_key.sign(b"Message to sign", Some(&[0x01, 0xA6])).unwrap();
// Sign a pre-hashed message without context
let signature_ph = private_key.sign_ph(b"Message to sign", None).unwrap();
// Verify the signature without context
assert!(public_key.verify(b"Message to sign", &signature, None).is_ok());
// Verify the signature with context
assert!(public_key.verify(b"Message to sign", &signature_ctx, Some(&[0x01, 0xA6])).is_ok());
// Verify the signature with the pre-hash and without context
assert!(public_key.verify_ph(b"Message to sign", &signature_ph, None).is_ok());
}
```
## License
This code is licensed under [MIT] / [Apache2.0]
[MIT]: LICENSE_MIT.txt
[Apache2.0]: LICENSE_APACHE2.txt

View File

@ -0,0 +1,46 @@
// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Errors of this crate
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Ed448Error {
/// The provided array is not in the correct length for the private key.
///
/// It must be [`crate::KEY_LENGTH`].
///
/// See [PrivateKey::from](crate::PrivateKey::from).
WrongKeyLength,
/// The provided array is not in the correct length for the public key.
///
/// It must be [`crate::KEY_LENGTH`].
///
/// See [PublicKey::from](crate::PublicKey::from).
WrongPublicKeyLength,
/// The provided array is not in the correct length for the signature.
///
/// It must be [`SIG_LENGTH`](crate::SIG_LENGTH).
///
/// See [PublicKey::verify](crate::PublicKey::verify).
WrongSignatureLength,
/// The computed point is not valid (maybe forged/altered public key or signature).
InvalidPoint,
/// Signature verification failed.
///
/// See [PublicKey::verify](crate::PublicKey::verify).
InvalidSignature,
/// The provided context byte array is too long.
///
/// It must not be more than 256 byte.
ContextTooLong,
}

View File

@ -0,0 +1,291 @@
// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(feature = "docinclude", feature(external_doc))]
#![deny(
missing_docs,
missing_copy_implementations,
missing_debug_implementations,
trivial_numeric_casts,
unreachable_pub,
unsafe_code,
unused_extern_crates,
unused_qualifications,
single_use_lifetimes,
unused_import_braces,
unused_lifetimes,
unused_results,
clippy::all,
clippy::pedantic,
clippy::nursery
)]
#![doc(
test(no_crate_inject, attr(deny(warnings))),
test(attr(allow(unused_variables))),
html_no_source
)]
#![deny(
clippy::absurd_extreme_comparisons,
clippy::almost_swapped,
clippy::approx_constant,
clippy::async_yields_async,
clippy::bad_bit_mask,
clippy::cast_ref_to_mut,
clippy::clone_double_ref,
clippy::cmp_nan,
clippy::deprecated_semver,
clippy::derive_hash_xor_eq,
clippy::derive_ord_xor_partial_ord,
clippy::drop_copy,
clippy::drop_ref,
clippy::enum_clike_unportable_variant,
clippy::eq_op,
clippy::erasing_op,
clippy::float_cmp,
clippy::float_equality_without_abs,
clippy::fn_address_comparisons,
clippy::for_loops_over_fallibles,
clippy::forget_copy,
clippy::forget_ref,
clippy::if_let_mutex,
clippy::if_same_then_else,
clippy::ifs_same_cond,
clippy::ineffective_bit_mask,
clippy::infinite_iter,
clippy::inherent_to_string_shadow_display,
clippy::inline_fn_without_body,
clippy::invalid_atomic_ordering,
clippy::invalid_regex,
clippy::invisible_characters,
clippy::iter_next_loop,
clippy::iterator_step_by_zero,
clippy::let_underscore_lock,
clippy::logic_bug,
clippy::mem_discriminant_non_enum,
clippy::mem_replace_with_uninit,
clippy::min_max,
clippy::mismatched_target_os,
clippy::mistyped_literal_suffixes,
clippy::modulo_one,
clippy::mut_from_ref,
clippy::mutable_key_type,
clippy::never_loop,
clippy::nonsensical_open_options,
clippy::not_unsafe_ptr_arg_deref,
clippy::option_env_unwrap,
clippy::out_of_bounds_indexing,
clippy::panicking_unwrap,
clippy::possible_missing_comma,
clippy::reversed_empty_ranges,
clippy::self_assignment,
clippy::serde_api_misuse,
clippy::size_of_in_element_count,
clippy::suspicious_arithmetic_impl,
clippy::suspicious_op_assign_impl,
clippy::to_string_in_display,
clippy::transmuting_null,
clippy::undropped_manually_drops,
clippy::uninit_assumed_init,
clippy::unit_cmp,
clippy::unit_return_expecting_ord,
clippy::unsound_collection_transmute,
clippy::unused_io_amount,
clippy::useless_attribute,
clippy::vec_resize_to_zero,
clippy::vtable_address_comparisons,
clippy::while_immutable_condition,
clippy::wrong_transmute,
clippy::zst_offset
)]
#![allow(
non_snake_case,
non_upper_case_globals,
clippy::similar_names,
clippy::module_name_repetitions
)]
//! # EdDSA implementation for ed448
//!
//! This is a Edwards-Curve Digital Signature Algorithm (EdDSA) for ed448 only
//! in pure rust.
//!
//! # Usage
//!
//! There is two variants that can be combined to sign/verify:
//!
//! 1. [`PrivateKey::sign`](crate::PrivateKey::sign) to sign all the content
//! as-it and [`PrivateKey::sign_ph`](crate::PrivateKey::sign_ph) to
//! pre-hash the message internaly before signing it. It will be hashed
//! using Shake256 and the result of 64 byte will be signed/verified.
//!
//! Note: use the same variant for verifying the signature.
//!
//! 2. The second parameter of [`sign`](crate::PrivateKey::sign)/
//! [`sign_ph`](crate::PrivateKey::sign_ph) and the third of
//! [`verify`](crate::PublicKey::verify)/
//! [`verify_ph`](crate::PublicKey::verify_ph) if an optional context
//! of 255 byte length max.
//!
//! The context can be used to facilitate different signature over
//! different protocol, but it must be immuable over the protocol.
//! More information about this can be found at
//! [RFC 8032 Use of Contexts](https://tools.ietf.org/html/rfc8032#section-8.3).
//!
//! # Examples
//!
//! ## Generating a new key pair
//!
//! ```
//! use rand_core::OsRng;
//! use ed448_rust::{PrivateKey, PublicKey};
//! let private_key = PrivateKey::new(&mut OsRng);
//! let public_key = PublicKey::from(&private_key);
//! ```
//!
//! ## Sign a message
//!
//! ```
//! # use rand_core::OsRng;
//! use ed448_rust::{PrivateKey, Ed448Error};
//! # let retrieve_pkey = || PrivateKey::new(&mut OsRng);
//! let message = b"Message to sign";
//! let private_key = retrieve_pkey();
//! match private_key.sign(message, None) {
//! Ok(signature) => {
//! // Signature OK, use it
//! // This is a slice of 144 byte length
//! }
//! Err(Ed448Error::ContextTooLong) => {
//! // The used context is more than 255 bytes length
//! }
//! Err(_) => unreachable!()
//! }
//! ```
//!
//! ## Verify a signature
//!
//! ```
//! # use rand_core::OsRng;
//! use ed448_rust::{PublicKey, Ed448Error};
//! # let private_key = ed448_rust::PrivateKey::new(&mut OsRng);
//! let message = b"Signed message to verify";
//! # let retrieve_signature = || private_key.sign(message, None).unwrap();
//! # let retrieve_pubkey = || PublicKey::from(&private_key);
//! let public_key = retrieve_pubkey(); // A slice or array of KEY_LENGTH byte length
//! let signature = retrieve_signature(); // A slice or array of SIG_LENGTH byte length
//! match public_key.verify(message, &signature, None) {
//! Ok(()) => {
//! // Signature OK, use the message
//! }
//! Err(Ed448Error::InvalidSignature) => {
//! // The verification of the signature is invalid
//! }
//! Err(Ed448Error::ContextTooLong) => {
//! // The used context is more than 255 bytes length
//! }
//! Err(Ed448Error::WrongSignatureLength) => {
//! // The signature is not 144 bytes length
//! }
//! Err(_) => unreachable!()
//! }
//! ```
use sha3::{
digest::{ExtendableOutput, Update},
Shake256,
};
pub use crate::error::Ed448Error;
pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use std::borrow::Cow;
mod error;
mod point;
mod private_key;
mod public_key;
#[allow(
missing_docs,
missing_copy_implementations,
missing_debug_implementations
)]
#[doc(hidden)]
#[cfg_attr(feature = "docinclude", doc(include = "../README.md"))]
pub struct ReadmeDoctests;
/// Specialized [`Result`](core::result::Result) for this crate.
pub type Result<T> = core::result::Result<T, Ed448Error>;
/// Length of either a public or a private key length in byte.
pub const KEY_LENGTH: usize = 57;
/// Length of the signature length in byte.
pub const SIG_LENGTH: usize = 114;
/// Indicate if the message need to be pre-hashed before being signed/verified
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum PreHash {
/// Pre-hash the message
True,
/// Leave the message unchanged
False,
}
impl From<PreHash> for u8 {
#[inline]
fn from(hash: PreHash) -> Self {
match hash {
PreHash::False => 0,
PreHash::True => 1,
}
}
}
/// Produce a Shake256 for signing/verifying signatures
fn shake256(items: Vec<&[u8]>, ctx: &[u8], pre_hash: PreHash) -> Box<[u8]> {
#[allow(clippy::cast_possible_truncation)]
let mut shake = Shake256::default()
.chain(b"SigEd448")
.chain(&[pre_hash.into(), ctx.len() as u8])
.chain(ctx);
for item in items {
shake.update(item);
}
shake.finalize_boxed(114)
}
/// Common tasks for signing/verifying
#[allow(clippy::type_complexity)]
fn init_sig<'a, 'b>(
ctx: Option<&'b [u8]>,
pre_hash: PreHash,
msg: &'a [u8],
) -> Result<(Cow<'b, [u8]>, Cow<'a, [u8]>)> {
let ctx = ctx.unwrap_or(b"");
if ctx.len() > 255 {
return Err(Ed448Error::ContextTooLong);
}
let ctx = Cow::Borrowed(ctx);
let msg = match pre_hash {
PreHash::False => Cow::Borrowed(msg),
PreHash::True => {
let hash = Shake256::default().chain(msg).finalize_boxed(64).to_vec();
Cow::Owned(hash)
}
};
Ok((ctx, msg))
}

View File

@ -0,0 +1,433 @@
// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::{
convert::TryInto,
ops::{Add, Div, Mul, Neg, Sub},
};
use lazy_static::lazy_static;
use num_bigint::{BigInt, Sign};
use num_traits::{One, Zero};
use crate::{Ed448Error, KEY_LENGTH};
use subtle::{Choice, ConstantTimeEq};
lazy_static! {
// 2 ^ 448 - 2 ^224 - 1
static ref p: BigInt = BigInt::from(2).pow(448).sub(BigInt::from(2).pow(224)) - 1;
static ref d: Field = Field::new(BigInt::from(-39081));
static ref f0: Field = Field::new(BigInt::zero());
static ref f1: Field = Field::new(BigInt::one());
static ref xb: Field = Field::new(BigInt::from_bytes_be(
Sign::Plus,
&[
0x4F, 0x19, 0x70, 0xC6, 0x6B, 0xED, 0x0D, 0xED, 0x22, 0x1D, 0x15, 0xA6, 0x22, 0xBF,
0x36, 0xDA, 0x9E, 0x14, 0x65, 0x70, 0x47, 0x0F, 0x17, 0x67, 0xEA, 0x6D, 0xE3, 0x24,
0xA3, 0xD3, 0xA4, 0x64, 0x12, 0xAE, 0x1A, 0xF7, 0x2A, 0xB6, 0x65, 0x11, 0x43, 0x3B,
0x80, 0xE1, 0x8B, 0x00, 0x93, 0x8E, 0x26, 0x26, 0xA8, 0x2B, 0xC7, 0x0C, 0xC0, 0x5E,
]
));
static ref yb: Field = Field::new(BigInt::from_bytes_be(
Sign::Plus,
&[
0x69, 0x3F, 0x46, 0x71, 0x6E, 0xB6, 0xBC, 0x24, 0x88, 0x76, 0x20, 0x37, 0x56, 0xC9,
0xC7, 0x62, 0x4B, 0xEA, 0x73, 0x73, 0x6C, 0xA3, 0x98, 0x40, 0x87, 0x78, 0x9C, 0x1E,
0x05, 0xA0, 0xC2, 0xD7, 0x3A, 0xD3, 0xFF, 0x1C, 0xE6, 0x7C, 0x39, 0xC4, 0xFD, 0xBD,
0x13, 0x2C, 0x4E, 0xD7, 0xC8, 0xAD, 0x98, 0x08, 0x79, 0x5B, 0xF2, 0x30, 0xFA, 0x14,
]
));
static ref l: BigInt = BigInt::from_bytes_be(
Sign::Plus,
&[
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7c, 0xca, 0x23, 0xe9, 0xc4, 0x4e, 0xdb, 0x49, 0xae, 0xd6, 0x36, 0x90, 0x21, 0x6c,
0xc2, 0x72, 0x8d, 0xc5, 0x8f, 0x55, 0x23, 0x78, 0xc2, 0x92, 0xab, 0x58, 0x44, 0xf3,
]
);
}
#[derive(Debug, Clone)]
pub struct Field(BigInt);
impl Field {
pub fn new(value: BigInt) -> Self {
if value < BigInt::zero() {
Self((&p as &BigInt) + value)
} else {
Self(value % &p as &BigInt)
}
}
/// Field inverse (inverse of 0 is 0).
#[inline]
pub fn inv(self) -> Self {
Self::new(self.0.modpow(&(&p as &BigInt - 2), &p))
}
/// Compute sign of number, 0 or 1. The sign function
/// has the following property:
/// sign(x) = 1 - sign(-x) if x != 0.
#[inline]
pub fn sign(&self) -> BigInt {
&self.0 % 2
}
/// Field square root. Returns none if square root does not exist.
/// Note: not presently implemented for p mod 8 = 1 case.
pub fn sqrt(self) -> crate::Result<Self> {
// Compute candidate square root.
let y = self
.0
.modpow(&((&p as &BigInt).add(1_u32).div(&4)), &p as &BigInt);
let y = Self::new(y);
// Check square root candidate valid.
if &y * &y == self {
Ok(y)
} else {
Err(Ed448Error::InvalidPoint)
}
}
/// Is the field element the additive identity?
#[inline]
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
impl PartialEq for Field {
fn eq(&self, other: &Self) -> bool {
fn sign_to_choice(sign: Sign) -> Choice {
match sign {
Sign::Plus => 1,
Sign::Minus => 0,
Sign::NoSign => unreachable!(),
}
.into()
}
let me = self.0.to_u64_digits();
let other = other.0.to_u64_digits();
let val = me.1.ct_eq(&other.1);
let sign_me = sign_to_choice(me.0);
let sign_other = sign_to_choice(other.0);
let sign = sign_me ^ sign_other;
(val & !sign).into()
}
}
impl Add for Field {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
self + &other
}
}
impl Add<&'_ Self> for Field {
type Output = Self;
#[inline]
fn add(self, rhs: &Self) -> Self {
Self::new(self.0 + &rhs.0)
}
}
impl Add<&'_ Field> for &'_ Field {
type Output = Field;
#[inline]
fn add(self, other: &Field) -> Self::Output {
self.clone() + other
}
}
impl Sub for Field {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
self - &other
}
}
impl Sub<&'_ Self> for Field {
type Output = Self;
#[inline]
fn sub(self, other: &Self) -> Self {
Self::new(self.0 + &p as &BigInt - &other.0)
}
}
impl Sub<&'_ Field> for &'_ Field {
type Output = Field;
#[inline]
fn sub(self, other: &Field) -> Field {
self.clone() - other
}
}
impl Mul for Field {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
self * &other
}
}
impl Mul<&'_ Self> for Field {
type Output = Self;
#[inline]
fn mul(self, other: &Self) -> Self {
Self::new(self.0 * &other.0)
}
}
impl Mul<&'_ Field> for &'_ Field {
type Output = Field;
#[inline]
fn mul(self, other: &Field) -> Field {
self.clone() * other
}
}
impl Neg for Field {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::new(&p as &BigInt - self.0)
}
}
impl Div for Field {
type Output = Self;
#[inline]
fn div(self, other: Self) -> Self {
self / &other
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Div<&'_ Self> for Field {
type Output = Self;
#[inline]
fn div(self, other: &Self) -> Self {
self * other.clone().inv()
}
}
impl Div<&'_ Field> for &'_ Field {
type Output = Field;
#[inline]
fn div(self, other: &'_ Field) -> Field {
self.clone() / other
}
}
#[derive(Debug, Clone)]
pub struct Point {
x: Field,
y: Field,
z: Field,
}
impl Point {
pub fn new(x: &Field, y: &Field) -> crate::Result<Self> {
// Check that the point is actually on the curve.
if y * y + x * x == (&f1 as &Field) + &((&d as &Field) * x * x * y * y) {
Ok(Self {
x: x.clone(),
y: y.clone(),
..Self::default()
})
} else {
Err(Ed448Error::InvalidPoint)
}
}
/// Order of basepoint.
#[inline]
pub fn l() -> &'static BigInt {
&l as &BigInt
}
#[inline]
pub fn new_stdbase() -> Self {
Self::new(&f0, &f1).unwrap()
}
/// Point doubling.
pub fn double(self) -> Self {
// The formulas are from EFD.
let (x1s, y1s, z1s) = (&self.x * &self.x, &self.y * &self.y, &self.z * &self.z);
let xys = &self.x + &self.y;
let F = &x1s + &y1s;
let J = &F - &(&z1s + &z1s);
let (x, y, z) = (
(&xys * &xys - &x1s - &y1s) * &J,
&F * &(&x1s - &y1s),
&F * &J,
);
Self { x, y, z }
}
/// Encode a point representation.
pub fn encode(&self) -> [u8; KEY_LENGTH] {
let (xp, yp) = (&self.x / &self.z, &self.y / &self.z);
// Encode y.
let mut tmp = yp.0.magnitude().to_bytes_le();
tmp.resize_with(KEY_LENGTH, Default::default);
let mut s: [u8; KEY_LENGTH] = tmp.try_into().unwrap();
// Add sign bit of x to encoding.
if !xp.sign().is_zero() {
s[56] |= 0b1000_0000;
}
s
}
/// Decode a point representation.
pub fn decode(s: &[u8]) -> crate::Result<Self> {
// Extract signbit.
let xs = BigInt::from(s[56] >> 7);
// Decode y. If this fails, fail.
let y = Self::frombytes(s)?;
// Try to recover x. If it does not exist, or if zero and xs
// are wrong, fail.
let mut x = Self::solve_x2(&y).sqrt()?;
if x.is_zero() && xs != x.sign() {
return Err(Ed448Error::InvalidPoint);
}
// If sign of x isn't correct, flip it.
if x.sign() != xs {
x = -x;
}
// Return the constructed point.
Self::new(&x, &y)
}
/// Unserialize number from bits.
fn frombytes(x: &[u8]) -> crate::Result<Field> {
let rv = BigInt::from_bytes_le(Sign::Plus, x) % BigInt::from(2).pow(455);
if &rv < &p as &BigInt {
Ok(Field::new(rv))
} else {
Err(Ed448Error::InvalidPoint)
}
}
/// Solve for x^2.
#[inline]
fn solve_x2(y: &Field) -> Field {
(y * y - &f1 as &Field) / (&d as &Field * y * y - &f1 as &Field)
}
}
impl Mul<&'_ BigInt> for Point {
type Output = Self;
#[inline]
fn mul(self, x: &BigInt) -> Self {
self * x.clone()
}
}
impl Mul<BigInt> for Point {
type Output = Self;
fn mul(mut self, mut x: BigInt) -> Self {
let mut r = Self::new_stdbase();
while !x.is_zero() {
if !((&x % 2) as BigInt).is_zero() {
r = r + &self;
}
self = self.double();
x /= 2;
}
r
}
}
impl Add for Point {
type Output = Self;
fn add(self, y: Self) -> Self {
// The formulas are from EFD.
let (xcp, ycp, zcp) = (&self.x * &y.x, &self.y * &y.y, &self.z * &y.z);
let B = &zcp * &zcp;
let E = &d as &Field * &xcp * &ycp;
let (F, G) = (&B - &E, B + E);
let x = &zcp * &F * ((self.x + self.y) * (y.x + y.y) - &xcp - &ycp);
let (y, z) = (zcp * &G * (ycp - xcp), F * G);
Self { x, y, z }
}
}
impl Add<&'_ Self> for Point {
type Output = Self;
#[inline]
fn add(self, other: &Self) -> Self {
self + other.clone()
}
}
impl Add<&'_ Point> for &'_ Point {
type Output = Point;
#[inline]
fn add(self, other: &Point) -> Point {
self.clone() + other.clone()
}
}
impl PartialEq<Self> for Point {
fn eq(&self, other: &Self) -> bool {
// Need to check x1/z1 == x2/z2 and similarly for y, so cross
// multiply to eliminate divisions.
let xn1 = &self.x * &other.z;
let xn2 = &other.x * &self.z;
let yn1 = &self.y * &other.z;
let yn2 = &other.y * &self.z;
xn1 == xn2 && yn1 == yn2
}
}
impl Default for Point {
#[inline]
fn default() -> Self {
Self {
x: xb.clone(),
y: yb.clone(),
z: Field::new(BigInt::one()),
}
}
}

View File

@ -0,0 +1,255 @@
// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::convert::{TryFrom, TryInto};
use num_bigint::{BigInt, Sign};
use rand_core::{CryptoRng, RngCore};
use sha3::{
digest::{ExtendableOutput, Update},
Shake256,
};
use crate::{
init_sig, point::Point, shake256, Ed448Error, PreHash, PublicKey, KEY_LENGTH, SIG_LENGTH,
};
#[allow(clippy::redundant_pub_crate)]
pub(crate) type PrivateKeyRaw = [u8; KEY_LENGTH];
#[allow(clippy::redundant_pub_crate)]
pub(crate) type SeedRaw = [u8; KEY_LENGTH];
/// This represent a private key. **Must be kept secret.**
///
/// Could be used to generate a new one or restore an older already saved.
#[derive(Copy, Clone)]
pub struct PrivateKey(PrivateKeyRaw);
opaque_debug::implement!(PrivateKey);
impl PrivateKey {
/// Generate a random key.
///
/// # Example
///
/// ```
/// use rand_core::OsRng;
/// use ed448_rust::PrivateKey;
/// let private_key = PrivateKey::new(&mut OsRng);
/// ```
pub fn new<T>(rnd: &mut T) -> Self
where
T: CryptoRng + RngCore,
{
let mut key = [0; KEY_LENGTH];
rnd.fill_bytes(&mut key);
Self::from(key)
}
/// Convert the private key to a format exportable.
///
/// # Example
///
/// ```
/// # use rand_core::OsRng;
/// # use ed448_rust::PrivateKey;
/// # let private_key = PrivateKey::new(&mut OsRng);
/// let exportable_pkey = private_key.as_bytes();
/// ```
#[inline]
#[must_use]
pub const fn as_bytes(&self) -> &[u8; KEY_LENGTH] {
&self.0
}
pub(crate) fn expand(&self) -> (PrivateKeyRaw, SeedRaw) {
// 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
// digest in a 114-octet large buffer, denoted h.
let h = Shake256::default()
.chain(self.as_bytes())
.finalize_boxed(114);
// Only the lower 57 bytes are used for generating the public key.
let mut s: [u8; KEY_LENGTH] = h[..KEY_LENGTH].try_into().unwrap();
// 2. Prune the buffer: The two least significant bits of the first
// octet are cleared, all eight bits the last octet are cleared, and
// the highest bit of the second to last octet is set.
s[0] &= 0b1111_1100;
s[56] = 0;
s[55] |= 0b1000_0000;
let seed: [u8; KEY_LENGTH] = h[KEY_LENGTH..].try_into().unwrap();
(s, seed)
}
/// Sign with key pair.
///
/// It's possible to indicate a context. More information in
/// [RFC8032 8.3 Use of contexts](https://tools.ietf.org/html/rfc8032#section-8.3).
///
/// # Examples
///
/// Without any context.
///
/// ```
/// use ed448_rust::PrivateKey;
/// let pkey = PrivateKey::from([0xcd, 0x23, 0xd2, 0x4f, 0x71, 0x42, 0x74, 0xe7, 0x44, 0x34, 0x32, 0x37, 0xb9,
/// 0x32, 0x90, 0xf5, 0x11, 0xf6, 0x42, 0x5f, 0x98, 0xe6, 0x44, 0x59, 0xff, 0x20, 0x3e, 0x89, 0x85,
/// 0x08, 0x3f, 0xfd, 0xf6, 0x05, 0x00, 0x55, 0x3a, 0xbc, 0x0e, 0x05, 0xcd, 0x02, 0x18, 0x4b, 0xdb,
/// 0x89, 0xc4, 0xcc, 0xd6, 0x7e, 0x18, 0x79, 0x51, 0x26, 0x7e, 0xb3, 0x28]);
/// let msg = &[0x0c, 0x3e, 0x54, 0x40, 0x74, 0xec, 0x63, 0xb0, 0x26, 0x5e, 0x0c];
/// let sig = pkey.sign(msg, None).unwrap();
///
/// assert_eq!(
/// sig.iter().map(|b| format!("{:02x}", b)).collect::<Vec<String>>().concat(),
/// "1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8a\
/// c5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc9\
/// 57dbb47b51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00"
/// );
/// ```
///
/// With a context.
///
/// ```
/// use ed448_rust::PrivateKey;
/// let pkey = PrivateKey::from([0xc4, 0xea, 0xb0, 0x5d, 0x35, 0x70, 0x07, 0xc6, 0x32, 0xf3, 0xdb, 0xb4, 0x84,
/// 0x89, 0x92, 0x4d, 0x55, 0x2b, 0x08, 0xfe, 0x0c, 0x35, 0x3a, 0x0d, 0x4a, 0x1f, 0x00, 0xac, 0xda,
/// 0x2c, 0x46, 0x3a, 0xfb, 0xea, 0x67, 0xc5, 0xe8, 0xd2, 0x87, 0x7c, 0x5e, 0x3b, 0xc3, 0x97, 0xa6,
/// 0x59, 0x94, 0x9e, 0xf8, 0x02, 0x1e, 0x95, 0x4e, 0x0a, 0x12, 0x27, 0x4e]);
/// let msg = &[03];
/// let sig = pkey.sign(msg, Some(&[0x66, 0x6f, 0x6f])).unwrap();
///
/// assert_eq!(
/// sig.iter().map(|b| format!("{:02x}", b)).collect::<Vec<String>>().concat(),
/// "d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2151f7647f11d8ca2\
/// ae279fb842d607217fce6e042f6815ea000c85741de5c8da1144a6a1aba7f96de42505d7a7298\
/// 524fda538fccbbb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c00"
/// );
/// ```
///
/// # Errors
///
/// * [`Ed448Error::ContextTooLong`] if the context is more than 255 byte length.
#[inline]
pub fn sign(&self, msg: &[u8], ctx: Option<&[u8]>) -> crate::Result<[u8; SIG_LENGTH]> {
self.sign_real(msg, ctx, PreHash::False)
}
/// Sign with key pair. Message is pre-hashed before signed.
///
/// The message is hashed before being signed. The size of the signed message in this
/// case is always 64 bytes length.
///
/// See [`PrivateKey::sign`].
///
/// # Errors
///
/// * [`Ed448Error::ContextTooLong`] if the context is more than 255 byte length.
#[inline]
pub fn sign_ph(&self, msg: &[u8], ctx: Option<&[u8]>) -> crate::Result<[u8; SIG_LENGTH]> {
self.sign_real(msg, ctx, PreHash::True)
}
fn sign_real(
&self,
msg: &[u8],
ctx: Option<&[u8]>,
pre_hash: PreHash,
) -> crate::Result<[u8; SIG_LENGTH]> {
let (ctx, msg) = init_sig(ctx, pre_hash, msg)?;
// Expand key.
let (a, seed) = &self.expand();
let a = BigInt::from_bytes_le(Sign::Plus, a);
// Calculate r and R (R only used in encoded form).
let r = shake256(vec![seed, &msg], ctx.as_ref(), pre_hash);
let r = BigInt::from_bytes_le(Sign::Plus, r.as_ref()) % Point::l();
let R = (Point::default() * &r).encode();
// Calculate h.
let h = shake256(
vec![&R, &PublicKey::from(a.clone()).as_byte(), &msg],
ctx.as_ref(),
pre_hash,
);
let h = BigInt::from_bytes_le(Sign::Plus, h.as_ref()) % Point::l();
// Calculate s.
let S = (r + h * a) % Point::l();
// The final signature is a concatenation of R and S.
let mut S = S.magnitude().to_bytes_le();
S.resize_with(KEY_LENGTH, Default::default);
let S: [u8; KEY_LENGTH] = S.try_into().unwrap();
Ok([R, S].concat().try_into().unwrap())
}
}
/// Restore the private key from the slice.
impl From<PrivateKeyRaw> for PrivateKey {
#[inline]
fn from(array: PrivateKeyRaw) -> Self {
Self(array)
}
}
/// Restore the private key from an array.
///
/// # Error
///
/// Could return [`Ed448Error::WrongKeyLength`] if the array's length
/// is not [`KEY_LENGTH`].
impl TryFrom<&'_ [u8]> for PrivateKey {
type Error = Ed448Error;
fn try_from(bytes: &[u8]) -> crate::Result<Self> {
if bytes.len() != KEY_LENGTH {
return Err(Ed448Error::WrongKeyLength);
}
let bytes: &[u8; KEY_LENGTH] = bytes.try_into().unwrap();
Ok(Self::from(bytes))
}
}
impl From<&'_ PrivateKeyRaw> for PrivateKey {
#[inline]
fn from(bytes: &PrivateKeyRaw) -> Self {
Self::from(*bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::OsRng;
#[test]
fn create_new_pkey() {
let pkey = PrivateKey::new(&mut OsRng);
let a = pkey.as_bytes();
assert_eq!(a.len(), KEY_LENGTH);
}
#[test]
fn invalid_key_len() {
let invalid_pk = PrivateKey::try_from(&[0x01_u8][..]);
assert_eq!(invalid_pk.unwrap_err(), Ed448Error::WrongKeyLength);
}
#[test]
fn invalid_context_length() {
let pkey = PrivateKey::new(&mut OsRng);
let ctx = [0; 256];
let invalid_sig = pkey.sign(b"message", Some(&ctx));
assert_eq!(invalid_sig.unwrap_err(), Ed448Error::ContextTooLong);
}
}

View File

@ -0,0 +1,293 @@
// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::convert::TryFrom;
use num_bigint::{BigInt, Sign};
use crate::{
init_sig,
point::Point,
private_key::PrivateKey,
{shake256, Ed448Error, PreHash, KEY_LENGTH, SIG_LENGTH},
};
/// This is a public key. _Should be distributed._
///
/// You can extract a `PublicKey` by calling [`Self::from()`].
#[derive(Clone)]
pub struct PublicKey(Point);
opaque_debug::implement!(PublicKey);
impl PublicKey {
/// Convert the public key to an easily exportable format.
#[inline]
#[must_use]
pub fn as_byte(&self) -> [u8; 57] {
// 4. The public key A is the encoding of the point [s]B.
self.0.encode()
}
/// Verify signature with public key.
///
/// # Example
///
/// ```
/// # use rand_core::OsRng;
/// use ed448_rust::{PublicKey, Ed448Error};
/// # let private_key = ed448_rust::PrivateKey::new(&mut OsRng);
/// let message = b"Signed message to verify";
/// # let retrieve_signature = || private_key.sign(message, None).unwrap();
/// # let retrieve_pubkey = || PublicKey::from(&private_key);
/// let public_key = retrieve_pubkey();
/// let signature = retrieve_signature();
/// match public_key.verify(message, &signature, None) {
/// Ok(()) => {
/// // Signature OK, use the message
/// }
/// Err(Ed448Error::InvalidSignature) => {
/// // The verification of the signature is invalid
/// }
/// Err(Ed448Error::ContextTooLong) => {
/// // The used context is more than 255 bytes length
/// }
/// Err(Ed448Error::WrongSignatureLength) => {
/// // The signature is not 144 bytes length
/// }
/// Err(_) => unreachable!()
/// }
/// ```
///
/// # Errors
///
/// * [`Ed448Error::InvalidSignature`] if the signature is not valid, either the public key
/// or the signature used are not the right, or the message has been altered.
/// * [`Ed448Error::ContextTooLong`] if the optional context is more than 255 byte length.
/// * [`Ed448Error::WrongSignatureLength`] if the signature is not `SIG_LENGTH` byte.
#[inline]
pub fn verify(&self, msg: &[u8], sign: &[u8], ctx: Option<&[u8]>) -> crate::Result<()> {
self.verify_real(msg, sign, ctx, PreHash::False)
}
/// Verify signature with public key. Message is pre-hashed before checked.
///
/// See [`PublicKey::verify`] for more information.
///
/// # Errors
///
/// * [`Ed448Error::InvalidSignature`] if the signature is not valid, either the public key
/// or the signature used are not the right, or the message has been altered.
/// * [`Ed448Error::ContextTooLong`] if the optional context is more than 255 byte length.
/// * [`Ed448Error::WrongSignatureLength`] if the signature is not `SIG_LENGTH` byte.
#[inline]
pub fn verify_ph(&self, msg: &[u8], sign: &[u8], ctx: Option<&[u8]>) -> crate::Result<()> {
self.verify_real(msg, sign, ctx, PreHash::True)
}
fn verify_real(
&self,
msg: &[u8],
sign: &[u8],
ctx: Option<&[u8]>,
pre_hash: PreHash,
) -> crate::Result<()> {
// Sanity-check sizes.
if sign.len() < SIG_LENGTH {
return Err(Ed448Error::WrongSignatureLength);
}
// Split signature into R and S, and parse.
let (Rraw, Sraw) = sign.split_at(KEY_LENGTH);
let (R, S) = (
Point::decode(Rraw).map_err(|_| Ed448Error::InvalidSignature)?,
BigInt::from_bytes_le(Sign::Plus, Sraw),
);
// Parse public key.
let A = Point::decode(&self.as_byte()).map_err(|_| Ed448Error::InvalidSignature)?;
if &S >= Point::l() {
return Err(Ed448Error::InvalidSignature);
}
// Calculate h.
let h = {
let (ctx, msg) = init_sig(ctx, pre_hash, msg)?;
shake256(vec![Rraw, &self.as_byte(), &msg], ctx.as_ref(), pre_hash)
};
let h = BigInt::from_bytes_le(Sign::Plus, &h) % Point::l();
// Calculate left and right sides of check eq.
let rhs = R + (A * h);
let lhs = Point::default() * S;
// Check eq. holds?
if lhs.double().double() == rhs.double().double() {
Ok(())
} else {
Err(Ed448Error::InvalidSignature)
}
}
}
/// Instantiate a `PublicKey` from the `PrivateKey`.
impl From<&PrivateKey> for PublicKey {
#[inline]
fn from(private_key: &PrivateKey) -> Self {
let (s, _) = &private_key.expand();
// 3. Interpret the buffer as the little-endian integer, forming a
// secret scalar s.
Self::from(BigInt::from_bytes_le(Sign::Plus, s))
}
}
/// Do not use, it's for internal use only to generate the PublicKey
#[doc(hidden)]
impl From<BigInt> for PublicKey {
#[inline]
fn from(s: BigInt) -> Self {
// Perform a known-base-point scalar multiplication [s]B.
let A = Point::default() * s;
// 4. The public key A is the encoding of the point [s]B.
Self(A)
}
}
impl From<[u8; KEY_LENGTH]> for PublicKey {
#[inline]
fn from(array: [u8; KEY_LENGTH]) -> Self {
Self(Point::decode(&array).expect("bad ed448 public key"))
}
}
impl From<&'_ [u8; KEY_LENGTH]> for PublicKey {
#[inline]
fn from(array: &'_ [u8; KEY_LENGTH]) -> Self {
Self(Point::decode(array).expect("bad ed448 public key"))
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = Ed448Error;
#[inline]
fn try_from(array: &[u8]) -> Result<Self, Self::Error> {
if array.len() != KEY_LENGTH {
return Err(Ed448Error::WrongPublicKeyLength);
}
Ok(Self(Point::decode(array)?))
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use super::*;
use rand_core::OsRng;
#[test]
fn test_vectors_rfc8032_public() {
let secret_vec = hex::decode(
"6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3\
528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b",
)
.unwrap();
let ref_public = hex::decode(
"5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778\
edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180",
)
.unwrap();
let secret = PrivateKey::try_from(&secret_vec[..]).unwrap();
let public = PublicKey::from(&secret);
assert_eq!(&public.as_byte()[..], &ref_public[..]);
}
#[test]
fn wrong_verification_with_another_pub_key() {
let secret_1 = PrivateKey::new(&mut OsRng);
let msg = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec.";
let sig_1 = secret_1.sign(msg, None).unwrap();
let public_2 = PublicKey::from(&PrivateKey::new(&mut OsRng));
assert_eq!(
public_2.verify(msg, &sig_1, None).unwrap_err(),
Ed448Error::InvalidSignature
);
}
#[test]
fn wrong_pubkey_length() {
let pub_key = PublicKey::try_from(&[0x01_u8][..]);
assert_eq!(pub_key.unwrap_err(), Ed448Error::WrongPublicKeyLength);
}
#[test]
fn wrong_sign_length() {
let pubkey = PublicKey::from(&PrivateKey::new(&mut OsRng));
let sig = [0x01; SIG_LENGTH - 1];
assert_eq!(
pubkey.verify(b"message", &sig, None).unwrap_err(),
Ed448Error::WrongSignatureLength
);
}
#[test]
fn instantiate_pubkey() {
let pkey = PrivateKey::new(&mut OsRng);
let pkey_slice = *pkey.as_bytes();
let pub_key1 = PublicKey::from(&pkey_slice);
let pub_key2 = PublicKey::from(pkey_slice);
assert_eq!(pub_key1.as_byte(), pub_key2.as_byte());
}
#[test]
fn wrong_with_altered_message() {
let secret = PrivateKey::new(&mut OsRng);
let public = PublicKey::from(&PrivateKey::new(&mut OsRng));
let msg_1 = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec.";
// One dot missing at the end
let msg_2 = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec";
let sig = secret.sign(msg_1, None).unwrap();
assert_eq!(
public.verify(msg_2, &sig, None).unwrap_err(),
Ed448Error::InvalidSignature
);
}
#[test]
fn wrong_with_forged_pub_key() {
let secret = PrivateKey::new(&mut OsRng);
let public = PublicKey::from(&[255; KEY_LENGTH]);
let msg = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec.";
// One dot missing at the end
let sig = secret.sign(msg, None).unwrap();
assert_eq!(
public.verify(msg, &sig, None).unwrap_err(),
Ed448Error::InvalidSignature
);
}
#[test]
fn wrong_with_forged_signature() {
let secret = PrivateKey::new(&mut OsRng);
let public = PublicKey::from(&secret);
let msg = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec.";
// One dot missing at the end
let sig = [1; SIG_LENGTH];
assert_eq!(
public.verify(msg, &sig, None).unwrap_err(),
Ed448Error::InvalidSignature
);
}
}

View File

@ -0,0 +1,215 @@
use std::convert::TryFrom;
use ed448_rust::{PrivateKey, PublicKey};
#[test]
fn ed448() {
vec![
(
"-----Blank",
"6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3\
fc5b94492f8f032e7549a20098f95b",
"5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6\
783df1e50f6cd1fa1abeafe8256180",
"",
None,
"533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd\
7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852\
db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600",
),
(
"-----1 octet",
"c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3b\
c397a659949ef8021e954e0a12274e",
"43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b843\
8ea4cb82169c235160627b4c3a9480",
"03",
None,
"26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f\
62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0\
af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00",
),
(
"-----1 octet (with context)",
"c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3b\
c397a659949ef8021e954e0a12274e",
"43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b843\
8ea4cb82169c235160627b4c3a9480",
"03",
Some("666f6f"),
"d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2151f7647f11d8ca2ae27\
9fb842d607217fce6e042f6815ea000c85741de5c8da1144a6a1aba7f96de42505d7a7298524fda53\
8fccbbb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c00",
),
(
"-----11 octets",
"cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffdf60500553abc0e05cd02\
184bdb89c4ccd67e187951267eb328",
"dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a54\
3ea4cb5f7e9f1d8b00696447001400",
"0c3e544074ec63b0265e0c",
None,
"1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8ac5cd\
fb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc957dbb47b\
51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00",
),
(
"-----12 octets",
"258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29a\
df86ec9929dccb52c1c5fd2ff7e21b",
"3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d1\
9b945361726bd75e149ef09817f580",
"64a65f3cdedcdd66811e2915",
None,
"7eeeab7c4e50fb799b418ee5e3197ff6bf15d43a14c34389b59dd1a7b1b85b4ae90438aca634bea45e3a\
2695f1270f07fdcdf7c62b8efeaf00b45c2c96ba457eb1a8bf075a3db28e5c24f6b923ed4ad747c3c\
9e03c7079efb87cb110d3a99861e72003cbae6d6b8b827e4e6c143064ff3c00",
),
(
"-----13 octets",
"7ef4e84544236752fbb56b8f31a23a10e42814f5f55ca037cdcc11c64c9a3b2949c1bb60700314611732\
a6c2fea98eebc0266a11a93970100e",
"b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381c\
a0938e13c6c07b174be65dfa578e80",
"64a65f3cdedcdd66811e2915e7",
None,
"6a12066f55331b6c22acd5d5bfc5d71228fbda80ae8dec26bdd306743c5027cb4890810c162c02746867\
5ecf645a83176c0d7323a2ccde2d80efe5a1268e8aca1d6fbc194d3f77c44986eb4ab4177919ad8be\
c33eb47bbb5fc6e28196fd1caf56b4e7e0ba5519234d047155ac727a1053100",
),
(
"-----64 octets",
"d65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca\
6dfe0fb9f4fab4fa135d5542ea3f01",
"df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b\
099bb03b3fb50906cb28bd8a081f00",
"bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b50\
79631f64eb3112182da3005835461113718d1a5ef944",
None,
"554bc2480860b49eab8532d2a533b7d578ef473eeb58c98bb2d0e1ce488a98b18dfde9b9b90775e67f47\
d4a1c3482058efc9f40d2ca033a0801b63d45b3b722ef552bad3b4ccb667da350192b61c508cf7b6b\
5adadc2c8d9a446ef003fb05cba5f30e88e36ec2703b349ca229c2670833900",
),
(
"-----256 octets",
"2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162\
343a21c8590aa9cebca9014c636df5",
"79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d\
81e22e9ec80e7690862ef3d4ed3a00",
"15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8\
409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f\
2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536\
c8ba516a6039c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316db\
c5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145\
c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38\
fabdf86f3bef6044819de11",
None,
"c650ddbb0601c19ca11439e1640dd931f43c518ea5bea70d3dcde5f4191fe53f00cf966546b72bcc7d58\
be2b9badef28743954e3a44a23f880e8d4f1cfce2d7a61452d26da05896f0a50da66a239a8a188b6d\
825b3305ad77b73fbac0836ecc60987fd08527c1a8e80d5823e65cafe2a3d00",
),
(
"-----1023 octets",
"872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fa\
fb7632d8be199ea165f5ad55dd9ce8",
"a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56\
f64eecaef8cdf11cc38737838cf400",
"6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed6515580\
07db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8c\
edd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11\
c77f7777e972660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d0443\
22fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c\
4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723\
fa37fe387c7d3e50bdf9813a30e5bb12cf4cd930c40cfb4e1fc622592a49588794494d56d24ea4b40\
c89fc0596cc9ebb961c8cb10adde976a5d602b1c3f85b9b9a001ed3c6a4d3b1437f52096cd1956d04\
2a597d561a596ecd3d1735a8d570ea0ec27225a2c4aaff26306d1526c1af3ca6d9cf5a2c98f47e1c4\
6db9a33234cfd4d81f2c98538a09ebe76998d0d8fd25997c7d255c6d66ece6fa56f11144950f02779\
5e653008f4bd7ca2dee85d8e90f3dc315130ce2a00375a318c7c3d97be2c8ce5b6db41a6254ff264f\
a6155baee3b0773c0f497c573f19bb4f4240281f0b1f4f7be857a4e59d416c06b4c50fa09e1810ddc\
6b1467baeac5a3668d11b6ecaa901440016f389f80acc4db977025e7f5924388c7e340a732e554440\
e76570f8dd71b7d640b3450d1fd5f0410a18f9a3494f707c717b79b4bf75c98400b096b21653b5d21\
7cf3565c9597456f70703497a078763829bc01bb1cbc8fa04eadc9a6e3f6699587a9e75c94e5bab00\
36e0b2e711392cff0047d0d6b05bd2a588bc109718954259f1d86678a579a3120f19cfb2963f177ae\
b70f2d4844826262e51b80271272068ef5b3856fa8535aa2a88b2d41f2a0e2fda7624c2850272ac4a\
2f561f8f2f7a318bfd5caf9696149e4ac824ad3460538fdc25421beec2cc6818162d06bbed0c40a38\
7192349db67a118bada6cd5ab0140ee273204f628aad1c135f770279a651e24d8c14d75a6059d76b9\
6a6fd857def5e0b354b27ab937a5815d16b5fae407ff18222c6d1ed263be68c95f32d908bd895cd76\
207ae726487567f9a67dad79abec316f683b17f2d02bf07e0ac8b5bc6162cf94697b3c27cd1fea49b\
27f23ba2901871962506520c392da8b6ad0d99f7013fbc06c2c17a569500c8a7696481c1cd33e9b14\
e40b82e79a5f5db82571ba97bae3ad3e0479515bb0e2b0f3bfcd1fd33034efc6245eddd7ee2086dda\
e2600d8ca73e214e8c2b0bdb2b047c6a464a562ed77b73d2d841c4b34973551257713b753632efba3\
48169abc90a68f42611a40126d7cb21b58695568186f7e569d2ff0f9e745d0487dd2eb997cafc5abf\
9dd102e62ff66cba87",
None,
"e301345a41a39a4d72fff8df69c98075a0cc082b802fc9b2b6bc503f926b65bddf7f4c8f1cb49f6396af\
c8a70abe6d8aef0db478d4c6b2970076c6a0484fe76d76b3a97625d79f1ce240e7c576750d2955282\
86f719b413de9ada3e8eb78ed573603ce30d8bb761785dc30dbc320869e1a00",
),
]
.iter()
.for_each(|(name, pkey, pub_key, msg, context, sig)| {
println!("Test: {}", name);
let pkey = hex::decode(pkey).unwrap();
let pub_key = hex::decode(pub_key).unwrap();
let msg = hex::decode(msg).unwrap();
let context = context.map(|ctx| hex::decode(ctx).unwrap());
let sig = hex::decode(sig).unwrap();
let pkey = PrivateKey::try_from(&pkey[..]).unwrap();
let pub_k = PublicKey::from(&pkey);
assert_eq!(&pub_k.as_byte()[..], &pub_key[..]);
let sig_ = pkey.sign(&msg, context.as_deref()).unwrap();
assert_eq!(&sig_[..], &sig[..]);
pub_k.verify(&msg, &sig, context.as_deref()).unwrap();
})
}
#[test]
fn ed448ph() {
vec![
(
"-----TEST abc",
"833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05\
d6dbefde69e3ab2cec7c867c6e2c49",
"259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303\
d90d8132c276d5ed3d5d01c0f53880",
"616263",
None,
"822f6901f7480f3d5f562c592994d9693602875614483256505600bbc281ae381f54d6bce2ea91157493\
2f52a4e6cadd78769375ec3ffd1b801a0d9b3f4030cd433964b6457ea39476511214f97469b57dd32\
dbc560a9a94d00bff07620464a3ad203df7dc7ce360c3cd3696d9d9fab90f00",
),
(
"-----TEST abc (with context)",
"833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05\
d6dbefde69e3ab2cec7c867c6e2c49",
"259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303\
d90d8132c276d5ed3d5d01c0f53880",
"616263",
Some("666f6f"),
"c32299d46ec8ff02b54540982814dce9a05812f81962b649d528095916a2aa481065b1580423ef927ecf\
0af5888f90da0f6a9a85ad5dc3f280d91224ba9911a3653d00e484e2ce232521481c8658df304bb77\
45a73514cdb9bf3e15784ab71284f8d0704a608c54a6b62d97beb511d132100",
),
]
.iter()
.for_each(|(name, pkey, pub_key, msg, context, sig)| {
println!("Test pre-hashed: {}", name);
let pkey = hex::decode(pkey).unwrap();
let pub_key = hex::decode(pub_key).unwrap();
let msg = hex::decode(msg).unwrap();
let context = context.map(|ctx| hex::decode(ctx).unwrap());
let sig = hex::decode(sig).unwrap();
let pkey = PrivateKey::try_from(&pkey[..]).unwrap();
let pub_k = PublicKey::from(&pkey);
assert_eq!(&pub_k.as_byte()[..], &pub_key[..]);
let sig_ = pkey.sign_ph(&msg, context.as_deref()).unwrap();
assert_eq!(&sig_[..], &sig[..]);
pub_k.verify_ph(&msg, &sig, context.as_deref()).unwrap();
})
}