Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 628335a

Browse files
committedJun 23, 2023
Add zkp
1 parent a8ab7d7 commit 628335a

File tree

10 files changed

+543
-2
lines changed

10 files changed

+543
-2
lines changed
 

‎Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ members = [
33
"generic-ec",
44
"generic-ec-core",
55
"generic-ec-curves",
6+
"generic-ec-zkp",
67
]

‎generic-ec-core/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use zeroize::Zeroize;
1111
pub mod coords;
1212
pub mod hash_to_curve;
1313

14-
pub trait Curve: Debug + Copy + Eq + Ord + Hash + Default + Sync + Send {
14+
pub trait Curve: Debug + Copy + Eq + Ord + Hash + Default + Sync + Send + 'static {
15+
const CURVE_NAME: &'static str;
16+
1517
type Point: Additive
1618
+ From<CurveGenerator>
1719
+ Zero

‎generic-ec-zkp/Cargo.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "generic-ec-zkp"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
generic-ec = { path = "../generic-ec" }
10+
11+
subtle = "2.4"
12+
digest = "0.10"
13+
rand_core = "0.6"
14+
15+
[dev-dependencies]
16+
rand = "0.8"
17+
sha2 = "0.10"

‎generic-ec-zkp/src/hash_commitment.rs

+304
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
//! Hash commitment
2+
//!
3+
//! Hash commitment is a procedure that allows player $\P$ to commit some value (scalar, point, byte string, etc.),
4+
//! and reveal it later on. Player $\V$ can verify that commitment matches revealed value.
5+
//!
6+
//! ## Example
7+
//!
8+
//! 1. $\P$ commits some data (point, scalars, slices, whatever that can be represented as bytes):
9+
//! ```rust
10+
//! # use generic_ec::{Point, Scalar, Curve};
11+
//! # use generic_ec_zkp::hash_commitment::HashCommit;
12+
//! # use sha2::Sha256; use rand::rngs::OsRng;
13+
//! #
14+
//! # fn doc_fn<E: Curve>() {
15+
//! let point: Point<E> = some_point();
16+
//! let scalars: &[Scalar<E>] = some_scalars();
17+
//! let arbitrary_data: &[&[u8]] = some_arbitrary_data();
18+
//!
19+
//! let (commit, decommit) = HashCommit::<Sha256>::builder()
20+
//! .mix(point)
21+
//! .mix_many(scalars)
22+
//! .mix_many_bytes(arbitrary_data)
23+
//! .commit(&mut OsRng);
24+
//! # }
25+
//! # fn some_point<E: Curve>() -> Point<E> { unimplemented!() }
26+
//! # fn some_scalars<T>() -> T { unimplemented!() }
27+
//! # fn some_arbitrary_data<T>() -> T { unimplemented!() }
28+
//! ```
29+
//! 2. $\P$ sends `commit` to $\V$
30+
//! 3. At some point, $\P$ chooses to reveal committed data. It sends data + `decommit` to $\V$.
31+
//!
32+
//! $\V$ verifies that revealed data matches `commit`:
33+
//!
34+
//! ```rust
35+
//! # use generic_ec::{Point, Scalar, Curve};
36+
//! # use generic_ec_zkp::hash_commitment::{HashCommit, DecommitNonce, MismatchedRevealedData};
37+
//! # use sha2::Sha256;
38+
//! #
39+
//! # fn doc_fn<E: Curve>() -> Result<(), MismatchedRevealedData> {
40+
//! # let commit: HashCommit<Sha256> = unimplemented!();
41+
//! # let (point, scalars, arbitrary_data): (Point<E>, &[Scalar<E>], &[&[u8]]) = unimplemented!();
42+
//! # let decommit: DecommitNonce<Sha256> = unimplemented!();
43+
//! HashCommit::<Sha256>::builder()
44+
//! .mix(point)
45+
//! .mix_many(scalars)
46+
//! .mix_many_bytes(arbitrary_data)
47+
//! .verify(&commit, &decommit)?;
48+
//! # }
49+
//! ```
50+
//!
51+
//! ## Algorithm
52+
//! Underlying algorithm is based on hash function $\H$. To commit data, we sample a large random nonce,
53+
//! and hash it along with data. When we hash bytestrings, we prepend its length to it, in that way we
54+
//! ensure that there's only one set of inputs that can be decommitted.
55+
//!
56+
//! Roughly, algorithm is:
57+
//!
58+
//! 1. $commit(i_1, \dots, i_n) =$ \
59+
//! 1. $\mathit{nonce} \gets \\{0,1\\}^k$
60+
//! 2. $\text{return}\ \H(\dots \\| \text{u32\\_to\\_be}(\mathit{len}(i_j)) \\| i_j \\| \dots \\| \mathit{nonce}), \mathit{nonce}$
61+
//!
62+
//! 2. $decommit(commit, nonce, i_1, \dots, i_n) =$
63+
//! 1. $\text{return}\ \H(\dots \\| \text{u32\\_to\\_be}(\mathit{len}(i_j)) \\| i_j \\| \dots \\| nonce) \\? commit$
64+
65+
use digest::{generic_array::GenericArray, Digest, Output};
66+
use rand_core::RngCore;
67+
use subtle::ConstantTimeEq;
68+
69+
use generic_ec::{Curve, Point, Scalar};
70+
71+
/// Builder for commitment/verification
72+
pub struct Builder<D: Digest>(D);
73+
74+
impl<D: Digest> Builder<D> {
75+
/// Creates an instance of [`Builder`]
76+
pub fn new() -> Self {
77+
Self(D::new())
78+
}
79+
80+
/// Mixes value serialized to bytes into commitment
81+
///
82+
/// You can use this method with [`Point<E>`](Point) or [`Scalar<E>`](Scalar). Also you can
83+
/// implement [`EncodesToBytes`] for your own types.
84+
///
85+
/// ```rust
86+
/// use generic_ec::{Point, Scalar};
87+
/// use generic_ec_zkp::hash_commitment::HashCommit;
88+
/// # use sha2::Sha256;
89+
/// # use rand::rngs::OsRng;
90+
///
91+
/// # use generic_ec::Curve;
92+
/// # fn doc_fn<E: Curve>() {
93+
/// let point: Point<E> = some_point();
94+
/// let scalar: Scalar<E> = some_scalar();
95+
///
96+
/// let (commit, decommit) = HashCommit::<Sha256>::builder()
97+
/// .mix(point)
98+
/// .mix(scalar)
99+
/// .commit(&mut OsRng);
100+
/// # }
101+
/// # fn some_point<E: Curve>() -> Point<E> { unimplemented!() }
102+
/// # fn some_scalar<E: Curve>() -> Scalar<E> { unimplemented!() }
103+
/// ```
104+
///
105+
/// ## Panics
106+
/// Panics if serialized value length exceeds `u32::MAX`. On most of modern systems, it's not
107+
/// possible to allocate such large chunk of memory.
108+
pub fn mix<T>(self, encodable: T) -> Self
109+
where
110+
T: EncodesToBytes,
111+
{
112+
self.mix_bytes(encodable.to_bytes())
113+
}
114+
115+
/// Mixes values serialized to bytes into commitment
116+
///
117+
/// ```rust
118+
/// use generic_ec::Point;
119+
/// use generic_ec_zkp::hash_commitment::HashCommit;
120+
/// # use sha2::Sha256;
121+
/// # use rand::rngs::OsRng;
122+
///
123+
/// # use generic_ec::Curve;
124+
/// # fn doc_fn<E: Curve>() {
125+
/// let points: Vec<Point<E>> = some_points();
126+
///
127+
/// let (commit, decommit) = HashCommit::<Sha256>::builder()
128+
/// .mix_many(&points)
129+
/// .commit(&mut OsRng);
130+
/// # }
131+
/// # fn some_points<E: Curve>() -> Vec<Point<E>> { unimplemented!() }
132+
/// ```
133+
///
134+
/// ## Panics
135+
/// Panics if number of values exceeds `u32::MAX` or if any of serialized values length exceeds
136+
/// `u32::MAX`.
137+
pub fn mix_many<T>(mut self, encodables: &[T]) -> Self
138+
where
139+
T: EncodesToBytes,
140+
{
141+
let len: u32 = encodables
142+
.len()
143+
.try_into()
144+
.expect("encodables len exceeds u32::MAX");
145+
self = self.mix_bytes(len.to_be_bytes());
146+
for encodable in encodables {
147+
self = self.mix(encodable);
148+
}
149+
self
150+
}
151+
152+
/// Mixes bytes into commitment
153+
///
154+
/// ```rust
155+
/// use generic_ec_zkp::hash_commitment::HashCommit;
156+
/// # use sha2::Sha256;
157+
/// # use rand::rngs::OsRng;
158+
///
159+
/// # use generic_ec::Curve;
160+
/// # fn doc_fn<E: Curve>() {
161+
/// let (commit, decommit) = HashCommit::<Sha256>::builder()
162+
/// .mix_bytes(b"some message")
163+
/// .commit(&mut OsRng);
164+
/// # }
165+
/// ```
166+
///
167+
/// ## Panics
168+
/// Panics if `data` length exceeds `u32::MAX`. On most of modern systems, it's not possible
169+
/// to allocate such large chuck of memory.
170+
pub fn mix_bytes(self, data: impl AsRef<[u8]>) -> Self {
171+
let data_len: u32 = data
172+
.as_ref()
173+
.len()
174+
.try_into()
175+
.expect("data len exceeds u32::MAX");
176+
Self(
177+
self.0
178+
.chain_update(data_len.to_be_bytes())
179+
.chain_update(data),
180+
)
181+
}
182+
183+
/// Mixes list of byte strings into commitment
184+
///
185+
/// ```rust
186+
/// use generic_ec_zkp::hash_commitment::HashCommit;
187+
/// # use sha2::Sha256;
188+
/// # use rand::rngs::OsRng;
189+
///
190+
/// # use generic_ec::Curve;
191+
/// # fn doc_fn<E: Curve>() {
192+
/// let (commit, decommit) = HashCommit::<Sha256>::builder()
193+
/// .mix_many_bytes(&[b"some message", b"another message"])
194+
/// .commit(&mut OsRng);
195+
/// # }
196+
/// ```
197+
///
198+
/// ## Panics
199+
/// Panics if `list` length exceeds `u32::MAX` or if length of any item of the list exceeds
200+
/// `u32::MAX`.
201+
pub fn mix_many_bytes(mut self, list: &[&[u8]]) -> Self {
202+
let list_len: u32 = list.len().try_into().expect("list len exceeds u32::MAX");
203+
self = self.mix_bytes(list_len.to_be_bytes());
204+
for item in list {
205+
self = self.mix_bytes(item)
206+
}
207+
self
208+
}
209+
210+
/// Performs commitment
211+
///
212+
/// Decommitment nonce is generated from provided randomness source
213+
pub fn commit<R: RngCore>(self, rng: &mut R) -> (HashCommit<D>, DecommitNonce<D>) {
214+
let mut nonce = DecommitNonce::<D>::default();
215+
rng.fill_bytes(&mut nonce.nonce);
216+
(self.commit_with_fixed_nonce(&nonce), nonce)
217+
}
218+
219+
/// Performs commitment with specified decommitment nonce
220+
pub fn commit_with_fixed_nonce(mut self, nonce: &DecommitNonce<D>) -> HashCommit<D> {
221+
self = self.mix_bytes(&nonce.nonce);
222+
let resulting_hash = self.0.finalize();
223+
HashCommit(resulting_hash)
224+
}
225+
226+
/// Verifies that provided data matches commitment and decommitment
227+
pub fn verify(
228+
self,
229+
commit: &HashCommit<D>,
230+
nonce: &DecommitNonce<D>,
231+
) -> Result<(), MismatchedRevealedData> {
232+
let should_be = self.commit_with_fixed_nonce(nonce);
233+
if commit.0.ct_eq(&should_be.0).into() {
234+
Ok(())
235+
} else {
236+
Err(MismatchedRevealedData)
237+
}
238+
}
239+
}
240+
241+
/// Committed value
242+
#[derive(Clone)]
243+
pub struct HashCommit<D: Digest>(pub Output<D>);
244+
245+
impl<D: Digest> HashCommit<D> {
246+
pub fn builder() -> Builder<D> {
247+
Builder::new()
248+
}
249+
}
250+
251+
/// Random nonce that was used to "blind" commitment
252+
#[derive(Clone)]
253+
pub struct DecommitNonce<D: Digest> {
254+
pub nonce: GenericArray<u8, D::OutputSize>,
255+
}
256+
257+
impl<D: Digest> Default for DecommitNonce<D> {
258+
fn default() -> Self {
259+
Self {
260+
nonce: Default::default(),
261+
}
262+
}
263+
}
264+
265+
/// Infallibly encodable to bytes
266+
///
267+
/// Used in [`hash_commitment::Builder`](Builder) methods [`mix`](Builder::mix) and [`mix_many`](Builder::mix_many) to convert given value to bytes.
268+
pub trait EncodesToBytes {
269+
/// Value byte representation
270+
type Bytes: AsRef<[u8]>;
271+
/// Encodes value to bytes
272+
fn to_bytes(&self) -> Self::Bytes;
273+
}
274+
275+
impl<T: EncodesToBytes> EncodesToBytes for &T {
276+
type Bytes = T::Bytes;
277+
fn to_bytes(&self) -> Self::Bytes {
278+
<T as EncodesToBytes>::to_bytes(*self)
279+
}
280+
}
281+
282+
impl<E: Curve> EncodesToBytes for Point<E> {
283+
type Bytes = generic_ec::EncodedPoint<E>;
284+
fn to_bytes(&self) -> Self::Bytes {
285+
self.to_bytes(true)
286+
}
287+
}
288+
289+
impl<E: Curve> EncodesToBytes for Scalar<E> {
290+
type Bytes = generic_ec::EncodedScalar<E>;
291+
fn to_bytes(&self) -> Self::Bytes {
292+
self.to_bytes()
293+
}
294+
}
295+
296+
impl EncodesToBytes for u16 {
297+
type Bytes = [u8; 2];
298+
fn to_bytes(&self) -> Self::Bytes {
299+
self.to_be_bytes()
300+
}
301+
}
302+
303+
/// Error indicating that revealed data doesn't match commitment
304+
pub struct MismatchedRevealedData;

‎generic-ec-zkp/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod hash_commitment;
2+
pub mod schnorr_pok;

‎generic-ec-zkp/src/schnorr_pok.rs

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! Schnorr Proof of Knowledge $\Pi^\text{sch}$
2+
//!
3+
//! Schnorr Proof of Knowledge is an interactive $\Sigma$ protocol that lets prover $\P$ convince
4+
//! verifier $\V$ that it knows secret $x$ such as $X = x \cdot G$.
5+
//!
6+
//! ## Example
7+
//!
8+
//! 0. $\P$ knows a secret $x$ and wants to prove its knowledge.
9+
//! ```rust
10+
//! # use generic_ec::{Curve, SecretScalar, Point};
11+
//! # use rand::rngs::OsRng;
12+
//! # fn doc_fn<E: Curve>() {
13+
//! let x = SecretScalar::<E>::random(&mut OsRng);
14+
//! let X = Point::generator() * &x; // assumed to be known by verifier
15+
//! # }
16+
//! ```
17+
//! 1. $\P$ generates and commits an ephemeral secret. Committed secret is sent to $\V$.
18+
//! ```rust
19+
//! # use generic_ec::Curve;
20+
//! # use generic_ec_zkp::schnorr_pok::*;
21+
//! # use rand::rngs::OsRng;
22+
//! # fn doc_fn<E: Curve>() {
23+
//! let (eph_secret, commit) = prover_commits_ephemeral_secret::<E, _>(&mut OsRng);
24+
//! send(commit);
25+
//! # }
26+
//! # fn send<T>(_: T) {}
27+
//! ```
28+
//! 2. $\V$ receives commitment, and responds with challenge.
29+
//! ```rust
30+
//! # use generic_ec::Curve;
31+
//! # use generic_ec_zkp::schnorr_pok::*;
32+
//! # use rand::rngs::OsRng;
33+
//! # fn doc_fn<E: Curve>() {
34+
//! let commit: Commit<E> = receive();
35+
//! let challenge = Challenge::<E>::generate(&mut OsRng);
36+
//! send(challenge);
37+
//! # }
38+
//! # fn send<T>(_: T) {}
39+
//! # fn receive<T>() -> T { unimplemented!() }
40+
//! ```
41+
//! 3. $\P$ receives a challenge and responds with proof.
42+
//! ```rust
43+
//! # use generic_ec::{Curve, SecretScalar};
44+
//! # use generic_ec_zkp::schnorr_pok::*;
45+
//! # use rand::rngs::OsRng;
46+
//! # fn doc_fn<E: Curve>() {
47+
//! # let (eph_secret, x): (ProverSecret<E>, SecretScalar<E>) = recall();
48+
//! let challenge: Challenge<E> = receive();
49+
//! let proof = prove(&eph_secret, &challenge, &x);
50+
//! send(proof);
51+
//! # }
52+
//! # fn send<T>(_: T) {}
53+
//! # fn receive<T>() -> T { unimplemented!() }
54+
//! # fn recall<T>() -> T { unimplemented!() }
55+
//! ```
56+
//! 4. $\V$ receives a proof and verifies it.
57+
//! ```rust
58+
//! # use generic_ec::{Curve, Point};
59+
//! # use generic_ec_zkp::schnorr_pok::*;
60+
//! # use rand::rngs::OsRng;
61+
//! # fn doc_fn<E: Curve>() {
62+
//! # let (commit, challenge, X): (Commit<E>, Challenge<E>, Point<E>) = recall();
63+
//! let proof: Proof<E> = receive();
64+
//! proof.verify(&commit, &challenge, &X);
65+
//! # }
66+
//! # fn send<T>(_: T) {}
67+
//! # fn receive<T>() -> T { unimplemented!() }
68+
//! # fn recall<T>() -> T { unimplemented!() }
69+
//! ```
70+
//!
71+
//! ## Algorithm
72+
//!
73+
//! Schnor PoK is defined as:
74+
//!
75+
//! * Prove
76+
//! 1. Prover samples $\alpha \gets \Z_q$ and sends $A = \alpha \cdot G$ to verifier
77+
//! 2. Verifier replies with $e \gets \Z_q$
78+
//! 3. Prover sends $z = \alpha + ex$
79+
//! * Verification \
80+
//! Verifier checks that $z \cdot G \\? A + e \cdot X$
81+
82+
use generic_ec::{Curve, Point, Scalar, SecretScalar};
83+
use rand_core::{CryptoRng, RngCore};
84+
use subtle::ConstantTimeEq;
85+
86+
/// Committed prover ephemeral secret
87+
#[derive(Clone)]
88+
pub struct Commit<E: Curve>(pub Point<E>);
89+
90+
/// Prover ephemeral secret
91+
pub struct ProverSecret<E: Curve> {
92+
pub nonce: SecretScalar<E>,
93+
}
94+
95+
/// Challenge generated by verifier
96+
pub struct Challenge<E: Curve> {
97+
pub nonce: Scalar<E>,
98+
}
99+
100+
impl<E: Curve> Challenge<E> {
101+
/// Generates a random challenge
102+
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
103+
Self {
104+
nonce: Scalar::random(rng),
105+
}
106+
}
107+
}
108+
109+
/// The proof that can convince $\V$ that $\P$ knows secret $x$
110+
#[derive(Clone)]
111+
pub struct Proof<E: Curve>(pub Scalar<E>);
112+
113+
impl<E: Curve> Proof<E> {
114+
/// Verifies that prover knows secret $x$ such as $X = x \cdot G$
115+
#[allow(non_snake_case)]
116+
pub fn verify(
117+
&self,
118+
commit: &Commit<E>,
119+
challenge: &Challenge<E>,
120+
X: &Point<E>,
121+
) -> Result<(), InvalidProof> {
122+
let lhs = Point::generator() * self.0;
123+
let rhs = commit.0 + challenge.nonce * X;
124+
if lhs.ct_eq(&rhs).into() {
125+
Ok(())
126+
} else {
127+
Err(InvalidProof)
128+
}
129+
}
130+
}
131+
132+
/// Generates and commits prover ephemeral secret
133+
pub fn prover_commits_ephemeral_secret<E: Curve, R: RngCore + CryptoRng>(
134+
rng: &mut R,
135+
) -> (ProverSecret<E>, Commit<E>) {
136+
let secret = SecretScalar::random(rng);
137+
let public = Point::generator() * &secret;
138+
(ProverSecret { nonce: secret }, Commit(public))
139+
}
140+
141+
/// Proves knowledge of `secret`
142+
pub fn prove<E: Curve>(
143+
committed_secret: &ProverSecret<E>,
144+
challenge: &Challenge<E>,
145+
secret: impl AsRef<Scalar<E>>,
146+
) -> Proof<E> {
147+
Proof(&committed_secret.nonce + challenge.nonce * secret.as_ref())
148+
}
149+
150+
/// Invalid proof error
151+
pub struct InvalidProof;

‎generic-ec/src/arithmetic.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use core::ops::{Add, Mul, Neg, Sub};
1+
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub};
22

33
use crate::{Curve, Generator, NonZero, Point, Scalar, SecretScalar};
44

@@ -298,6 +298,21 @@ macro_rules! impl_unary_ops {
298298
)*};
299299
}
300300

301+
macro_rules! impl_op_assign {
302+
($($ty:ty, $trait:ident, $rhs:ty, $fn:ident, $op:tt),+,) => {$(
303+
impl<E: Curve> $trait<$rhs> for $ty {
304+
fn $fn(&mut self, rhs: $rhs) {
305+
*self = *self $op rhs;
306+
}
307+
}
308+
impl<E: Curve> $trait<&$rhs> for $ty {
309+
fn $fn(&mut self, rhs: &$rhs) {
310+
*self = *self $op rhs;
311+
}
312+
}
313+
)+};
314+
}
315+
301316
// Point <> Point, Point <> Scalar, Scalar <> Scalar arithmetic ops
302317
impl_binary_ops! {
303318
Add (Point<E>, add, Point<E> = Point<E>) laws::sum_of_points_is_valid_point,
@@ -374,6 +389,13 @@ impl_unary_ops! {
374389
Neg (neg NonZero<Scalar<E>>) scalar::neg_nonzero,
375390
}
376391

392+
impl_op_assign! {
393+
Point<E>, AddAssign, Point<E>, add_assign, +,
394+
Point<E>, MulAssign, Scalar<E>, mul_assign, *,
395+
Scalar<E>, AddAssign, Scalar<E>, add_assign, +,
396+
Scalar<E>, MulAssign, Scalar<E>, mul_assign, *,
397+
}
398+
377399
#[cfg(test)]
378400
#[allow(dead_code)]
379401
fn ensure_ops_implemented<E: Curve>(

‎generic-ec/src/point/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::iter::Sum;
2+
13
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
24

35
use crate::{
@@ -128,3 +130,15 @@ impl<E: Curve> AsRef<Point<E>> for Point<E> {
128130
self
129131
}
130132
}
133+
134+
impl<E: Curve> Sum for Point<E> {
135+
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
136+
iter.fold(Point::zero(), |acc, p| acc + p)
137+
}
138+
}
139+
140+
impl<'a, E: Curve> Sum<&'a Point<E>> for Point<E> {
141+
fn sum<I: Iterator<Item = &'a Point<E>>>(iter: I) -> Self {
142+
iter.fold(Point::zero(), |acc, p| acc + p)
143+
}
144+
}

‎generic-ec/src/scalar/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,27 @@ impl<E: Curve> AsRef<Scalar<E>> for Scalar<E> {
140140
self
141141
}
142142
}
143+
144+
impl<E: Curve> iter::Sum for Scalar<E> {
145+
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
146+
iter.fold(Scalar::zero(), |acc, x| acc + x)
147+
}
148+
}
149+
150+
impl<'a, E: Curve> iter::Sum<&'a Scalar<E>> for Scalar<E> {
151+
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
152+
iter.fold(Scalar::zero(), |acc, x| acc + x)
153+
}
154+
}
155+
156+
impl<E: Curve> iter::Product for Scalar<E> {
157+
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
158+
iter.fold(Scalar::one(), |acc, x| acc * x)
159+
}
160+
}
161+
162+
impl<'a, E: Curve> iter::Product<&'a Scalar<E>> for Scalar<E> {
163+
fn product<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
164+
iter.fold(Scalar::one(), |acc, x| acc * x)
165+
}
166+
}

‎katex-header.html

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
"\\G": "\\mathbb{G}",
3434
"\\T": "\\mathbb{T}",
3535
"\\O": "\\mathcal{O}",
36+
"\\P": "\\mathcal{P}",
37+
"\\V": "\\mathcal{V}",
38+
"\\H": "\\mathcal{H}",
39+
"\\?": "\\stackrel{?}{=}",
3640
},
3741
});
3842
});

0 commit comments

Comments
 (0)
Please sign in to comment.