openvm_ecc_guest/
ecdsa.rs
1use alloc::vec::Vec;
2use core::ops::{Add, AddAssign, Mul};
3
4use ecdsa::{self, hazmat::bits2field, Error, RecoveryId, Result};
5use elliptic_curve::{sec1::Tag, PrimeCurve};
6use openvm_algebra_guest::{DivUnsafe, IntMod, Reduce};
7
8use crate::{
9 weierstrass::{FromCompressed, IntrinsicCurve, WeierstrassPoint},
10 CyclicGroup, Group,
11};
12
13pub type Coordinate<C> = <<C as IntrinsicCurve>::Point as WeierstrassPoint>::Coordinate;
14pub type Scalar<C> = <C as IntrinsicCurve>::Scalar;
15
16#[repr(C)]
17#[derive(Clone)]
18pub struct VerifyingKey<C: IntrinsicCurve> {
19 pub(crate) inner: PublicKey<C>,
20}
21
22#[repr(C)]
23#[derive(Clone)]
24pub struct PublicKey<C: IntrinsicCurve> {
25 point: <C as IntrinsicCurve>::Point,
27}
28
29impl<C: IntrinsicCurve> PublicKey<C>
30where
31 C::Point: WeierstrassPoint + Group + FromCompressed<Coordinate<C>>,
32 Coordinate<C>: IntMod,
33 for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
34{
35 pub fn new(point: <C as IntrinsicCurve>::Point) -> Self {
36 Self { point }
37 }
38
39 pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
40 if bytes.is_empty() {
41 return Err(Error::new());
42 }
43
44 let tag = Tag::from_u8(bytes[0]).unwrap();
46
47 let expected_len = tag.message_len(Coordinate::<C>::NUM_LIMBS);
49 if bytes.len() != expected_len {
50 return Err(Error::new());
51 }
52
53 match tag {
54 Tag::Identity => {
55 let point = <<C as IntrinsicCurve>::Point as WeierstrassPoint>::IDENTITY;
56 Ok(Self { point })
57 }
58
59 Tag::CompressedEvenY | Tag::CompressedOddY => {
60 let x = Coordinate::<C>::from_be_bytes(&bytes[1..]);
61 let rec_id = bytes[0] & 1;
62 let point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
63 Ok(Self { point })
64 }
65
66 Tag::Uncompressed => {
67 let (x_bytes, y_bytes) = bytes[1..].split_at(Coordinate::<C>::NUM_LIMBS);
68 let x = Coordinate::<C>::from_be_bytes(x_bytes);
69 let y = Coordinate::<C>::from_be_bytes(y_bytes);
70 let point = <C as IntrinsicCurve>::Point::from_xy(x, y).ok_or_else(Error::new)?;
71 Ok(Self { point })
72 }
73
74 _ => Err(Error::new()),
75 }
76 }
77
78 pub fn to_sec1_bytes(&self, compress: bool) -> Vec<u8> {
79 if self.point.is_identity() {
80 return vec![0x00];
81 }
82
83 let (x, y) = self.point.clone().into_coords();
84
85 if compress {
86 let mut bytes = Vec::<u8>::with_capacity(1 + Coordinate::<C>::NUM_LIMBS);
87 let tag = if y.as_le_bytes()[0] & 1 == 1 {
88 Tag::CompressedOddY
89 } else {
90 Tag::CompressedEvenY
91 };
92 bytes.push(tag.into());
93 bytes.extend_from_slice(x.to_be_bytes().as_ref());
94 bytes
95 } else {
96 let mut bytes = Vec::<u8>::with_capacity(1 + Coordinate::<C>::NUM_LIMBS * 2);
97 bytes.push(Tag::Uncompressed.into());
98 bytes.extend_from_slice(x.to_be_bytes().as_ref());
99 bytes.extend_from_slice(y.to_be_bytes().as_ref());
100 bytes
101 }
102 }
103
104 pub fn as_affine(&self) -> &<C as IntrinsicCurve>::Point {
105 &self.point
106 }
107}
108
109impl<C: IntrinsicCurve> VerifyingKey<C>
110where
111 C::Point: WeierstrassPoint + Group + FromCompressed<Coordinate<C>>,
112 Coordinate<C>: IntMod,
113 for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
114{
115 pub fn new(public_key: PublicKey<C>) -> Self {
116 Self { inner: public_key }
117 }
118
119 pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
120 let public_key = PublicKey::<C>::from_sec1_bytes(bytes)?;
121 Ok(Self::new(public_key))
122 }
123
124 pub fn from_affine(point: <C as IntrinsicCurve>::Point) -> Result<Self> {
125 let public_key = PublicKey::<C>::new(point);
126 Ok(Self::new(public_key))
127 }
128
129 pub fn to_sec1_bytes(&self, compress: bool) -> Vec<u8> {
130 self.inner.to_sec1_bytes(compress)
131 }
132
133 pub fn as_affine(&self) -> &<C as IntrinsicCurve>::Point {
134 self.inner.as_affine()
135 }
136}
137
138impl<C> VerifyingKey<C>
139where
140 C: PrimeCurve + IntrinsicCurve,
141 C::Point: WeierstrassPoint + CyclicGroup + FromCompressed<Coordinate<C>>,
142 Coordinate<C>: IntMod,
143 C::Scalar: IntMod + Reduce,
144{
145 #[allow(non_snake_case)]
152 pub fn recover_from_prehash_noverify(
153 prehash: &[u8],
154 sig: &[u8],
155 recovery_id: RecoveryId,
156 ) -> Result<Self>
157 where
158 for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
159 for<'a> &'a Coordinate<C>: Mul<&'a Coordinate<C>, Output = Coordinate<C>>,
160 {
161 assert!(Scalar::<C>::NUM_LIMBS <= Coordinate::<C>::NUM_LIMBS);
163 assert_eq!(sig.len(), <C as IntrinsicCurve>::Scalar::NUM_LIMBS * 2);
165 let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
167 let r = Scalar::<C>::from_be_bytes(r_be);
169 let s = Scalar::<C>::from_be_bytes(s_be);
170 if !r.is_reduced() || !s.is_reduced() {
171 return Err(Error::new());
172 }
173 if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
174 return Err(Error::new());
175 }
176
177 let z = Scalar::<C>::from_be_bytes(bits2field::<C>(prehash).unwrap().as_ref());
179
180 let mut x = Coordinate::<C>::from_le_bytes(r.as_le_bytes());
183 if recovery_id.is_x_reduced() {
184 let order = Coordinate::<C>::from_le_bytes(Scalar::<C>::MODULUS.as_ref());
186 x.add_assign(order);
187 }
188 let rec_id = recovery_id.to_byte();
189 let R: C::Point = FromCompressed::decompress(x, &rec_id).ok_or_else(Error::new)?;
191
192 let neg_u1 = z.div_unsafe(&r);
193 let u2 = s.div_unsafe(&r);
194 let NEG_G = C::Point::NEG_GENERATOR;
195 let point = <C as IntrinsicCurve>::msm(&[neg_u1, u2], &[NEG_G, R]);
196 let public_key = PublicKey { point };
197
198 Ok(VerifyingKey { inner: public_key })
199 }
200
201 #[allow(non_snake_case)]
203 pub fn verify_prehashed(self, prehash: &[u8], sig: &[u8]) -> Result<()>
204 where
205 for<'a> &'a C::Point: Add<&'a C::Point, Output = C::Point>,
206 for<'a> &'a Scalar<C>: DivUnsafe<&'a Scalar<C>, Output = Scalar<C>>,
207 {
208 assert!(Scalar::<C>::NUM_LIMBS <= Coordinate::<C>::NUM_LIMBS);
210 assert_eq!(sig.len(), Scalar::<C>::NUM_LIMBS * 2);
212 let (r_be, s_be) = sig.split_at(<C as IntrinsicCurve>::Scalar::NUM_LIMBS);
214 let r = Scalar::<C>::from_be_bytes(r_be);
216 let s = Scalar::<C>::from_be_bytes(s_be);
217 if !r.is_reduced() || !s.is_reduced() {
218 return Err(Error::new());
219 }
220 if r == Scalar::<C>::ZERO || s == Scalar::<C>::ZERO {
221 return Err(Error::new());
222 }
223
224 let z = <C as IntrinsicCurve>::Scalar::from_be_bytes(
226 bits2field::<C>(prehash).unwrap().as_ref(),
227 );
228
229 let u1 = z.div_unsafe(&s);
230 let u2 = (&r).div_unsafe(&s);
231
232 let G = C::Point::GENERATOR;
233 let Q = self.inner.point;
235 let R = <C as IntrinsicCurve>::msm(&[u1, u2], &[G, Q]);
236 if R.is_identity() {
237 return Err(Error::new());
238 }
239 let (x_1, _) = R.into_coords();
240 let x_mod_n = Scalar::<C>::reduce_le_bytes(x_1.as_le_bytes());
242 if x_mod_n == r {
243 Ok(())
244 } else {
245 Err(Error::new())
246 }
247 }
248}