mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
additional
This commit is contained in:
parent
5ccd96e47b
commit
c76c07874f
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -18,6 +18,7 @@ members = [
|
||||
"crates/channel-wasm",
|
||||
"crates/classgroup",
|
||||
"crates/bls48581",
|
||||
"crates/ed448-rust",
|
||||
"crates/rpm",
|
||||
]
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, ¤t_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())))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
crates/ed448-rust/.editorconfig
Normal file
10
crates/ed448-rust/.editorconfig
Normal 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
|
||||
28
crates/ed448-rust/.github/workflows/audit.yml
vendored
Normal file
28
crates/ed448-rust/.github/workflows/audit.yml
vendored
Normal 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 }}
|
||||
171
crates/ed448-rust/.github/workflows/ci.yml
vendored
Normal file
171
crates/ed448-rust/.github/workflows/ci.yml
vendored
Normal 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
4
crates/ed448-rust/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
default.profraw
|
||||
.DS_Store
|
||||
14
crates/ed448-rust/CHANGELOG.md
Normal file
14
crates/ed448-rust/CHANGELOG.md
Normal 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
|
||||
55
crates/ed448-rust/Cargo.toml
Normal file
55
crates/ed448-rust/Cargo.toml
Normal 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.
|
||||
175
crates/ed448-rust/LICENSE_APACHE2.txt
Normal file
175
crates/ed448-rust/LICENSE_APACHE2.txt
Normal 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.
|
||||
21
crates/ed448-rust/LICENSE_MIT.txt
Normal file
21
crates/ed448-rust/LICENSE_MIT.txt
Normal 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.
|
||||
68
crates/ed448-rust/README.md
Normal file
68
crates/ed448-rust/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Ed448-Rust
|
||||
|
||||
[](https://github.com/lolo32/ed448-rust/actions/workflows/ci.yml)
|
||||
[](https://github.com/lolo32/ed448-rust/actions/workflows/audit.yml)
|
||||
[](https://codecov.io/gh/lolo32/ed448-rust)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://docs.rs/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
|
||||
46
crates/ed448-rust/src/error.rs
Normal file
46
crates/ed448-rust/src/error.rs
Normal 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,
|
||||
}
|
||||
291
crates/ed448-rust/src/lib.rs
Normal file
291
crates/ed448-rust/src/lib.rs
Normal 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))
|
||||
}
|
||||
433
crates/ed448-rust/src/point.rs
Normal file
433
crates/ed448-rust/src/point.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
255
crates/ed448-rust/src/private_key.rs
Normal file
255
crates/ed448-rust/src/private_key.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
293
crates/ed448-rust/src/public_key.rs
Normal file
293
crates/ed448-rust/src/public_key.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
215
crates/ed448-rust/tests/rfc8032.rs
Normal file
215
crates/ed448-rust/tests/rfc8032.rs
Normal 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();
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user