Skip to content

Commit 65a25dd

Browse files
Add support for ed25519 and ed25519ph
1 parent 23d43be commit 65a25dd

File tree

5 files changed

+224
-48
lines changed

5 files changed

+224
-48
lines changed

extensions/ecc/guest/src/eddsa.rs

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
use openvm_algebra_guest::{IntMod, Reduce};
2-
use openvm_sha2::sha512;
3-
4-
use crate::{edwards::TwistedEdwardsPoint, CyclicGroup, FromCompressed, IntrinsicCurve};
1+
// Implementation of the EdDSA signature verification algorithm.
2+
// The code is generic over the twisted Edwards curve, but currently only instantiated with Ed25519.
3+
// The implementation is based on the RFC: https://datatracker.ietf.org/doc/html/rfc8032
4+
// We support both the prehash variant (Ed25519ph) and the non-prehash variant (Ed25519).
5+
// Note: our implementation is not intended to be safe against timing attacks.
56

67
extern crate alloc;
78
use alloc::vec::Vec;
89

10+
use openvm_sha2::sha512;
11+
12+
use crate::{
13+
algebra::{IntMod, Reduce},
14+
edwards::TwistedEdwardsPoint,
15+
CyclicGroup, FromCompressed, IntrinsicCurve,
16+
};
17+
918
type Coordinate<C> = <<C as IntrinsicCurve>::Point as TwistedEdwardsPoint>::Coordinate;
1019
type Scalar<C> = <C as IntrinsicCurve>::Scalar;
1120
type Point<C> = <C as IntrinsicCurve>::Point;
@@ -17,6 +26,13 @@ pub struct VerifyingKey<C: IntrinsicCurve> {
1726
point: Point<C>,
1827
}
1928

29+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30+
pub enum VerificationError {
31+
InvalidSignature,
32+
InvalidContext,
33+
FailedToVerify,
34+
}
35+
2036
impl<C: IntrinsicCurve> VerifyingKey<C>
2137
where
2238
Point<C>: TwistedEdwardsPoint + FromCompressed<Coordinate<C>> + CyclicGroup,
@@ -33,32 +49,70 @@ where
3349
})
3450
}
3551

36-
pub fn verify(&self, message: &[u8], sig: &[u8]) -> bool {
37-
let Some(sig) = Signature::<C>::from_bytes(sig) else {
38-
return false;
39-
};
52+
pub fn verify(&self, message: &[u8], sig: &[u8]) -> Result<(), VerificationError> {
53+
self.verify_prehashed(message, sig, &[])
54+
}
4055

56+
/// The verify function for the prehash variant of Ed25519.
57+
/// message should be the message to be verified, before the prehash is applied.
58+
/// context is the optional context bytes that are shared between a signer and verifier, as per
59+
/// the Ed25519ph specification. If no context is provided, the empty slice will be used.
60+
/// The context can be up to 255 bytes.
61+
pub fn verify_ph(
62+
&self,
63+
message: &[u8],
64+
context: Option<&[u8]>,
65+
sig: &[u8],
66+
) -> Result<(), VerificationError> {
4167
let prehash = sha512(message);
4268

43-
// h = SHA512(dom2(F, C) || R || A || PH(M))
44-
// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.7
45-
let mut sha_input = Vec::new();
46-
4769
// dom2(F, C) domain separator
4870
// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-2
4971
// See definition of dom2 in the RFC. Note that the RFC refers to the prehash
5072
// version of Ed25519 as Ed25519ph, and the non-prehash version as Ed25519.
51-
sha_input.extend_from_slice(b"SigEd25519 no Ed25519 collisions");
52-
sha_input.extend_from_slice(&[1]); // phflag = 1
73+
let mut dom2 = Vec::new();
74+
dom2.extend_from_slice(b"SigEd25519 no Ed25519 collisions");
75+
dom2.push(1); // phflag = 1
5376

5477
// The RFC specifies optional "context" bytes that are shared between a signer and verifier.
55-
// We don't use any context bytes.
5678
// See: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1
57-
sha_input.extend_from_slice(&[0]); // context len = 0
79+
if let Some(context) = context {
80+
if context.len() > 255 {
81+
return Err(VerificationError::InvalidContext);
82+
}
83+
dom2.push(context.len() as u8);
84+
dom2.extend_from_slice(context);
85+
} else {
86+
dom2.push(0); // context len = 0
87+
}
88+
89+
self.verify_prehashed(&prehash, sig, &dom2)
90+
}
91+
92+
// Shared verify function for both the prehash and non-prehash variants of Ed25519.
93+
// prehash is either SHA512(message) or message, for Ed25519ph and Ed25519 respectively.
94+
// dom2 is the domain separator for the Ed25519ph and Ed25519 variants. It should be empty for
95+
// Ed25519.
96+
// See RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-2
97+
fn verify_prehashed(
98+
&self,
99+
prehash: &[u8],
100+
sig: &[u8],
101+
dom2: &[u8],
102+
) -> Result<(), VerificationError> {
103+
let Some(sig) = Signature::<C>::from_bytes(sig) else {
104+
return Err(VerificationError::InvalidSignature);
105+
};
106+
107+
// h = SHA512(dom2(F, C) || R || A || PH(M))
108+
// RFC reference: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.7
109+
let mut sha_input = Vec::new();
110+
111+
sha_input.extend_from_slice(dom2);
58112

59113
sha_input.extend_from_slice(&encode_point::<C>(&sig.r));
60114
sha_input.extend_from_slice(&encode_point::<C>(&self.point));
61-
sha_input.extend_from_slice(&prehash);
115+
sha_input.extend_from_slice(prehash);
62116

63117
let h = sha512(&sha_input);
64118

@@ -75,7 +129,11 @@ where
75129
<Point<C> as CyclicGroup>::NEG_GENERATOR,
76130
],
77131
);
78-
res == <Point<C> as TwistedEdwardsPoint>::IDENTITY
132+
if res == <Point<C> as TwistedEdwardsPoint>::IDENTITY {
133+
Ok(())
134+
} else {
135+
Err(VerificationError::FailedToVerify)
136+
}
79137
}
80138
}
81139

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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_ed25519_ed25519.rs");
10+
11+
pub struct Ed25519TestData {
12+
pub msg: &'static [u8],
13+
pub signature: [u8; 64],
14+
pub vk: [u8; 32],
15+
}
16+
17+
// Test data for the non-prehash variant of Ed25519.
18+
// The first five tests were taken from https://datatracker.ietf.org/doc/html/rfc8032#section-7.1
19+
// The rest were randomly generated.
20+
const ED25519_TEST_DATA: [Ed25519TestData; 10] = [
21+
Ed25519TestData {
22+
msg: b"",
23+
signature: hex!("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"),
24+
vk: hex!("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"),
25+
},
26+
Ed25519TestData {
27+
msg: &hex!("72"),
28+
signature: hex!("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"),
29+
vk: hex!("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"),
30+
},
31+
Ed25519TestData {
32+
msg: &hex!("af82"),
33+
signature: hex!("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a"),
34+
vk: hex!("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"),
35+
},
36+
Ed25519TestData {
37+
msg: &hex!("08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0"),
38+
signature: hex!("0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03"),
39+
vk: hex!("278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e"),
40+
},
41+
Ed25519TestData {
42+
msg: &hex!("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"),
43+
signature: hex!("dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b58909351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704"),
44+
vk: hex!("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"),
45+
},
46+
Ed25519TestData {
47+
msg: &hex!("470d6c430959"),
48+
signature: hex!("17ba04a7351648d316c9567cee48bfb568499ee0fea83fd246c44202e9ad9e920d983306ed7a3ac8ea51ebb5a1e57a0b270ca962c812aa8a89e60ce787ac8205"),
49+
vk: hex!("758751992ea75a6736661ac6f6ed4de7e5ed9dfbe33eaa9325780923614341d3"),
50+
},
51+
Ed25519TestData {
52+
msg: &hex!("a7bf65eac2bbbcb776761f247c3ccd6971396a88b3eb0bbebea592fd68b20a4d9e7cf474bea1eff3a332c9cdecd8fff2fe1e3cc6a3844318c2bc6f78a04a853ed1c535fe5824"),
53+
signature: hex!("a67dd824237e219fb224da3d16bcf5142b5d642e5a62198f3d2ed901eae5bb96e14975fcaeb714516fd0ded27a9fab1bada235d44d65457a96085f3d4eb0230c"),
54+
vk: hex!("7bfb9c93c50bb636b9d916fe3aec6a5ac6e19c47278ea404f7ea1721e3c46ced"),
55+
},
56+
Ed25519TestData {
57+
msg: &hex!("c781d2fd5640dd66f50c57cb7015feecf44a297c93ceafb611acffb39cb7c69f277491cc39eaa836008194e77860a7716799eca708859188b46d3e44dd3f57f3553244b1a8e5092fe1bdd6e016b67fd94e88187d03efe25d4178266dcac56aa1"),
58+
signature: hex!("63afcc5c9b282e2e7e8871b411cd69e1cad83f057cb764862453af88ed5bb255ebf96dab5ea1b1041bdc6d515e79f4c774e9c87d7b7a681cf399cab3005a580e"),
59+
vk: hex!("7e2467b9b1ae68d0b79e9c8214592022be6c369b2ba771cd7100d4be0db554b3"),
60+
},
61+
Ed25519TestData {
62+
msg: &hex!("7d89777ab2b5ff2ae46da312ed32a48b22977eb52a11fc3ab355f3ad7ad40641218681eef5add98f01"),
63+
signature: hex!("cf76c790c77166e2db3a28eca5de7f42ffc9dac85895de1f929c72714d23a9fdb92017432ef7424ff14acb815c76881d55dcc80cca1ed8630473baa9b9b9d005"),
64+
vk: hex!("4ff316d580e2330d99f92fdc149d4c88f36981be132a4f3fa065e649cf3571a8"),
65+
},
66+
Ed25519TestData {
67+
msg: &hex!("3d98781c525e466626b418"),
68+
signature: hex!("185a5fd9b6e82e07c72bc81296cb1e4f7a5bfd4f5226961f52e24c0f20cf12310b740d38146dfdba662be9b6b2926712a648e73fe22239486149a404864df50f"),
69+
vk: hex!("99cd13488f1b48f6d57d1f77ce2006487e65f8e7d6f1936cf6f36adf0b602d55"),
70+
},
71+
];
72+
73+
// Test data for the prehash variant of Ed25519.
74+
// First test was taken from the RFC: https://datatracker.ietf.org/doc/html/rfc8032#section-7.3
75+
// The rest were randomly generated.
76+
const ED25519PH_TEST_DATA: [Ed25519TestData; 10] = [
77+
// This test is taken from the RFC: https://datatracker.ietf.org/doc/html/rfc8032#section-7.3
78+
Ed25519TestData {
79+
msg: b"abc",
80+
signature: hex!(
81+
"98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406"
82+
),
83+
vk: hex!(
84+
"ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf"
85+
),
86+
},
87+
Ed25519TestData {
88+
msg: &hex!("4023e5edbfde97998cea65ee971c8cb24526596044f1216fa2c0d8c8ec8df95ef237bdd314022a2780dd09b9dcb8ba1df76d6ac7f0d7bf4374ef6405979dad73490d2b363545a2c0f5eddb965705a565f44a371d5cf58004d6834e0271c5e674"),
89+
signature: hex!("5ac8ece6e00341bb1bc10403837f2f59fc0a3cbcd352e9101dccb5af2ad41da9199758ad606679bd2dc4af5a1d89c73c36365ae5455b725c6a2cea8d06399501"),
90+
vk: hex!("6c6193110409068064bc7986acbc3c96449dfe32891e6c2fb3fc33ad7655ba0b")
91+
},
92+
Ed25519TestData {
93+
msg: &hex!("65b5f8e0fb2f47a16bc0e6777b5a4abed8a8dbcbd0b685257e47ede83a433cc3c5d8755959cdf8caa6990eae48f3759b03593b9bc0d8fc7383a5d8ea7b02de9dd761b410a4"),
94+
signature: hex!("93406bdd8d40925e1a6e316654874444baca364f6700c074e2975e50cd2e708e17d10084574701fe0c91eda4d2d796e26b2aba67c48b3f94fac151e699cfe809"),
95+
vk: hex!("9b606b002b395f9efe41a3f388d37bb81ed52486f8ddc19996176462da5d7b29")
96+
},
97+
Ed25519TestData {
98+
msg: &hex!("62dba7573793c7fa9908c4feb690c0b61b136d5c744b69b343a61bdc"),
99+
signature: hex!("c4bd72c7935dc4d3784abfcb7f20124429b73925d01ec48673bd37ab26c2cc159089ade51df1eb7ce175abf43afd6c23c7bb39ad2d6476acb3a04ce7339f2e0f"),
100+
vk: hex!("a166560aa0d208bf06c93a2a7f64748e503def9407ca8ee81687d7e6ee21efa7")
101+
},
102+
Ed25519TestData {
103+
msg: &hex!("40033e866996ddca4ff7a48d557bc7a4ffc4d97274bfae4691976cf0d587a9d5823a38ed5e7314b67b61b5d7536d3d581bd0ce77ca27ebd2ce26ce2e"),
104+
signature: hex!("24ea129ba9abc5b11dc5a690bccbaebe315b8882b029b8c81bd8cc6a5b65e79aa82298b64fc61e03d4081642c90a60ad3955ab484304194d95ecf1ba8dfe760a"),
105+
vk: hex!("bed62265ab0d6ba1be8dfe009fbb9514c6774ead6c34492adaee4893c32d39fc")
106+
},
107+
Ed25519TestData {
108+
msg: &hex!("0e9e0304a804628305083e4bde6de5d82fe5f5dbb47e232f2cf14439fd36dd59f26b87574614d8f4af6019c5d4d7ec77fee102445faf0c75f635a31234e2135199df5cdf013ff3472346e6f69e8a"),
109+
signature: hex!("dcf2a479abd7ea5211d853279f128e402fb64fc3148780694422e8a572e29ed1557fd89f172c0a3c1b2ab6944297deace095583bff09b302936f64198385490e"),
110+
vk: hex!("47547e699eaf210dcce343b4a2d176607a8a1ceb2a3e912360f40dd1fa3ab216")
111+
},
112+
Ed25519TestData {
113+
msg: &hex!("563df2cb74d0cfd961ed010958845e6983b1ca7a55761dba35ccebcf17dfd972bfcb908c116a4eacb84235ed"),
114+
signature: hex!("9960cb5936c23969707bf92ab0d51ae941dc2ce2534d818ed1c829dbbe916a93657bc6c7d38ad5f1d07df513f35409d9581cb3c122f0742f41295dc8e0396d0c"),
115+
vk: hex!("4c6a352b76f20b7757ee96083f2cd8b759556551d9e5937a7ec4345d4bf974ec")
116+
},
117+
Ed25519TestData {
118+
msg: &hex!("de08bd2b3a014ea5a85de35f718fea45e6c23c06f2d23e5bbc22bb998de9d7cb9d21fbb9a55aff4d2a867daaf4a897281c889c9536fb2259014030d8b24d9a04dfc0b74e62847638f1740767e62ccfbfab174749f657a75a3845924ca6a6a6d539"),
119+
signature: hex!("64bbb532b1ebe08554d6569be1133ed9219e0450fd1bf12fd30ce6aa3150aea3054ed3ff35ee7c458eeeee6141a9e5f3bb4d14c345a4435ae331ed3ab0fa350e"),
120+
vk: hex!("e22e10095f6fb59e5714093b78f9889ac3aa394c2a87b858ca6afb8558241a2e")
121+
},
122+
Ed25519TestData {
123+
msg: &hex!("96d3dd0d9df5160827ad62c96dc04ff1621e50b3c7a2bcba53e6e2c83b6939"),
124+
signature: hex!("5f6d536c7cb27391f7a274fd7f72e258b49f511476e5a0cd562c3f52a050694734c7251434bde23fe14977094a482fe5ff14a237fc24804cab5e595916240401"),
125+
vk: hex!("83a53511df806437a5c3e72c1fd43d448e217da4cc789d817fa2b12d9628128b")
126+
},
127+
Ed25519TestData {
128+
msg: &hex!("c366ea5e7b41a0382eaa22a5d6101d9fef9a7b234cc54f218108f2695f288c9369723aa7958d0605166b52be9a895133ae77eba9122d8780dd897d3f7c871cc48f57c11fcfa7a5a056c1e1"),
129+
signature: hex!("535e3a9fc9a3948998398f8d79af40ff7cb320e62498df3db3ad7cfbbd5c02eedee95a11f745f5334f6532e36ec5cfca1c2e14e416481e3cc3600ef58dab4901"),
130+
vk: hex!("4028f8f8d4f461fcc41d2c2878eddb7acb9fbf3254e7de511c3f513673ff05d0")
131+
},
132+
];
133+
134+
pub fn main() {
135+
for test_data in ED25519_TEST_DATA {
136+
let vk = VerifyingKey::<Ed25519Point>::from_bytes(&test_data.vk).unwrap();
137+
assert!(vk.verify(test_data.msg, &test_data.signature).is_ok());
138+
}
139+
140+
for test_data in ED25519PH_TEST_DATA {
141+
let vk = VerifyingKey::<Ed25519Point>::from_bytes(&test_data.vk).unwrap();
142+
assert!(vk
143+
.verify_ph(test_data.msg, None, &test_data.signature)
144+
.is_ok());
145+
}
146+
}

extensions/ecc/tests/programs/examples/eddsa.rs

Lines changed: 0 additions & 28 deletions
This file was deleted.
File renamed without changes.

extensions/ecc/tests/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,11 @@ mod tests {
320320
}
321321

322322
#[test]
323-
fn test_eddsa() -> Result<()> {
323+
fn test_ed25519() -> Result<()> {
324324
let config = Rv32EccConfig::new(vec![], vec![ED25519_CONFIG.clone()]);
325325
let elf = build_example_program_at_path_with_features(
326326
get_programs_dir!(),
327-
"eddsa",
327+
"ed25519",
328328
["ed25519"],
329329
&config,
330330
)?;

0 commit comments

Comments
 (0)