1#![allow(non_snake_case)]
11use sha2::{digest::Digest, Sha512};
12use static_assertions::const_assert;
13use zeroize::ZeroizeOnDrop;
14
15use crate::{
16 arithmetic::{
17 uint::{U256, U512},
18 BigInteger,
19 },
20 curve::{
21 te::{
22 instance::curve25519::{Curve25519Config, Curve25519FrParam},
23 Affine, Projective,
24 },
25 CurveConfig, CurveGroup, PrimeGroup,
26 },
27 field::{
28 fp::{Fp256, Fp512, FpParams, LIMBS_512},
29 prime::PrimeField,
30 },
31};
32
33pub type Scalar = Fp256<Curve25519FrParam>;
35
36pub(crate) type WideScalar = Fp512<Curve25519Fr512Param>;
38
39pub(crate) struct Curve25519Fr512Param;
41impl FpParams<LIMBS_512> for Curve25519Fr512Param {
42 const GENERATOR: Fp512<Self> = Fp512::from_fp(Curve25519FrParam::GENERATOR);
43 const MODULUS: U512 = U512::from_uint(Curve25519FrParam::MODULUS);
44}
45
46pub type ProjectivePoint = Projective<Curve25519Config>;
48
49pub type AffinePoint = Affine<Curve25519Config>;
51
52pub type SecretKey = [u8; SECRET_KEY_LENGTH];
59
60pub const SECRET_KEY_LENGTH: usize = 32;
62
63pub type PublicKey = [u8; PUBLIC_KEY_LENGTH];
67
68pub const PUBLIC_KEY_LENGTH: usize = 32;
70
71pub const SIGNATURE_LENGTH: usize = 64;
73
74#[derive(Clone, PartialEq, Debug, ZeroizeOnDrop)]
86pub(crate) struct ExpandedSecretKey {
87 pub(crate) scalar: Scalar,
89 pub(crate) hash_prefix: [u8; 32],
92}
93
94impl ExpandedSecretKey {
95 pub(crate) fn from_bytes(bytes: &[u8]) -> ExpandedSecretKey {
99 let mut scalar_bytes = [0u8; 32];
100 let mut hash_prefix = [0u8; 32];
101 scalar_bytes.copy_from_slice(&bytes[00..32]);
102 hash_prefix.copy_from_slice(&bytes[32..64]);
103
104 let scalar = Scalar::from_bigint(U256::from_bytes_le(&clamp_integer(
105 scalar_bytes,
106 )));
107 Self { scalar, hash_prefix }
108 }
109}
110
111#[must_use]
134pub const fn clamp_integer(mut bytes: [u8; 32]) -> [u8; 32] {
135 bytes[0] &= 0b1111_1000;
136 bytes[31] &= 0b0111_1111;
137 bytes[31] |= 0b0100_0000;
138 bytes
139}
140
141impl From<&SecretKey> for ExpandedSecretKey {
142 #[allow(clippy::unwrap_used)]
143 fn from(secret_key: &SecretKey) -> ExpandedSecretKey {
144 let hash = Sha512::default().chain_update(secret_key).finalize();
145 ExpandedSecretKey::from_bytes(hash.as_ref())
146 }
147}
148
149#[derive(Clone, PartialEq)]
157pub struct SigningKey {
158 pub(crate) signing_key: ExpandedSecretKey,
160 pub(crate) verifying_key: VerifyingKey,
162}
163
164impl SigningKey {
165 #[must_use]
167 pub fn verifying_key(&self) -> VerifyingKey {
168 self.verifying_key
169 }
170
171 #[inline]
173 #[must_use]
174 pub fn from_bytes(secret_key: &SecretKey) -> Self {
175 let signing_key: ExpandedSecretKey = secret_key.into();
176 let verifying_key: VerifyingKey = signing_key.clone().into();
177 Self { signing_key, verifying_key }
178 }
179
180 #[must_use]
182 pub fn is_valid_signature(
183 &self,
184 message: &[u8],
185 signature: &Signature,
186 ) -> bool {
187 self.verifying_key.is_valid(message, signature)
188 }
189
190 #[must_use]
203 pub fn sign(&self, message: &[u8]) -> Signature {
204 let mut h = Sha512::new();
205
206 h.update(self.signing_key.hash_prefix);
207 h.update(message);
208
209 let r = WideScalar::from_bigint(U512::from_bytes_le(
210 h.finalize().as_slice(),
211 ));
212 let r = Scalar::from_fp(r);
213
214 let R = ProjectivePoint::generator() * r;
215
216 h = Sha512::new();
217 h.update(CompressedPointY::from(R.into_affine()));
218 h.update(CompressedPointY::from(
219 self.verifying_key.point.into_affine(),
220 ));
221 h.update(message);
222
223 let k = WideScalar::from_bigint(U512::from_bytes_le(
224 h.finalize().as_slice(),
225 ));
226 let k = Scalar::from_fp(k);
227 let s: Scalar = (k * self.signing_key.scalar) + r;
228
229 Signature { R, s }
230 }
231}
232
233#[derive(Copy, Clone, Hash)]
239pub struct CompressedPointY([u8; 32]);
240
241impl AsRef<[u8]> for CompressedPointY {
242 fn as_ref(&self) -> &[u8] {
243 &self.0
244 }
245}
246
247impl From<CompressedPointY> for [u8; 32] {
248 fn from(value: CompressedPointY) -> Self {
249 value.0
250 }
251}
252
253const_assert!(
254 <Curve25519Config as CurveConfig>::BaseField::HAS_MODULUS_SPARE_BIT
255);
256
257impl From<AffinePoint> for CompressedPointY {
258 fn from(point: AffinePoint) -> Self {
259 let mut s: [u8; 32] = point
260 .y
261 .into_bigint()
262 .into_bytes_le()
263 .try_into()
264 .expect("Y coordinate should be 32 bytes");
265
266 let is_odd = point.x.into_bigint().is_odd();
269 s[31] ^= u8::from(is_odd) << 7;
270
271 CompressedPointY(s)
272 }
273}
274
275#[derive(Copy, Clone, Eq, PartialEq)]
280pub struct Signature {
281 pub R: ProjectivePoint,
291
292 pub s: Scalar,
302}
303
304impl Signature {
305 #[must_use]
310 pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
311 let mut bytes = [0u8; 64];
312
313 let r_compressed = CompressedPointY::from(self.R.into_affine());
315 bytes[..32].copy_from_slice(r_compressed.as_ref());
316
317 let s_bytes = self.s.into_bigint().into_bytes_le();
319 bytes[32..].copy_from_slice(&s_bytes[..32]);
320
321 bytes
322 }
323
324 #[must_use]
326 pub fn from_affine_R_s(R: AffinePoint, s: Scalar) -> Self {
327 Signature { R: R.into(), s }
328 }
329}
330
331#[derive(Copy, Clone, Default, PartialEq)]
333pub struct VerifyingKey {
334 pub point: ProjectivePoint,
336}
337
338impl VerifyingKey {
339 #[must_use]
341 pub fn from_affine(point: AffinePoint) -> Self {
342 VerifyingKey { point: point.into() }
343 }
344
345 #[must_use]
347 pub fn is_valid(&self, message: &[u8], signature: &Signature) -> bool {
348 let expected_r = self.compute_R(signature, message);
349 expected_r == signature.R
350 }
351
352 fn compute_R(
357 &self,
358 signature: &Signature,
359 message: &[u8],
360 ) -> ProjectivePoint {
361 let R = signature.R;
362 let A = self.point;
363
364 let mut h = Sha512::new();
365 h.update(CompressedPointY::from(R.into_affine()));
366 h.update(CompressedPointY::from(A.into_affine()));
367 h.update(message);
368
369 let k = WideScalar::from_bigint(U512::from_bytes_le(
370 h.finalize().as_slice(),
371 ));
372 let k = Scalar::from_fp(k);
373
374 A * (-k) + ProjectivePoint::generator() * signature.s
376 }
377
378 #[inline]
380 #[must_use]
381 pub fn to_bytes(&self) -> PublicKey {
382 CompressedPointY::from(self.point.into_affine()).into()
383 }
384}
385
386impl From<ProjectivePoint> for VerifyingKey {
387 fn from(point: ProjectivePoint) -> Self {
388 VerifyingKey { point }
389 }
390}
391
392impl From<ExpandedSecretKey> for VerifyingKey {
393 fn from(value: ExpandedSecretKey) -> Self {
394 let point = ProjectivePoint::generator() * value.scalar;
395 point.into()
396 }
397}
398
399#[cfg(test)]
400mod test {
401 use alloc::string::String;
402
403 use hex_literal::hex;
404 use proptest::prelude::*;
405
406 use super::*;
407
408 #[test]
409 fn sign_and_verify_known_message() {
410 let secret_key: SecretKey = [1u8; SECRET_KEY_LENGTH];
411 let signing_key = SigningKey::from_bytes(&secret_key);
412 let message = b"Sign me!";
413
414 let signature = signing_key.sign(message);
415 assert!(signing_key.is_valid_signature(message, &signature));
416
417 let invalid_message = b"I'm not signed!";
419 assert!(!signing_key.is_valid_signature(invalid_message, &signature));
420 }
421
422 #[test]
423 fn sign_and_verify() {
424 proptest!(|(secret_key: SecretKey, message: String, wrong_message: String)| {
425 let signing_key = SigningKey::from_bytes(&secret_key);
426
427 let signature = signing_key.sign(message.as_bytes());
428 assert!(signing_key.is_valid_signature(message.as_bytes(), &signature));
429
430 if message != wrong_message{
432 assert!(!signing_key.is_valid_signature(wrong_message.as_bytes(), &signature));
433 }
434 });
435 }
436
437 struct Rfc8032TestCase {
439 secret_key: &'static [u8],
440 expected_public_key: &'static [u8],
441 message: &'static [u8],
442 expected_signature: &'static [u8],
443 }
444
445 macro_rules! test_case {
447 (
448 $secret_key:expr, $public_key:expr, $message:expr, $signature:expr
449 ) => {
450 Rfc8032TestCase {
451 secret_key: &hex!($secret_key),
452 expected_public_key: &hex!($public_key),
453 message: &hex!($message),
454 expected_signature: &hex!($signature),
455 }
456 };
457 }
458
459 #[test]
460 fn rfc8032_known_signatures() {
461 let test_cases = [
465 test_case!(
466 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
467 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
468 "",
469 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
470 ),
471 test_case!(
472 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
473 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
474 "72",
475 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"
476 ),
477 test_case!(
478 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
479 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
480 "af82",
481 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a"
482 ),
483 test_case!(
484 "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9",
485 "e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057",
486 "cbc77b",
487 "d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00c"
488 ),
489 test_case!(
490 "6df9340c138cc188b5fe4464ebaa3f7fc206a2d55c3434707e74c9fc04e20ebb",
491 "c0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7",
492 "5f4c8989",
493 "124f6fc6b0d100842769e71bd530664d888df8507df6c56dedfdb509aeb93416e26b918d38aa06305df3095697c18b2aa832eaa52edc0ae49fbae5a85e150c07"
494 ),
495 test_case!(
496 "b780381a65edf8b78f6945e8dbec7941ac049fd4c61040cf0c324357975a293c",
497 "e253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01",
498 "18b6bec097",
499 "b2fc46ad47af464478c199e1f8be169f1be6327c7f9a0a6689371ca94caf04064a01b22aff1520abd58951341603faed768cf78ce97ae7b038abfe456aa17c09"
500 ),
501 test_case!(
502 "78ae9effe6f245e924a7be63041146ebc670dbd3060cba67fbc6216febc44546",
503 "fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d",
504 "89010d855972",
505 "6ed629fc1d9ce9e1468755ff636d5a3f40a5d9c91afd93b79d241830f7e5fa29854b8f20cc6eecbb248dbd8d16d14e99752194e4904d09c74d639518839d2300"
506 ),
507 test_case!(
508 "691865bfc82a1e4b574eecde4c7519093faf0cf867380234e3664645c61c5f79",
509 "98a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63",
510 "b4a8f381e70e7a",
511 "6e0af2fe55ae377a6b7a7278edfb419bd321e06d0df5e27037db8812e7e3529810fa5552f6c0020985ca17a0e02e036d7b222a24f99b77b75fdd16cb05568107"
512 ),
513 test_case!(
514 "3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073",
515 "f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863",
516 "4284abc51bb67235",
517 "d6addec5afb0528ac17bb178d3e7f2887f9adbb1ad16e110545ef3bc57f9de2314a5c8388f723b8907be0f3ac90c6259bbe885ecc17645df3db7d488f805fa08"
518 ),
519 test_case!(
520 "605f90b53d8e4a3b48b97d745439f2a0807d83b8502e8e2979f03e8d376ac9fe",
521 "aa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f",
522 "3bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0",
523 "dd0212e63288cbe14a4569b4d891da3c7f92727c5e7f9a801cf9d6827085e7095b669d7d45f882ca5f0745dccd24d87a57181320191e5b7a47c3f7f2dccbd707"
524 ),
525 test_case!(
526 "9e2c3d189838f4dd52ef0832886874c5ca493983ddadc07cbc570af2ee9d6209",
527 "f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69",
528 "19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc",
529 "38a31b6b465084738262a26c065fe5d9e2886bf9dd35cde05df9bad0cc7db401c750aa19e66090bce25a3c721201e60502c8c10454346648af065eab0ee7d80f"
530 ),
531 test_case!(
532 "575f8fb6c7465e92c250caeec1786224bc3eed729e463953a394c9849cba908f",
533 "71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2",
534 "2cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553",
535 "903b484cb24bc503cdced844614073256c6d5aa45f1f9f62c7f22e5649212bc1d6ef9eaa617b6b835a6de2beff2faac83d37a4a5fc5cc3b556f56edde2651f02"
536 ),
537 ];
538
539 for Rfc8032TestCase {
540 secret_key,
541 expected_public_key: public_key,
542 message,
543 expected_signature,
544 } in test_cases
545 {
546 let secret_key: SecretKey = secret_key
547 .try_into()
548 .expect("secret key should have proper size");
549
550 let signing_key = SigningKey::from_bytes(&secret_key);
552 assert_eq!(signing_key.verifying_key.to_bytes(), public_key);
553
554 let signature = signing_key.sign(message);
556 assert!(signing_key.is_valid_signature(message, &signature));
557
558 let serialized_sig = signature.to_bytes();
560 assert_eq!(serialized_sig.as_ref(), expected_signature);
561
562 let wrong_msg = [message, b"invalid"].concat();
564 assert!(!signing_key.is_valid_signature(&wrong_msg, &signature));
565 }
566 }
567
568 #[test]
569 fn zeroize_signing_key() {
570 let ptr = {
571 let secret = SigningKey::from_bytes(&[4u8; 32]);
572 &raw const secret.signing_key
573 };
574 let secret_key = unsafe { ptr.as_ref().unwrap() };
575
576 assert!(secret_key.scalar.is_zero());
577 assert_eq!(secret_key.hash_prefix, [0u8; 32]);
578 }
579}