Skip to content

Commit

Permalink
Remove schnorrkel, add generalized schnorr
Browse files Browse the repository at this point in the history
  • Loading branch information
kayabaNerve committed Apr 27, 2024
1 parent b6adef2 commit 9643b6d
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 267 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/crypto-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
-p dleq \
-p dkg \
-p modular-frost \
-p frost-schnorrkel
-p generalized-schnorr
52 changes: 11 additions & 41 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ members = [
"crypto/dleq",
"crypto/dkg",
"crypto/frost",
"crypto/schnorrkel",

"crypto/generalized-schnorr",

"tests/no-std",
]
Expand Down
42 changes: 42 additions & 0 deletions crypto/generalized-schnorr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "generalized-schnorr"
version = "0.1.0"
description = "Generalized Schnorr Protocols"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorr"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["schnorr", "ff", "group"]
edition = "2021"
rust-version = "1.74"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[lints]
workspace = true

[dependencies]
std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-features = false }

rand_core = { version = "0.6", default-features = false }

zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }

transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false }

ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["alloc"] }
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["batch"] }

[dev-dependencies]
hex = "0.4"

rand_core = { version = "0.6", features = ["std"] }

transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"] }

ciphersuite = { path = "../ciphersuite", features = ["ed25519"] }

[features]
std = ["std-shims/std", "rand_core/std", "zeroize/std", "ciphersuite/std", "multiexp/std"]
default = ["std"]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Luke Parker
Copyright (c) 2024 Luke Parker

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
19 changes: 19 additions & 0 deletions crypto/generalized-schnorr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Schnorr Signatures

A challenge (and therefore HRAm) agnostic Schnorr signature library. This is
intended to be used as a primitive by a variety of crates relying on Schnorr
signatures, voiding the need to constantly define a Schnorr signature struct
with associated functions.

This library provides signatures of the `R, s` form. Batch verification is
supported via the multiexp crate. Half-aggregation, as defined in
<https://eprint.iacr.org/2021/350>, is also supported.

This library was
[audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf),
culminating in commit
[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
Any subsequent changes have not undergone auditing.

This library is usable under no_std, via alloc, when the default features are
disabled.
183 changes: 183 additions & 0 deletions crypto/generalized-schnorr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(non_snake_case)]

use core::ops::Deref;
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
use std_shims::io::{self, Read, Write};

use rand_core::{RngCore, CryptoRng};

use zeroize::{Zeroize, Zeroizing};

use transcript::Transcript;

use ciphersuite::{
group::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
};
use multiexp::{multiexp_vartime, BatchVerifier};

#[cfg(test)]
mod tests;

/// A Generalized Schnorr Proof.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct GeneralizedSchnorr<
C: Ciphersuite,
const OUTPUTS: usize,
const SCALARS: usize,
const SCALARS_PLUS_TWO: usize,
> {
pub R: [C::G; OUTPUTS],
pub s: [C::F; SCALARS],
}

impl<C: Ciphersuite, const OUTPUTS: usize, const SCALARS: usize, const SCALARS_PLUS_TWO: usize>
GeneralizedSchnorr<C, OUTPUTS, SCALARS, SCALARS_PLUS_TWO>
{
/// Read a GeneralizedSchnorr from something implementing Read.
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut R = [C::G::identity(); OUTPUTS];
for R in &mut R {
*R = C::read_G(reader)?;
}
let mut s = [C::F::ZERO; SCALARS];
for s in &mut s {
*s = C::read_F(reader)?;
}
Ok(Self { R, s })
}

/// Write a GeneralizedSchnorr to something implementing Read.
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
for R in self.R {
writer.write_all(R.to_bytes().as_ref())?;
}
for s in self.s {
writer.write_all(s.to_repr().as_ref())?;
}
Ok(())
}

fn challenge(
transcript: &mut impl Transcript,
matrix: [[C::G; SCALARS]; OUTPUTS],
outputs: [C::G; OUTPUTS],
nonces: [C::G; OUTPUTS],
) -> C::F {
transcript.domain_separate(b"generalized_schnorr");
transcript.append_message(
b"scalars",
u32::try_from(SCALARS).expect("passed 2**32 scalars").to_le_bytes(),
);
transcript.append_message(
b"outputs",
u32::try_from(OUTPUTS).expect("passed 2**32 outputs").to_le_bytes(),
);
for row in matrix {
for generator in row {
transcript.append_message(b"generator", generator.to_bytes());
}
}
for output in outputs {
transcript.append_message(b"output", output.to_bytes());
}
for nonce in nonces {
transcript.append_message(b"nonce", nonce.to_bytes());
}
C::hash_to_F(b"generalized_schnorr", transcript.challenge(b"c").as_ref())
}

/// Serialize a GeneralizedSchnorr, returning a `Vec<u8>`.
#[cfg(feature = "std")]
pub fn serialize(&self) -> Vec<u8> {
let mut buf = vec![];
self.write(&mut buf).unwrap();
buf
}

/// Prove a Generalized Schnorr statement.
///
/// Returns the outputs and the proof for them.
pub fn prove(
rng: &mut (impl RngCore + CryptoRng),
transcript: &mut impl Transcript,
matrix: [[C::G; SCALARS]; OUTPUTS],
scalars: [&Zeroizing<C::F>; SCALARS],
) -> ([C::G; OUTPUTS], Self) {
let outputs: [C::G; OUTPUTS] = core::array::from_fn(|i| {
matrix[i].iter().zip(scalars.iter()).map(|(generator, scalar)| *generator * ***scalar).sum()
});

let nonces: [Zeroizing<C::F>; SCALARS] =
core::array::from_fn(|_| Zeroizing::new(C::F::random(&mut *rng)));
let R = core::array::from_fn(|i| {
matrix[i].iter().zip(nonces.iter()).map(|(generator, nonce)| *generator * **nonce).sum()
});

let c = Self::challenge(transcript, matrix, outputs, R);
(outputs, Self { R, s: core::array::from_fn(|i| c * scalars[i].deref() + nonces[i].deref()) })
}

/// Return the series of pairs whose products sum to zero for a valid signature.
///
/// This is intended to be used with a multiexp for efficient batch verification.
fn batch_statements(
&self,
transcript: &mut impl Transcript,
matrix: [[C::G; SCALARS]; OUTPUTS],
outputs: [C::G; OUTPUTS],
) -> [[(C::F, C::G); SCALARS_PLUS_TWO]; OUTPUTS] {
assert_eq!(SCALARS_PLUS_TWO, SCALARS + 2);
let c = Self::challenge(transcript, matrix, outputs, self.R);
core::array::from_fn(|i| {
core::array::from_fn(|j| {
if j == SCALARS {
(-C::F::ONE, self.R[i])
} else if j == (SCALARS + 1) {
(-c, outputs[i])
} else {
(self.s[j], matrix[i][j])
}
})
})
}

/// Verify a Generalized Schnorr proof.
#[must_use]
pub fn verify(
&self,
transcript: &mut impl Transcript,
matrix: [[C::G; SCALARS]; OUTPUTS],
outputs: [C::G; OUTPUTS],
) -> bool {
for statements in self.batch_statements(transcript, matrix, outputs) {
if !bool::from(multiexp_vartime(statements.as_slice()).is_identity()) {
return false;
}
}
true
}

/// Queue a proof for batch verification.
pub fn batch_verify<R: RngCore + CryptoRng, I: Copy + Zeroize>(
&self,
rng: &mut R,
transcript: &mut impl Transcript,
batch: &mut BatchVerifier<I, C::G>,
id: I,
matrix: [[C::G; SCALARS]; OUTPUTS],
outputs: [C::G; OUTPUTS],
) {
for statements in self.batch_statements(transcript, matrix, outputs) {
batch.queue(rng, id, statements);
}
}
}
Loading

0 comments on commit 9643b6d

Please sign in to comment.