Skip to main content

sui_crypto/
secp256r1.rs

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