tink_hybrid/subtle/
elliptic_curves.rs1use p256::{
18 elliptic_curve,
19 elliptic_curve::{
20 ecdh,
21 generic_array::typenum::Unsigned,
22 sec1::{EncodedPoint, FromEncodedPoint},
23 AffinePoint,
24 },
25};
26use tink_core::{utils::wrap_err, TinkError};
27use tink_proto::{EcPointFormat, EllipticCurveType};
28
29const EC_FORMAT_PREFIX_UNCOMPRESSED: u8 = 4;
32const EC_FORMAT_PREFIX_COMPRESSED_ODD: u8 = 3;
34const EC_FORMAT_PREFIX_COMPRESSED_EVEN: u8 = 2;
36
37#[derive(Debug, Clone)]
39pub enum EcPublicKey {
40 NistP256(AffinePoint<p256::NistP256>),
41}
42
43impl EcPublicKey {
44 pub fn new(curve: EllipticCurveType, x: &[u8], y: &[u8]) -> Result<Self, TinkError> {
45 match curve {
46 EllipticCurveType::NistP256 => {
47 let x = element_from_padded_slice::<p256::NistP256>(x)?;
48 let y = element_from_padded_slice::<p256::NistP256>(y)?;
49 let encoded_pt = EncodedPoint::<p256::NistP256>::from_affine_coordinates(
50 &x, &y, false,
51 );
52 let affine_pt = Option::<_>::from(
53 AffinePoint::<p256::NistP256>::from_encoded_point(&encoded_pt),
54 )
55 .ok_or_else(|| TinkError::new("invalid point"))?;
56 Ok(EcPublicKey::NistP256(affine_pt))
57 }
58 _ => Err(format!("unsupported curve {curve:?}").into()),
59 }
60 }
61
62 pub fn curve(&self) -> EllipticCurveType {
63 match self {
64 EcPublicKey::NistP256(_) => EllipticCurveType::NistP256,
65 }
66 }
67
68 pub fn x_y_bytes(&self) -> Result<(Vec<u8>, Vec<u8>), TinkError> {
69 match self {
70 EcPublicKey::NistP256(affine_pt) => {
71 let encoded_pt: EncodedPoint<p256::NistP256> =
77 EncodedPoint::<p256::NistP256>::from(*affine_pt);
78 let pub_key_data = encoded_pt.as_bytes().to_vec();
79 let point_len =
80 <p256::NistP256 as elliptic_curve::Curve>::FieldBytesSize::to_usize();
81 if pub_key_data.len() != 2 * point_len + 1
82 || pub_key_data[0] != EC_FORMAT_PREFIX_UNCOMPRESSED
83 {
84 Err("unexpected public key data format".into())
85 } else {
86 Ok((
87 pub_key_data[1..point_len + 1].to_vec(),
88 pub_key_data[point_len + 1..].to_vec(),
89 ))
90 }
91 }
92 }
93 }
94}
95
96#[derive(Clone)]
98pub enum EcPrivateKey {
99 NistP256(p256::NonZeroScalar),
100}
101
102impl EcPrivateKey {
103 pub fn public_key(&self) -> EcPublicKey {
104 match self {
105 EcPrivateKey::NistP256(d) => {
106 let pub_key = p256::PublicKey::from_secret_scalar(d);
107 EcPublicKey::NistP256(*pub_key.as_affine())
108 }
109 }
110 }
111 pub fn d_bytes(&self) -> Vec<u8> {
112 match self {
113 EcPrivateKey::NistP256(d) => d.to_bytes().to_vec(),
114 }
115 }
116}
117
118impl EcPrivateKey {
119 pub fn new(curve: EllipticCurveType, d: &[u8]) -> Result<EcPrivateKey, TinkError> {
121 match curve {
122 EllipticCurveType::NistP256 => {
123 let d_elt = element_from_padded_slice::<p256::NistP256>(d)?;
124 let d_scalar = Option::<_>::from(p256::NonZeroScalar::from_repr(d_elt))
125 .ok_or_else(|| TinkError::new("failed to parse D value"))?;
126 Ok(EcPrivateKey::NistP256(d_scalar))
127 }
128 _ => Err(format!("unsupported curve {curve:?}").into()),
129 }
130 }
131}
132
133fn field_size_in_bytes(c: EllipticCurveType) -> Result<usize, TinkError> {
134 match c {
135 EllipticCurveType::NistP256 => {
136 Ok(<p256::NistP256 as elliptic_curve::Curve>::FieldBytesSize::to_usize())
137 }
138 _ => Err(format!("unsupported curve {c:?}").into()),
139 }
140}
141
142pub fn encoding_size_in_bytes(c: EllipticCurveType, p: EcPointFormat) -> Result<usize, TinkError> {
143 let c_size = field_size_in_bytes(c)?;
144 match p {
145 EcPointFormat::Uncompressed => Ok(2 * c_size + 1), EcPointFormat::DoNotUseCrunchyUncompressed => Ok(2 * c_size), EcPointFormat::Compressed => Ok(c_size + 1), _ => Err(format!("invalid point format {p:?}").into()),
149 }
150}
151
152pub fn point_encode(
154 c: EllipticCurveType,
155 p_format: EcPointFormat,
156 pub_key: &EcPublicKey,
157) -> Result<Vec<u8>, TinkError> {
158 let c_size = field_size_in_bytes(c)?;
159 let (x, y) = pub_key.x_y_bytes()?;
160 match p_format {
161 EcPointFormat::Uncompressed => {
162 let mut encoded = vec![0; 2 * c_size + 1];
163 encoded[1 + 2 * c_size - y.len()..].copy_from_slice(&y);
164 encoded[1 + c_size - x.len()..1 + c_size].copy_from_slice(&x);
165 encoded[0] = EC_FORMAT_PREFIX_UNCOMPRESSED;
166 Ok(encoded)
167 }
168 EcPointFormat::DoNotUseCrunchyUncompressed => {
169 let mut encoded = vec![0; 2 * c_size];
170 encoded[2 * c_size - y.len()..].copy_from_slice(&y);
171 encoded[c_size - x.len()..c_size].copy_from_slice(&x);
172 Ok(encoded)
173 }
174 EcPointFormat::Compressed => {
175 let mut encoded = vec![0; c_size + 1];
176 encoded[0] = if y[y.len() - 1] & 0x01 == 1 {
177 EC_FORMAT_PREFIX_COMPRESSED_ODD
178 } else {
179 EC_FORMAT_PREFIX_COMPRESSED_EVEN
180 };
181 encoded[1 + c_size - x.len()..].copy_from_slice(&x);
182 Ok(encoded)
183 }
184 _ => Err("invalid point format".into()),
185 }
186}
187
188pub fn point_decode(
190 c: EllipticCurveType,
191 p_format: EcPointFormat,
192 e: &[u8],
193) -> Result<EcPublicKey, TinkError> {
194 let c_size = field_size_in_bytes(c)?;
195 match p_format {
196 EcPointFormat::Uncompressed => {
197 if e.len() != (2 * c_size + 1) {
198 return Err("invalid point size".into());
199 }
200 if e[0] != EC_FORMAT_PREFIX_UNCOMPRESSED {
201 return Err("invalid point format".into());
202 }
203 match c {
204 EllipticCurveType::NistP256 => {
205 let pub_key = p256::PublicKey::from_sec1_bytes(e)
206 .map_err(|e| wrap_err("invalid point", e))?;
207 Ok(EcPublicKey::NistP256(*pub_key.as_affine()))
208 }
209 _ => Err(format!("unsupported curve {c:?}").into()),
210 }
211 }
212 EcPointFormat::DoNotUseCrunchyUncompressed => {
213 if e.len() != 2 * c_size {
214 return Err("invalid point size".into());
215 }
216 let mut e_prefixed = Vec::with_capacity(1 + e.len());
217 e_prefixed.push(EC_FORMAT_PREFIX_UNCOMPRESSED);
218 e_prefixed.extend_from_slice(e);
219 point_decode(c, EcPointFormat::Uncompressed, &e_prefixed)
220 }
221 EcPointFormat::Compressed => {
222 if e.len() != c_size + 1 {
223 return Err("compressed point has wrong length".into());
224 }
225 let _lsb = match e[0] {
226 EC_FORMAT_PREFIX_COMPRESSED_EVEN => false,
227 EC_FORMAT_PREFIX_COMPRESSED_ODD => true,
228 _ => return Err("invalid format".into()),
229 };
230 match c {
231 EllipticCurveType::NistP256 => {
232 let pub_key = p256::PublicKey::from_sec1_bytes(e)
233 .map_err(|e| wrap_err("invalid point", e))?;
234 Ok(EcPublicKey::NistP256(*pub_key.as_affine()))
235 }
236 _ => Err(format!("unsupported curve {c:?}").into()),
237 }
238 }
239 _ => Err(format!("invalid point format: {p_format:?}").into()),
240 }
241}
242
243pub fn compute_shared_secret(
245 peer_pub_key: &EcPublicKey,
246 priv_key: &EcPrivateKey,
247) -> Result<Vec<u8>, TinkError> {
248 let shared_secret = match (peer_pub_key, priv_key) {
249 (EcPublicKey::NistP256(peer_pub_key), EcPrivateKey::NistP256(priv_key)) => {
250 ecdh::diffie_hellman(priv_key, peer_pub_key)
251 .raw_secret_bytes()
252 .to_vec()
253 }
254 };
255 Ok(shared_secret)
256}
257
258pub fn generate_ecdh_key_pair(c: EllipticCurveType) -> Result<EcPrivateKey, TinkError> {
260 let mut csprng = elliptic_curve::rand_core::OsRng {};
261 match c {
262 EllipticCurveType::NistP256 => Ok(EcPrivateKey::NistP256(p256::NonZeroScalar::random(
263 &mut csprng,
264 ))),
265 _ => Err(format!("unsupported curve {c:?}").into()),
266 }
267}
268
269pub(crate) fn element_from_padded_slice<C: elliptic_curve::Curve>(
271 data: &[u8],
272) -> Result<elliptic_curve::FieldBytes<C>, TinkError> {
273 let point_len = C::FieldBytesSize::to_usize();
274 if data.len() >= point_len {
275 let offset = data.len() - point_len;
276 for v in data.iter().take(offset) {
277 if *v != 0 {
280 return Err("point too large".into());
281 }
282 }
283 Ok(elliptic_curve::FieldBytes::<C>::clone_from_slice(
284 &data[offset..],
285 ))
286 } else {
287 let mut data_copy = vec![0; point_len];
290 data_copy[(point_len - data.len())..].copy_from_slice(data);
291 Ok(elliptic_curve::FieldBytes::<C>::clone_from_slice(
292 &data_copy,
293 ))
294 }
295}