Skip to content

Commit 79e1e53

Browse files
Add generic eddsa impl using sha256 in place of sha512
1 parent 197bb73 commit 79e1e53

File tree

12 files changed

+219
-1
lines changed

12 files changed

+219
-1
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ openvm-pairing-transpiler = { path = "extensions/pairing/transpiler", default-fe
169169
openvm-pairing-guest = { path = "extensions/pairing/guest", default-features = false }
170170
openvm-verify-stark = { path = "guest-libs/verify_stark", default-features = false }
171171

172+
# Guest Libraries
173+
openvm-sha2 = { path = "guest-libs/sha2", default-features = false }
174+
172175
# Benchmarking
173176
openvm-benchmarks-utils = { path = "benchmarks/utils", default-features = false }
174177

@@ -247,6 +250,7 @@ num-integer = { version = "0.1.46", default-features = false }
247250
num-traits = { version = "0.2.19", default-features = false }
248251
ff = { version = "0.13.1", default-features = false }
249252
sha2 = { version = "0.10", default-features = false }
253+
signature = { version = "2.2.0", default-features = false }
250254

251255
# For local development. Add to your `.cargo/config.toml`
252256
# [patch."https://github.com/Plonky3/Plonky3.git"]

extensions/ecc/circuit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ openvm-algebra-circuit = { workspace = true }
2020
openvm-rv32-adapters = { workspace = true }
2121
openvm-ecc-transpiler = { workspace = true }
2222
openvm-ecc-guest = { workspace = true, features = ["ed25519"] }
23+
openvm-sha256-circuit = { workspace = true }
2324

2425
num-bigint = { workspace = true }
2526
num-integer = { workspace = true }

extensions/ecc/circuit/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use openvm_algebra_circuit::*;
22
use openvm_circuit::arch::{InitFileGenerator, SystemConfig};
33
use openvm_circuit_derive::VmConfig;
44
use openvm_rv32im_circuit::*;
5+
use openvm_sha256_circuit::{Sha256, Sha256Executor, Sha256Periphery};
56
use openvm_stark_backend::p3_field::PrimeField32;
67
use serde::{Deserialize, Serialize};
78

@@ -21,6 +22,8 @@ pub struct Rv32EccConfig {
2122
pub modular: ModularExtension,
2223
#[extension]
2324
pub ecc: EccExtension,
25+
#[extension]
26+
pub sha256: Sha256,
2427
}
2528

2629
impl Rv32EccConfig {
@@ -44,6 +47,7 @@ impl Rv32EccConfig {
4447
io: Default::default(),
4548
modular: ModularExtension::new(primes),
4649
ecc: EccExtension::new(sw_curves, te_curves),
50+
sha256: Default::default(),
4751
}
4852
}
4953
}

extensions/ecc/guest/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ openvm-ecc-te-macros = { workspace = true }
2222
once_cell = { workspace = true, features = ["race", "alloc"] }
2323
num-bigint = { workspace = true }
2424
hex-literal = { workspace = true }
25+
signature = { workspace = true }
26+
openvm-sha2 = { workspace = true }
2527

2628
# Used for `halo2curves` feature
2729
halo2curves-axiom = { workspace = true, optional = true }

extensions/ecc/guest/src/eddsa.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use openvm_algebra_guest::{IntMod, Reduce};
2+
use openvm_sha2::sha256;
3+
4+
use crate::{edwards::TwistedEdwardsPoint, CyclicGroup, FromCompressed, IntrinsicCurve};
5+
6+
extern crate alloc;
7+
use alloc::vec::Vec;
8+
9+
type Coordinate<C> = <<C as IntrinsicCurve>::Point as TwistedEdwardsPoint>::Coordinate;
10+
type Scalar<C> = <C as IntrinsicCurve>::Scalar;
11+
type Point<C> = <C as IntrinsicCurve>::Point;
12+
13+
#[repr(C)]
14+
#[derive(Clone)]
15+
pub struct VerifyingKey<C: IntrinsicCurve> {
16+
/// Affine point
17+
point: Point<C>,
18+
}
19+
20+
impl<C: IntrinsicCurve> VerifyingKey<C>
21+
where
22+
Point<C>: TwistedEdwardsPoint + FromCompressed<Coordinate<C>> + CyclicGroup,
23+
Coordinate<C>: IntMod,
24+
C::Scalar: IntMod + Reduce,
25+
{
26+
/// Assumes the point is encoded as in https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
27+
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
28+
if bytes.len() != Coordinate::<C>::NUM_LIMBS {
29+
return None;
30+
}
31+
Some(Self {
32+
point: decode_point::<C>(bytes)?,
33+
})
34+
}
35+
36+
pub fn verify(&self, message: &[u8], sig: &[u8]) -> bool {
37+
let Some(sig) = Signature::<C>::from_bytes(sig) else {
38+
return false;
39+
};
40+
41+
// TODO: replace with sha512
42+
let prehash = sha256(message);
43+
44+
// h = SHA512(dom2(F, C) || R || A || PH(M))
45+
// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.7
46+
let mut sha_input = Vec::new();
47+
48+
// dom2(F, C) domain separator
49+
// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-2
50+
// See definition of dom2 in the RFC. Note that the RFC refers to the prehash
51+
// version of Ed25519 as Ed25519ph, and the non-prehash version as Ed25519.
52+
sha_input.extend_from_slice(b"SigEd25519 no Ed25519 collisions");
53+
sha_input.extend_from_slice(&[1]); // phflag = 1
54+
55+
// The RFC specifies optional "context" bytes that are shared between a signer and verifier.
56+
// We don't use any context bytes.
57+
// See: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1
58+
sha_input.extend_from_slice(&[0]); // context len = 0
59+
60+
sha_input.extend_from_slice(&encode_point::<C>(&sig.r));
61+
sha_input.extend_from_slice(&encode_point::<C>(&self.point));
62+
sha_input.extend_from_slice(&prehash);
63+
64+
// TOOD: replace with sha512
65+
let h = sha256(&sha_input);
66+
67+
let h = C::Scalar::reduce_le_bytes(&h);
68+
69+
// assert s * B = R + h * A
70+
// <=> R + h * A - s * B = 0
71+
// <=> [1, h, s] * [R, A, -B] = 0
72+
let res = C::msm(
73+
&[C::Scalar::ONE, h, sig.s],
74+
&[
75+
sig.r,
76+
self.point.clone(),
77+
<Point<C> as CyclicGroup>::NEG_GENERATOR,
78+
],
79+
);
80+
res == <Point<C> as TwistedEdwardsPoint>::IDENTITY
81+
}
82+
}
83+
84+
// Internal struct used for decoding the signature from bytes
85+
struct Signature<C: IntrinsicCurve> {
86+
r: C::Point,
87+
s: C::Scalar,
88+
}
89+
90+
impl<C: IntrinsicCurve> Signature<C>
91+
where
92+
C::Point: TwistedEdwardsPoint + FromCompressed<Coordinate<C>>,
93+
Coordinate<C>: IntMod,
94+
C::Scalar: IntMod,
95+
{
96+
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
97+
if bytes.len() != Coordinate::<C>::NUM_LIMBS + Scalar::<C>::NUM_LIMBS {
98+
return None;
99+
}
100+
// from_le_bytes checks that s is reduced
101+
let s = Scalar::<C>::from_le_bytes(&bytes[Coordinate::<C>::NUM_LIMBS..])?;
102+
Some(Self {
103+
r: decode_point::<C>(&bytes[..Coordinate::<C>::NUM_LIMBS])?,
104+
s,
105+
})
106+
}
107+
}
108+
109+
/// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.3
110+
/// We require that the most significant bit in the little-endian encoding of
111+
/// elements of the coordinate field is always 0, because we pack the parity
112+
/// of the x-coordinate there.
113+
fn decode_point<C: IntrinsicCurve>(bytes: &[u8]) -> Option<Point<C>>
114+
where
115+
Point<C>: TwistedEdwardsPoint + FromCompressed<Coordinate<C>>,
116+
Coordinate<C>: IntMod,
117+
{
118+
if bytes.len() != Coordinate::<C>::NUM_LIMBS {
119+
return None;
120+
}
121+
let mut y_bytes = bytes.to_vec();
122+
// most significant bit stores the parity of the x-coordinate
123+
let rec_id = y_bytes[Coordinate::<C>::NUM_LIMBS - 1] & 0b10000000;
124+
y_bytes[Coordinate::<C>::NUM_LIMBS - 1] &= 0b01111111;
125+
// from_le_bytes checks that y is reduced
126+
let y = Coordinate::<C>::from_le_bytes(&y_bytes)?;
127+
Point::<C>::decompress(y, &rec_id)
128+
}
129+
130+
/// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2
131+
/// We require that the most significant bit in the little-endian encoding of
132+
/// elements of the coordinate field is always 0, because we pack the parity
133+
/// of the x-coordinate there.
134+
fn encode_point<C: IntrinsicCurve>(p: &Point<C>) -> Vec<u8>
135+
where
136+
Point<C>: TwistedEdwardsPoint,
137+
Coordinate<C>: IntMod,
138+
{
139+
let mut y_bytes = p.y().as_le_bytes().to_vec();
140+
if p.x().as_le_bytes()[0] & 1u8 == 1 {
141+
y_bytes[Coordinate::<C>::NUM_LIMBS - 1] |= 0b10000000;
142+
}
143+
y_bytes
144+
}

extensions/ecc/guest/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub use msm::*;
1818

1919
/// Optimized ECDSA implementation with the same functional interface as the `ecdsa` crate
2020
pub mod ecdsa;
21+
/// Optimized EDDSA implementation
22+
pub mod eddsa;
2123
/// Edwards curve traits
2224
pub mod edwards;
2325
/// Weierstrass curve traits

extensions/ecc/tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ openvm-stark-sdk.workspace = true
1212
openvm-circuit = { workspace = true, features = ["test-utils"] }
1313
openvm-transpiler.workspace = true
1414
openvm-algebra-transpiler.workspace = true
15+
openvm-sha256-transpiler.workspace = true
1516
openvm-ecc-transpiler.workspace = true
1617
openvm-ecc-circuit.workspace = true
1718
openvm-rv32im-transpiler.workspace = true

extensions/ecc/tests/programs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ openvm = { path = "../../../../crates/toolchain/openvm" }
99
openvm-platform = { path = "../../../../crates/toolchain/platform" }
1010
openvm-custom-insn = { path = "../../../../crates/toolchain/custom_insn", default-features = false }
1111

12-
openvm-ecc-guest = { path = "../../guest", default-features = false }
12+
openvm-ecc-guest = { path = "../../guest", default-features = false, features = ["ed25519"] }
1313
openvm-ecc-sw-macros = { path = "../../../../extensions/ecc/sw-macros", default-features = false }
1414
openvm-ecc-te-macros = { path = "../../../../extensions/ecc/te-macros", default-features = false }
1515
openvm-algebra-guest = { path = "../../../algebra/guest", default-features = false }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![cfg_attr(not(feature = "std"), no_main)]
2+
#![cfg_attr(not(feature = "std"), no_std)]
3+
4+
use hex_literal::hex;
5+
use openvm_ecc_guest::{ed25519::Ed25519Point, eddsa::VerifyingKey};
6+
7+
openvm::entry!(main);
8+
9+
openvm::init!("openvm_init_eddsa_ed25519.rs");
10+
11+
// Ref: https://docs.rs/k256/latest/k256/ecdsa/index.html
12+
pub fn main() {
13+
// Test data taken from the RFC: https://datatracker.ietf.org/doc/html/rfc8032#section-7.3
14+
// Unfortuantely, the RFC only provides one test for Ed25519ph
15+
// TODO: find/generate more tests
16+
let msg = b"abc";
17+
18+
let signature = hex!(
19+
"98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406"
20+
);
21+
22+
let vk = VerifyingKey::<Ed25519Point>::from_bytes(&hex!(
23+
"ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"
24+
))
25+
.unwrap();
26+
27+
assert!(vk.verify(msg, &signature));
28+
}

0 commit comments

Comments
 (0)