iota_sdk_crypto/
secp256r1.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use iota_types::{
6    Secp256r1PublicKey, Secp256r1Signature, SignatureScheme, SimpleSignature, UserSignature,
7};
8use p256::{
9    ecdsa::{SigningKey, VerifyingKey},
10    elliptic_curve::group::GroupEncoding,
11};
12use signature::{Signer, Verifier};
13
14use crate::SignatureError;
15
16#[derive(Clone, Eq, PartialEq)]
17pub struct Secp256r1PrivateKey(SigningKey);
18
19impl std::fmt::Debug for Secp256r1PrivateKey {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_tuple("Secp256r1PrivateKey")
22            .field(&"__elided__")
23            .finish()
24    }
25}
26
27#[cfg(test)]
28impl proptest::arbitrary::Arbitrary for Secp256r1PrivateKey {
29    type Parameters = ();
30    type Strategy = proptest::strategy::BoxedStrategy<Self>;
31    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
32        use proptest::strategy::Strategy;
33
34        proptest::arbitrary::any::<[u8; Self::LENGTH]>()
35            .prop_map(Self::new)
36            .boxed()
37    }
38}
39
40impl Secp256r1PrivateKey {
41    /// The length of an secp256r1 private key in bytes.
42    pub const LENGTH: usize = 32;
43
44    pub fn new(bytes: [u8; Self::LENGTH]) -> Self {
45        Self(SigningKey::from_bytes(&bytes.into()).unwrap())
46    }
47
48    pub fn scheme(&self) -> SignatureScheme {
49        SignatureScheme::Secp256r1
50    }
51
52    pub fn verifying_key(&self) -> Secp256r1VerifyingKey {
53        let verifying_key = self.0.verifying_key();
54        Secp256r1VerifyingKey(*verifying_key)
55    }
56
57    pub fn public_key(&self) -> Secp256r1PublicKey {
58        Secp256r1PublicKey::new(self.0.verifying_key().as_ref().to_bytes().into())
59    }
60
61    pub fn generate<R>(mut rng: R) -> Self
62    where
63        R: rand_core::RngCore + rand_core::CryptoRng,
64    {
65        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
66        rng.fill_bytes(&mut buf);
67        Self::new(buf)
68    }
69
70    #[cfg(feature = "pem")]
71    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
72    /// Deserialize PKCS#8 private key from ASN.1 DER-encoded data (binary
73    /// format).
74    pub fn from_der(bytes: &[u8]) -> Result<Self, SignatureError> {
75        p256::pkcs8::DecodePrivateKey::from_pkcs8_der(bytes)
76            .map(Self)
77            .map_err(SignatureError::from_source)
78    }
79
80    #[cfg(feature = "pem")]
81    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
82    /// Serialize this private key as DER-encoded PKCS#8
83    pub fn to_der(&self) -> Result<Vec<u8>, SignatureError> {
84        use p256::pkcs8::EncodePrivateKey;
85
86        self.0
87            .to_pkcs8_der()
88            .map_err(SignatureError::from_source)
89            .map(|der| der.as_bytes().to_owned())
90    }
91
92    #[cfg(feature = "pem")]
93    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
94    /// Deserialize PKCS#8-encoded private key from PEM.
95    pub fn from_pem(s: &str) -> Result<Self, SignatureError> {
96        p256::pkcs8::DecodePrivateKey::from_pkcs8_pem(s)
97            .map(Self)
98            .map_err(SignatureError::from_source)
99    }
100
101    #[cfg(feature = "pem")]
102    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
103    /// Serialize this private key as PEM-encoded PKCS#8
104    pub fn to_pem(&self) -> Result<String, SignatureError> {
105        use pkcs8::EncodePrivateKey;
106
107        self.0
108            .to_pkcs8_pem(pkcs8::LineEnding::default())
109            .map_err(SignatureError::from_source)
110            .map(|pem| (*pem).to_owned())
111    }
112
113    #[cfg(feature = "pem")]
114    pub(crate) fn from_p256(private_key: SigningKey) -> Self {
115        Self(private_key)
116    }
117}
118
119impl crate::ToFromBytes for Secp256r1PrivateKey {
120    type Error = crate::PrivateKeyError;
121    type ByteArray = [u8; Self::LENGTH];
122
123    /// Return the raw 32-byte private key
124    fn to_bytes(&self) -> Self::ByteArray {
125        self.0.to_bytes().into()
126    }
127
128    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
129        if bytes.len() != Self::LENGTH {
130            return Err(crate::PrivateKeyError::InvalidScheme(
131                "invalid secp256r1 key length".to_string(),
132            ));
133        }
134
135        let mut arr = [0u8; Self::LENGTH];
136        arr.copy_from_slice(bytes);
137        Ok(Self::new(arr))
138    }
139}
140
141impl crate::PrivateKeyScheme for Secp256r1PrivateKey {
142    const SCHEME: SignatureScheme = SignatureScheme::Secp256r1;
143}
144
145#[cfg(feature = "mnemonic")]
146impl crate::FromMnemonic for Secp256r1PrivateKey {
147    type Error = crate::PrivateKeyError;
148
149    fn from_mnemonic(
150        phrase: &str,
151        account_index: impl Into<Option<u64>>,
152        password: impl Into<Option<String>>,
153    ) -> Result<Self, Self::Error>
154    where
155        Self: Sized,
156    {
157        let path = format!(
158            "m/{}'/{}'/0'/0/{}",
159            crate::DERIVATION_PATH_PURPOSE_SECP256R1,
160            crate::DERIVATION_PATH_COIN_TYPE,
161            account_index.into().unwrap_or_default()
162        );
163        Self::from_mnemonic_with_path(phrase, path, password)
164    }
165
166    fn from_mnemonic_with_path(
167        phrase: &str,
168        path: String,
169        password: impl Into<Option<String>>,
170    ) -> Result<Self, Self::Error>
171    where
172        Self: Sized,
173    {
174        use std::str::FromStr;
175
176        use crate::ToFromBytes;
177
178        let mnemonic = bip39::Mnemonic::parse_in_normalized(bip39::Language::English, phrase)?;
179        let seed = mnemonic.to_seed(password.into().unwrap_or_default());
180        let child_xprv =
181            bip32::XPrv::derive_from_path(seed, &bip32::DerivationPath::from_str(&path)?)?;
182        Self::from_bytes(&child_xprv.private_key().to_bytes())
183    }
184}
185
186impl Signer<Secp256r1Signature> for Secp256r1PrivateKey {
187    fn try_sign(&self, message: &[u8]) -> Result<Secp256r1Signature, SignatureError> {
188        let signature: p256::ecdsa::Signature = self.0.try_sign(message)?;
189        Ok(Secp256r1Signature::new(signature.to_bytes().into()))
190    }
191}
192
193impl Signer<SimpleSignature> for Secp256r1PrivateKey {
194    fn try_sign(&self, msg: &[u8]) -> Result<SimpleSignature, SignatureError> {
195        <Self as Signer<Secp256r1Signature>>::try_sign(self, msg).map(|signature| {
196            SimpleSignature::Secp256r1 {
197                signature,
198                public_key: self.public_key(),
199            }
200        })
201    }
202}
203
204impl Signer<UserSignature> for Secp256r1PrivateKey {
205    fn try_sign(&self, msg: &[u8]) -> Result<UserSignature, SignatureError> {
206        <Self as Signer<SimpleSignature>>::try_sign(self, msg).map(UserSignature::Simple)
207    }
208}
209
210#[derive(Debug, Clone, Eq, PartialEq)]
211pub struct Secp256r1VerifyingKey(VerifyingKey);
212
213impl Secp256r1VerifyingKey {
214    pub fn new(public_key: &Secp256r1PublicKey) -> Result<Self, SignatureError> {
215        VerifyingKey::try_from(public_key.inner().as_ref()).map(Self)
216    }
217
218    pub fn public_key(&self) -> Secp256r1PublicKey {
219        Secp256r1PublicKey::new(self.0.as_ref().to_bytes().into())
220    }
221
222    #[cfg(feature = "pem")]
223    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
224    /// Deserialize public key from ASN.1 DER-encoded data (binary format).
225    pub fn from_der(bytes: &[u8]) -> Result<Self, SignatureError> {
226        p256::pkcs8::DecodePublicKey::from_public_key_der(bytes)
227            .map(Self)
228            .map_err(SignatureError::from_source)
229    }
230
231    #[cfg(feature = "pem")]
232    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
233    /// Serialize this public key as DER-encoded data
234    pub fn to_der(&self) -> Result<Vec<u8>, SignatureError> {
235        use pkcs8::EncodePublicKey;
236
237        self.0
238            .to_public_key_der()
239            .map_err(SignatureError::from_source)
240            .map(|der| der.into_vec())
241    }
242
243    #[cfg(feature = "pem")]
244    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
245    /// Deserialize public key from PEM.
246    pub fn from_pem(s: &str) -> Result<Self, SignatureError> {
247        p256::pkcs8::DecodePublicKey::from_public_key_pem(s)
248            .map(Self)
249            .map_err(SignatureError::from_source)
250    }
251
252    #[cfg(feature = "pem")]
253    #[cfg_attr(doc_cfg, doc(cfg(feature = "pem")))]
254    /// Serialize this public key into PEM
255    pub fn to_pem(&self) -> Result<String, SignatureError> {
256        use pkcs8::EncodePublicKey;
257
258        self.0
259            .to_public_key_pem(pkcs8::LineEnding::default())
260            .map_err(SignatureError::from_source)
261    }
262
263    #[cfg(feature = "pem")]
264    pub(crate) fn from_p256(verifying_key: VerifyingKey) -> Self {
265        Self(verifying_key)
266    }
267}
268
269impl Verifier<Secp256r1Signature> for Secp256r1VerifyingKey {
270    fn verify(&self, message: &[u8], signature: &Secp256r1Signature) -> Result<(), SignatureError> {
271        let signature = p256::ecdsa::Signature::from_bytes(signature.inner().into())?;
272        self.0.verify(message, &signature)
273    }
274}
275
276impl Verifier<SimpleSignature> for Secp256r1VerifyingKey {
277    fn verify(&self, message: &[u8], signature: &SimpleSignature) -> Result<(), SignatureError> {
278        let SimpleSignature::Secp256r1 {
279            signature,
280            public_key,
281        } = signature
282        else {
283            return Err(SignatureError::from_source("not a secp256r1 signature"));
284        };
285
286        if public_key.inner() != self.public_key().inner() {
287            return Err(SignatureError::from_source(
288                "public_key in signature does not match",
289            ));
290        }
291
292        <Self as Verifier<Secp256r1Signature>>::verify(self, message, signature)
293    }
294}
295
296impl Verifier<UserSignature> for Secp256r1VerifyingKey {
297    fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
298        let UserSignature::Simple(signature) = signature else {
299            return Err(SignatureError::from_source("not a secp256r1 signature"));
300        };
301
302        <Self as Verifier<SimpleSignature>>::verify(self, message, signature)
303    }
304}
305
306#[derive(Default, Clone, Debug)]
307pub struct Secp256r1Verifier {}
308
309impl Secp256r1Verifier {
310    pub fn new() -> Self {
311        Self {}
312    }
313}
314
315impl Verifier<SimpleSignature> for Secp256r1Verifier {
316    fn verify(&self, message: &[u8], signature: &SimpleSignature) -> Result<(), SignatureError> {
317        let SimpleSignature::Secp256r1 {
318            signature,
319            public_key,
320        } = signature
321        else {
322            return Err(SignatureError::from_source("not a secp256r1 signature"));
323        };
324
325        let verifying_key = Secp256r1VerifyingKey::new(public_key)?;
326
327        verifying_key.verify(message, signature)
328    }
329}
330
331impl Verifier<UserSignature> for Secp256r1Verifier {
332    fn verify(&self, message: &[u8], signature: &UserSignature) -> Result<(), SignatureError> {
333        let UserSignature::Simple(signature) = signature else {
334            return Err(SignatureError::from_source("not a secp256r1 signature"));
335        };
336
337        <Self as Verifier<SimpleSignature>>::verify(self, message, signature)
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use iota_types::PersonalMessage;
344    use test_strategy::proptest;
345    #[cfg(target_arch = "wasm32")]
346    use wasm_bindgen_test::wasm_bindgen_test as test;
347
348    use super::*;
349    use crate::{IotaSigner, IotaVerifier};
350
351    // TODO need to export proptest impl from core crate
352    // #[proptest]
353    // fn transaction_signing(signer: Secp256r1PrivateKey, transaction: Transaction)
354    // {     let signature = signer.sign_transaction(&transaction).unwrap();
355    //     let verifier = signer.public_key();
356    //     verifier
357    //         .verify_transaction(&transaction, &signature)
358    //         .unwrap();
359    // }
360
361    #[proptest]
362    fn personal_message_signing(signer: Secp256r1PrivateKey, message: Vec<u8>) {
363        let message = PersonalMessage(message.into());
364        let signature = signer.sign_personal_message(&message).unwrap();
365        let verifying_key = signer.verifying_key();
366        verifying_key
367            .verify_personal_message(&message, &signature)
368            .unwrap();
369
370        let verifier = Secp256r1Verifier::default();
371        verifier
372            .verify_personal_message(&message, &signature)
373            .unwrap();
374    }
375
376    #[test]
377    fn personal_message_signing_fixture() {
378        let key = [
379            167, 44, 116, 0, 51, 221, 254, 179, 210, 44, 93, 196, 125, 155, 85, 94, 29, 41, 13, 60,
380            59, 132, 69, 84, 176, 217, 77, 49, 25, 113, 118, 125,
381        ];
382        let signer = Secp256r1PrivateKey::new(key);
383
384        let message = PersonalMessage(b"hello".into());
385        let sig = signer.sign_personal_message(&message).unwrap();
386        let external_sig = "AlqWPdkIE2bZAUquKv2Tdh9i+Ih+rVSQXH/YsgvwkmeOJR0YLjL/kadivoPtiQkvZBQ1ZI8eDZxe8SaLniwoT88Dh+/vAuGf1UrouFTdefpBEWn3apy8x3EexN5c5ESzGDc=";
387        let b64 = sig.to_base64();
388        assert_eq!(external_sig, b64);
389    }
390}