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