substrate_crypto_light/
ecdsa.rs1#[cfg(feature = "std")]
2use std::vec;
3
4#[cfg(not(feature = "std"))]
5use alloc::vec;
6
7use k256::ecdsa::{signature::hazmat::PrehashVerifier, SigningKey, VerifyingKey};
8use parity_scale_codec::{Decode, Encode};
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::{
12 common::{blake2_256, entropy_to_big_seed, DeriveJunction, FullDerivation, HASH_256_LEN},
13 error::Error,
14};
15
16pub const ID: &str = "Secp256k1HDKD";
17pub const PUBLIC_LEN: usize = 33;
18pub const SIGNATURE_LEN: usize = 65;
19
20#[derive(Clone, Copy, Decode, Debug, Encode, Eq, Ord, PartialEq, PartialOrd)]
21pub struct Public(pub [u8; PUBLIC_LEN]);
22
23impl Public {
24 pub fn verify(&self, msg: &[u8], signature: &Signature) -> bool {
25 let Ok(signature) = k256::ecdsa::Signature::from_slice(signature.0[..64].as_ref()) else {
26 return false;
27 };
28 let Ok(verifying_key) = VerifyingKey::from_sec1_bytes(self.0.as_ref()) else {
29 return false;
30 };
31 verifying_key
32 .verify_prehash(&blake2_256(msg), &signature)
33 .is_ok()
34 }
35}
36
37#[derive(Clone, Copy, Decode, Debug, Encode, Eq, Ord, PartialEq, PartialOrd)]
38pub struct Signature(pub [u8; SIGNATURE_LEN]);
39
40#[derive(ZeroizeOnDrop)]
41pub struct Pair(SigningKey);
42
43impl Pair {
44 pub fn from_entropy_and_pwd(entropy: &[u8], pwd: &str) -> Result<Self, Error> {
45 let mut big_seed = entropy_to_big_seed(entropy, pwd)?;
46 let seed = &big_seed[..HASH_256_LEN];
47 let signing_key_result = SigningKey::from_bytes(seed.as_ref().into());
48 big_seed.zeroize();
49 match signing_key_result {
50 Ok(signing_key) => Ok(Pair(signing_key)),
51 Err(_) => Err(Error::EcdsaPairGen),
52 }
53 }
54
55 pub fn from_entropy_and_full_derivation(
56 entropy: &[u8],
57 full_derivation: FullDerivation,
58 ) -> Result<Self, Error> {
59 let mut pair = Self::from_entropy_and_pwd(entropy, full_derivation.password.unwrap_or(""))?;
60 for junction in full_derivation.junctions.iter() {
61 match junction {
62 DeriveJunction::Hard(inner) => {
63 let mut blake2b_state = blake2b_simd::Params::new()
66 .hash_length(HASH_256_LEN)
67 .to_state();
68 blake2b_state.update(&ID.encode());
69 blake2b_state.update(pair.0.to_bytes().as_slice());
70 blake2b_state.update(inner);
71 let bytes = blake2b_state.finalize();
72 pair = Pair(
73 SigningKey::from_bytes(bytes.as_ref().into())
74 .map_err(|_| Error::EcdsaPairGen)?,
75 );
76 }
77 DeriveJunction::Soft(_) => return Err(Error::NoSoftDerivationEcdsa),
78 }
79 }
80 Ok(pair)
81 }
82
83 pub fn sign(&self, msg: &[u8]) -> Result<Signature, Error> {
84 let (signature_ecdsa, recid) = self
85 .0
86 .sign_prehash_recoverable(&blake2_256(msg))
87 .map_err(|_| Error::EcdsaSignatureGen)?;
88 Ok(Signature(
89 [
90 signature_ecdsa.to_bytes().as_slice().to_vec(),
91 vec![recid.to_byte()],
92 ]
93 .concat()
94 .try_into()
95 .map_err(|_| Error::EcdsaSignatureLength)?,
96 ))
97 }
98
99 pub fn public(&self) -> Result<Public, Error> {
100 Ok(Public(
101 self.0
102 .verifying_key()
103 .to_encoded_point(true)
104 .as_bytes()
105 .try_into()
106 .map_err(|_| Error::EcdsaPublicKeyLength)?,
107 ))
108 }
109}
110
111#[cfg(feature = "std")]
112#[cfg(test)]
113mod tests {
114
115 use mnemonic_external::{regular::InternalWordList, WordSet};
116 use sp_core::{crypto::Pair, ecdsa};
117
118 #[cfg(feature = "std")]
119 use std::format;
120
121 #[cfg(not(feature = "std"))]
122 use alloc::format;
123
124 use crate::common::{cut_path, ALICE_WORDS};
125 use crate::ecdsa::{Pair as EcdsaPair, Public as EcdsaPublic, Signature as EcdsaSignature};
126
127 fn identical_ecdsa(derivation: &str, password: &str) {
128 let phrase_with_derivations = format!("{ALICE_WORDS}{derivation}");
130
131 let path_and_pwd = format!("{derivation}///{password}");
133
134 let msg = b"super important thing to sign";
136
137 let pair_from_core =
139 ecdsa::Pair::from_string(&phrase_with_derivations, Some(password)).unwrap();
140 let public_from_core = pair_from_core.public().0;
141 let signature_from_core = pair_from_core.sign(msg).0;
142
143 let internal_word_list = InternalWordList;
145 let mut word_set = WordSet::new();
146 for word in ALICE_WORDS.split(' ') {
147 word_set.add_word(word, &internal_word_list).unwrap();
148 }
149 let entropy = word_set.to_entropy().unwrap();
150
151 let full_derivation = cut_path(&path_and_pwd).unwrap();
153
154 let pair = EcdsaPair::from_entropy_and_full_derivation(&entropy, full_derivation).unwrap();
156 let public = pair.public().unwrap().0;
157 let signature = pair.sign(msg).unwrap().0;
158
159 assert_eq!(public_from_core, public);
160
161 let signature_import_into_core = ecdsa::Signature::from_raw(signature);
164 let public_import_into_core = ecdsa::Public::from_raw(public);
165 assert!(ecdsa::Pair::verify(
166 &signature_import_into_core,
167 msg,
168 &public_import_into_core
169 ));
170
171 let signature_import = EcdsaSignature(signature_from_core);
174 let public_import = EcdsaPublic(public_from_core);
175 assert!(public_import.verify(msg, &signature_import));
176 }
177
178 #[test]
179 fn test_identical_ecdsa_1() {
180 identical_ecdsa(
181 "//hard//alicealicealicealicealicealicealicealice",
182 "trickytrick",
183 )
184 }
185
186 #[test]
187 fn test_identical_ecdsa_2() {
188 identical_ecdsa("//1", "pwd")
189 }
190}