referrerpolicy=no-referrer-when-downgrade

sp_core/
ecdsa.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Simple ECDSA secp256k1 API.
19
20use crate::{
21	crypto::{
22		CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes,
23		SecretStringError, SignatureBytes,
24	},
25	proof_of_possession::NonAggregatable,
26};
27
28#[cfg(not(feature = "std"))]
29use alloc::vec::Vec;
30use k256::ecdsa::{SigningKey as SecretKey, VerifyingKey};
31
32#[cfg(feature = "full_crypto")]
33type NativeSignature = (k256::ecdsa::Signature, k256::ecdsa::RecoveryId);
34
35/// An identifier used to match public keys against ecdsa keys
36pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds");
37
38/// The byte length of public key
39pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 33;
40
41/// The byte length of signature
42pub const SIGNATURE_SERIALIZED_SIZE: usize = 65;
43
44/// Returns `true` if the S component of a 65-byte ECDSA signature (R||S||V) is in
45/// the low range (s <= N/2), i.e. the signature is canonical.
46pub fn is_signature_normalized(sig: &[u8; 65]) -> bool {
47	let Ok(parsed) = k256::ecdsa::Signature::try_from(&sig[..64]) else {
48		return false;
49	};
50	parsed.normalize_s().is_none()
51}
52
53#[doc(hidden)]
54#[derive(Clone)]
55pub struct EcdsaTag;
56
57#[doc(hidden)]
58#[derive(Clone)]
59pub struct EcdsaKeccakTag;
60
61/// The secret seed.
62///
63/// The raw secret seed, which can be used to create the `Pair`.
64type Seed = [u8; 32];
65
66#[doc(hidden)]
67pub type GenericPublic<TAG> = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, TAG>;
68
69/// The ECDSA compressed public key.
70///
71/// Uses blake2 during key recovery.
72pub type Public = GenericPublic<EcdsaTag>;
73
74/// The ECDSA compressed public key.
75///
76/// Uses keccak during key recovery.
77pub type KeccakPublic = GenericPublic<EcdsaKeccakTag>;
78
79impl<TAG> GenericPublic<TAG> {
80	/// Create a new instance from the given full public key.
81	///
82	/// This will convert the full public key into the compressed format.
83	pub fn from_full(full: &[u8]) -> Result<Self, ()> {
84		let mut tagged_full = [0u8; 65];
85		let full = if full.len() == 64 {
86			// Tag it as uncompressed public key.
87			tagged_full[0] = 0x04;
88			tagged_full[1..].copy_from_slice(full);
89			&tagged_full
90		} else {
91			full
92		};
93		let pubkey = VerifyingKey::from_sec1_bytes(&full);
94		pubkey.map(|k| k.into()).map_err(|_| ())
95	}
96}
97
98impl<TAG> PartialEq<[u8; 33]> for GenericPublic<TAG> {
99	fn eq(&self, other: &[u8; 33]) -> bool {
100		&self.0 == other
101	}
102}
103
104impl<TAG> From<VerifyingKey> for GenericPublic<TAG> {
105	fn from(pubkey: VerifyingKey) -> Self {
106		Self::try_from(&pubkey.to_sec1_bytes()[..])
107			.expect("Valid key is serializable to [u8; 33]. qed.")
108	}
109}
110
111#[cfg(feature = "full_crypto")]
112impl<TAG> From<GenericPair<GenericPublic<TAG>>> for GenericPublic<TAG> {
113	fn from(x: GenericPair<GenericPublic<TAG>>) -> Self {
114		x.public
115	}
116}
117
118#[doc(hidden)]
119pub type GenericSignature<PUBLIC> = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, PUBLIC>;
120
121/// A signature (a 512-bit value, plus 8 bits for recovery ID).
122///
123/// Uses blake2 during key recovery.
124pub type Signature = GenericSignature<Public>;
125
126/// A signature (a 512-bit value, plus 8 bits for recovery ID).
127///
128/// Uses keccak during key recovery.
129pub type KeccakSignature = GenericSignature<KeccakPublic>;
130
131/// A signature that allows recovering the public key from a message.
132pub trait Recover: seal::Sealed {
133	/// The public key that will be recovered from the signature.
134	type Public;
135
136	/// Recover the public key from this signature and a pre-hashed message.
137	fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public>;
138
139	/// Recover the public key from this signature and a message.
140	fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public>;
141}
142
143impl<PUBLIC: From<VerifyingKey>> GenericSignature<PUBLIC> {
144	/// Recover the public key from this signature and a pre-hashed message.
145	pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option<PUBLIC> {
146		let rid = k256::ecdsa::RecoveryId::from_byte(self.0[64])?;
147		let sig = k256::ecdsa::Signature::from_bytes((&self.0[..64]).into()).ok()?;
148		// Recovery is a primitive operation and historically accepted high-S signatures. k256's
149		// verifier rejects them, so normalize here and adjust the recovery ID to preserve behavior.
150		let (sig, rid) = if let Some(normalized) = sig.normalize_s() {
151			(normalized, k256::ecdsa::RecoveryId::new(!rid.is_y_odd(), rid.is_x_reduced()))
152		} else {
153			(sig, rid)
154		};
155		VerifyingKey::recover_from_prehash(message, &sig, rid).map(From::from).ok()
156	}
157}
158
159/// Proof of Possession is the same as Signature.
160///
161/// Uses blake2 during key recovery.
162pub type ProofOfPossession = Signature;
163
164/// Proof of Possession is the same as Signature.
165///
166/// Uses keccak during key recovery.
167pub type KeccakProofOfPossession = KeccakSignature;
168
169impl Signature {
170	/// Recover the public key from this signature and a message.
171	pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Public> {
172		self.recover_prehashed(&sp_crypto_hashing::blake2_256(message.as_ref()))
173	}
174}
175
176impl KeccakSignature {
177	/// Recover the public key from this signature and a message.
178	pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<KeccakPublic> {
179		self.recover_prehashed(&sp_crypto_hashing::keccak_256(message.as_ref()))
180	}
181}
182
183impl Recover for Signature {
184	type Public = Public;
185
186	fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public> {
187		self.recover_prehashed(message)
188	}
189
190	fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public> {
191		self.recover(message)
192	}
193}
194
195impl Recover for KeccakSignature {
196	type Public = KeccakPublic;
197
198	fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public> {
199		self.recover_prehashed(message)
200	}
201
202	fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public> {
203		self.recover(message)
204	}
205}
206
207impl<PUBLIC> From<(k256::ecdsa::Signature, k256::ecdsa::RecoveryId)> for GenericSignature<PUBLIC> {
208	fn from(recsig: (k256::ecdsa::Signature, k256::ecdsa::RecoveryId)) -> Self {
209		let mut r = Self::default();
210		r.0[..64].copy_from_slice(&recsig.0.to_bytes());
211		r.0[64] = recsig.1.to_byte();
212		r
213	}
214}
215
216/// Derive a single hard junction.
217fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
218	use codec::Encode;
219	("Secp256k1HDKD", secret_seed, cc).using_encoded(sp_crypto_hashing::blake2_256)
220}
221
222#[derive(Clone)]
223#[doc(hidden)]
224pub struct GenericPair<PUBLIC> {
225	public: PUBLIC,
226	secret: SecretKey,
227}
228
229/// An ecdsa key pair using the blake2 algorithm for hashing the message.
230pub type Pair = GenericPair<Public>;
231
232/// An ecdsa key pair using the keccak algorithm for hashing the message.
233pub type KeccakPair = GenericPair<KeccakPublic>;
234
235impl TraitPair for Pair {
236	type Public = Public;
237	type Seed = Seed;
238	type Signature = Signature;
239	type ProofOfPossession = ProofOfPossession;
240
241	fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
242		Self::from_seed_slice(seed_slice)
243	}
244
245	fn derive<Iter: Iterator<Item = DeriveJunction>>(
246		&self,
247		path: Iter,
248		_seed: Option<Seed>,
249	) -> Result<(Self, Option<Seed>), DeriveError> {
250		self.derive(path)
251	}
252
253	fn public(&self) -> Self::Public {
254		self.public
255	}
256
257	#[cfg(feature = "full_crypto")]
258	fn sign(&self, message: &[u8]) -> Self::Signature {
259		self.sign(message)
260	}
261
262	/// Verify a signature on a message. Returns true if the signature is good.
263	fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Public) -> bool {
264		Self::verify(sig, message, public)
265	}
266
267	/// Return a vec filled with raw data.
268	fn to_raw_vec(&self) -> Vec<u8> {
269		self.to_raw_vec()
270	}
271}
272
273impl TraitPair for KeccakPair {
274	type Public = KeccakPublic;
275	type Seed = Seed;
276	type Signature = KeccakSignature;
277	type ProofOfPossession = KeccakProofOfPossession;
278
279	fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
280		Self::from_seed_slice(seed_slice)
281	}
282
283	fn derive<Iter: Iterator<Item = DeriveJunction>>(
284		&self,
285		path: Iter,
286		_seed: Option<Seed>,
287	) -> Result<(Self, Option<Seed>), DeriveError> {
288		self.derive(path)
289	}
290
291	fn public(&self) -> Self::Public {
292		self.public
293	}
294
295	#[cfg(feature = "full_crypto")]
296	fn sign(&self, message: &[u8]) -> Self::Signature {
297		self.sign(message)
298	}
299
300	/// Verify a signature on a message. Returns true if the signature is good.
301	fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool {
302		Self::verify(sig, message, public)
303	}
304
305	/// Return a vec filled with raw data.
306	fn to_raw_vec(&self) -> Vec<u8> {
307		self.to_raw_vec()
308	}
309}
310
311impl<PUBLIC> GenericPair<PUBLIC>
312where
313	Self: TraitPair<Seed = Seed, Signature: Recover>,
314	<<Self as TraitPair>::Signature as Recover>::Public: PartialEq<PUBLIC>,
315	PUBLIC: PartialEq<[u8; 33]>,
316{
317	/// Get the seed for this key.
318	pub fn seed(&self) -> Seed {
319		self.secret.to_bytes().into()
320	}
321
322	/// Exactly as `from_string` except that if no matches are found then, the the first 32
323	/// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey.
324	#[cfg(feature = "std")]
325	pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Self {
326		Self::from_string(s, password_override).unwrap_or_else(|_| {
327			let mut padded_seed: Seed = [b' '; 32];
328			let len = s.len().min(32);
329			padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]);
330			Self::from_seed(&padded_seed)
331		})
332	}
333
334	/// Verify a signature on a pre-hashed message. Return `true` if the signature is valid
335	/// and thus matches the given `public` key.
336	pub fn verify_prehashed(
337		sig: &<Self as TraitPair>::Signature,
338		message: &[u8; 32],
339		public: &PUBLIC,
340	) -> bool {
341		match sig.recover_prehashed(message) {
342			Some(actual) => actual == *public,
343			None => false,
344		}
345	}
346
347	/// Verify a signature on a message. Returns true if the signature is good.
348	/// Parses Signature using parse_overflowing_slice.
349	#[deprecated(note = "please use `verify` instead")]
350	pub fn verify_deprecated<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &Public) -> bool {
351		let message =
352			libsecp256k1::Message::parse(&sp_crypto_hashing::blake2_256(message.as_ref()));
353
354		let parse_signature_overflowing = |x: [u8; SIGNATURE_SERIALIZED_SIZE]| {
355			let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?;
356			let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?;
357			Some((sig, rid))
358		};
359
360		let (sig, rid) = match parse_signature_overflowing(sig.0) {
361			Some(sigri) => sigri,
362			_ => return false,
363		};
364		match libsecp256k1::recover(&message, &sig, &rid) {
365			Ok(actual) => pubkey == &actual.serialize_compressed(),
366			_ => false,
367		}
368	}
369
370	fn derive<Iter: Iterator<Item = DeriveJunction>>(
371		&self,
372		path: Iter,
373	) -> Result<(Self, Option<Seed>), DeriveError> {
374		let mut acc = self.seed();
375		for j in path {
376			match j {
377				DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath),
378				DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc),
379			}
380		}
381		Ok((Self::from_seed(&acc), Some(acc)))
382	}
383
384	fn verify<M: AsRef<[u8]>>(
385		sig: &<Self as TraitPair>::Signature,
386		message: M,
387		public: &PUBLIC,
388	) -> bool {
389		sig.recover(message).map(|actual| actual == *public).unwrap_or_default()
390	}
391
392	fn to_raw_vec(&self) -> Vec<u8> {
393		self.seed().to_vec()
394	}
395}
396
397impl<PUBLIC: From<VerifyingKey>> GenericPair<PUBLIC> {
398	fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
399		let secret =
400			SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?;
401		Ok(Self { public: VerifyingKey::from(&secret).into(), secret })
402	}
403}
404
405#[cfg(feature = "full_crypto")]
406impl<PUBLIC> GenericPair<PUBLIC>
407where
408	Self: TraitPair,
409	<Self as TraitPair>::Signature: From<NativeSignature>,
410{
411	/// Sign a pre-hashed message
412	pub fn sign_prehashed(&self, message: &[u8; 32]) -> <Self as TraitPair>::Signature {
413		let (raw_sig, recovery_id) = self
414			.secret
415			.sign_prehash_recoverable(message)
416			.expect("Signing can't fail when using 32 bytes message hash. qed.");
417
418		// k256 currently returns low-S signatures, but keep the normalization explicit.
419		let (normalized_sig, adjusted_v) = if let Some(normalized) = raw_sig.normalize_s() {
420			(
421				normalized,
422				k256::ecdsa::RecoveryId::new(!recovery_id.is_y_odd(), recovery_id.is_x_reduced()),
423			)
424		} else {
425			(raw_sig, recovery_id)
426		};
427		(normalized_sig, adjusted_v).into()
428	}
429}
430
431#[cfg(feature = "full_crypto")]
432impl Pair
433where
434	<Self as TraitPair>::Signature: From<NativeSignature>,
435{
436	fn sign(&self, message: &[u8]) -> Signature {
437		self.sign_prehashed(&sp_crypto_hashing::blake2_256(message))
438	}
439}
440
441#[cfg(feature = "full_crypto")]
442impl KeccakPair
443where
444	<Self as TraitPair>::Signature: From<NativeSignature>,
445{
446	fn sign(&self, message: &[u8]) -> KeccakSignature {
447		self.sign_prehashed(&sp_crypto_hashing::keccak_256(message))
448	}
449}
450
451impl CryptoType for Public {
452	type Pair = Pair;
453}
454
455impl CryptoType for KeccakPublic {
456	type Pair = KeccakPair;
457}
458
459impl CryptoType for Signature {
460	type Pair = Pair;
461}
462
463impl CryptoType for KeccakSignature {
464	type Pair = KeccakPair;
465}
466
467impl CryptoType for Pair {
468	type Pair = Self;
469}
470
471impl CryptoType for KeccakPair {
472	type Pair = Self;
473}
474
475impl NonAggregatable for Pair {}
476
477mod seal {
478	pub trait Sealed {}
479	impl Sealed for super::Signature {}
480	impl Sealed for super::KeccakSignature {}
481}
482
483#[cfg(test)]
484mod test {
485	use super::*;
486	use crate::{
487		crypto::{
488			set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry,
489			Ss58Codec, DEV_PHRASE,
490		},
491		proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier},
492	};
493	use serde_json;
494
495	#[test]
496	fn default_phrase_should_be_used() {
497		assert_eq!(
498			Pair::from_string("//Alice///password", None).unwrap().public(),
499			Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
500				.unwrap()
501				.public(),
502		);
503	}
504
505	#[test]
506	fn seed_and_derive_should_work() {
507		let seed = array_bytes::hex2array_unchecked(
508			"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
509		);
510		let pair = Pair::from_seed(&seed);
511		assert_eq!(pair.seed(), seed);
512		let path = vec![DeriveJunction::Hard([0u8; 32])];
513		let derived = pair.derive(path.into_iter()).ok().unwrap();
514		assert_eq!(
515			derived.0.seed(),
516			array_bytes::hex2array_unchecked::<_, 32>(
517				"b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61"
518			)
519		);
520	}
521
522	#[test]
523	fn test_vector_should_work() {
524		let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
525			"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
526		));
527		let public = pair.public();
528		assert_eq!(
529			public,
530			Public::from_full(
531				&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
532			).unwrap(),
533		);
534		let message = b"";
535		let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
536		let signature = Signature::from_raw(signature);
537		assert!(pair.sign(&message[..]) == signature);
538		assert!(Pair::verify(&signature, &message[..], &public));
539	}
540
541	#[test]
542	fn test_vector_by_string_should_work() {
543		let pair = Pair::from_string(
544			"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
545			None,
546		)
547		.unwrap();
548		let public = pair.public();
549		assert_eq!(
550			public,
551			Public::from_full(
552				&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
553			).unwrap(),
554		);
555		let message = b"";
556		let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
557		let signature = Signature::from_raw(signature);
558		assert!(pair.sign(&message[..]) == signature);
559		assert!(Pair::verify(&signature, &message[..], &public));
560	}
561
562	#[test]
563	fn generated_pair_should_work() {
564		let (pair, _) = Pair::generate();
565		let public = pair.public();
566		let message = b"Something important";
567		let signature = pair.sign(&message[..]);
568		assert!(Pair::verify(&signature, &message[..], &public));
569		assert!(!Pair::verify(&signature, b"Something else", &public));
570	}
571
572	#[test]
573	fn generated_pair_should_work_keccak() {
574		let (pair, _) = KeccakPair::generate();
575		let public = pair.public();
576		let message = b"Something important";
577		let signature = pair.sign(&message[..]);
578		assert!(KeccakPair::verify(&signature, &message[..], &public));
579		assert!(!KeccakPair::verify(&signature, b"Something else", &public));
580	}
581
582	#[test]
583	fn seeded_pair_should_work() {
584		let pair = Pair::from_seed(b"12345678901234567890123456789012");
585		let public = pair.public();
586		assert_eq!(
587			public,
588			Public::from_full(
589				&array_bytes::hex2bytes_unchecked("5676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba995840f3de562156558efbfdac3f16af0065e5f66795f4dd8262a228ef8c6d813"),
590			).unwrap(),
591		);
592		let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000");
593		let signature = pair.sign(&message[..]);
594		println!("Correct signature: {:?}", signature);
595		assert!(Pair::verify(&signature, &message[..], &public));
596		assert!(!Pair::verify(&signature, "Other message", &public));
597	}
598
599	#[test]
600	fn generate_with_phrase_recovery_possible() {
601		let (pair1, phrase, _) = Pair::generate_with_phrase(None);
602		let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
603
604		assert_eq!(pair1.public(), pair2.public());
605	}
606
607	#[test]
608	fn generate_with_password_phrase_recovery_possible() {
609		let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
610		let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap();
611
612		assert_eq!(pair1.public(), pair2.public());
613	}
614
615	#[test]
616	fn generate_with_phrase_should_be_recoverable_with_from_string() {
617		let (pair, phrase, seed) = Pair::generate_with_phrase(None);
618		let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
619		assert_eq!(pair.public(), repair_seed.public());
620		assert_eq!(pair.secret, repair_seed.secret);
621		let (repair_phrase, reseed) =
622			Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
623		assert_eq!(seed, reseed);
624		assert_eq!(pair.public(), repair_phrase.public());
625		assert_eq!(pair.secret, repair_phrase.secret);
626		let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
627		assert_eq!(pair.public(), repair_string.public());
628		assert_eq!(pair.secret, repair_string.secret);
629	}
630
631	#[test]
632	fn password_does_something() {
633		let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
634		let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
635
636		assert_ne!(pair1.public(), pair2.public());
637		assert_ne!(pair1.secret, pair2.secret);
638	}
639
640	#[test]
641	fn ss58check_roundtrip_works() {
642		let pair = Pair::from_seed(b"12345678901234567890123456789012");
643		let public = pair.public();
644		let s = public.to_ss58check();
645		println!("Correct: {}", s);
646		let cmp = Public::from_ss58check(&s).unwrap();
647		assert_eq!(cmp, public);
648	}
649
650	#[test]
651	fn ss58check_format_check_works() {
652		let pair = Pair::from_seed(b"12345678901234567890123456789012");
653		let public = pair.public();
654		let format = Ss58AddressFormatRegistry::Reserved46Account.into();
655		let s = public.to_ss58check_with_version(format);
656		assert_eq!(Public::from_ss58check_with_version(&s), Err(PublicError::FormatNotAllowed));
657	}
658
659	#[test]
660	fn ss58check_full_roundtrip_works() {
661		let pair = Pair::from_seed(b"12345678901234567890123456789012");
662		let public = pair.public();
663		let format = Ss58AddressFormatRegistry::PolkadotAccount.into();
664		let s = public.to_ss58check_with_version(format);
665		let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
666		assert_eq!(k, public);
667		assert_eq!(f, format);
668
669		let format = Ss58AddressFormat::custom(64);
670		let s = public.to_ss58check_with_version(format);
671		let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
672		assert_eq!(k, public);
673		assert_eq!(f, format);
674	}
675
676	#[test]
677	fn ss58check_custom_format_works() {
678		// We need to run this test in its own process to not interfere with other tests running in
679		// parallel and also relying on the ss58 version.
680		if std::env::var("RUN_CUSTOM_FORMAT_TEST") == Ok("1".into()) {
681			use crate::crypto::Ss58AddressFormat;
682			// temp save default format version
683			let default_format = crate::crypto::default_ss58_version();
684			// set current ss58 version is custom "200" `Ss58AddressFormat::Custom(200)`
685
686			set_default_ss58_version(Ss58AddressFormat::custom(200));
687			// custom addr encoded by version 200
688			let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV";
689			Public::from_ss58check(addr).unwrap();
690
691			set_default_ss58_version(default_format);
692			// set current ss58 version to default version
693			let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C";
694			Public::from_ss58check(addr).unwrap();
695
696			println!("CUSTOM_FORMAT_SUCCESSFUL");
697		} else {
698			let executable = std::env::current_exe().unwrap();
699			let output = std::process::Command::new(executable)
700				.env("RUN_CUSTOM_FORMAT_TEST", "1")
701				.args(&["--nocapture", "ss58check_custom_format_works"])
702				.output()
703				.unwrap();
704
705			let output = String::from_utf8(output.stdout).unwrap();
706			assert!(output.contains("CUSTOM_FORMAT_SUCCESSFUL"));
707		}
708	}
709
710	#[test]
711	fn signature_serialization_works() {
712		let pair = Pair::from_seed(b"12345678901234567890123456789012");
713		let message = b"Something important";
714		let signature = pair.sign(&message[..]);
715		let serialized_signature = serde_json::to_string(&signature).unwrap();
716		// Signature is 65 bytes, so 130 chars + 2 quote chars
717		assert_eq!(serialized_signature.len(), SIGNATURE_SERIALIZED_SIZE * 2 + 2);
718		let signature = serde_json::from_str(&serialized_signature).unwrap();
719		assert!(Pair::verify(&signature, &message[..], &pair.public()));
720	}
721
722	#[test]
723	fn signature_serialization_doesnt_panic() {
724		fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
725			serde_json::from_str(text)
726		}
727		assert!(deserialize_signature("Not valid json.").is_err());
728		assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
729		// Poorly-sized
730		assert!(deserialize_signature("\"abc123\"").is_err());
731	}
732
733	#[test]
734	fn sign_prehashed_works() {
735		let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
736
737		// sign_prehashed always produces a low-S (normalized) signature
738		let msg = [0u8; 32];
739		let sig1 = pair.sign_prehashed(&msg);
740		assert!(
741			is_signature_normalized(&sig1.0),
742			"sign_prehashed should always produce a low-S signature"
743		);
744
745		// sign_prehashed is deterministic
746		let sig1_again = pair.sign_prehashed(&msg);
747		assert_eq!(sig1, sig1_again, "sign_prehashed should be deterministic");
748
749		// prehashed signature differs from sign() (which blake2-hashes first)
750		let sig2 = pair.sign(&msg);
751		assert_ne!(sig1, sig2);
752
753		// using pre-hashed `msg` works
754		let msg = b"this should be hashed";
755		let sig1 = pair.sign_prehashed(&sp_crypto_hashing::blake2_256(msg));
756		let sig2 = pair.sign(msg);
757		assert_eq!(sig1, sig2);
758	}
759
760	#[test]
761	fn verify_prehashed_works() {
762		let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
763
764		// `msg` and `sig` match
765		let msg = sp_crypto_hashing::blake2_256(b"this should be hashed");
766		let sig = pair.sign_prehashed(&msg);
767		assert!(Pair::verify_prehashed(&sig, &msg, &pair.public()));
768
769		// `msg` and `sig` don't match
770		let msg = sp_crypto_hashing::blake2_256(b"this is a different message");
771		assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public()));
772	}
773
774	#[test]
775	fn recover_prehashed_works() {
776		let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
777
778		// recovered key matches signing key
779		let msg = sp_crypto_hashing::blake2_256(b"this should be hashed");
780		let sig = pair.sign_prehashed(&msg);
781		let key = sig.recover_prehashed(&msg).unwrap();
782		assert_eq!(pair.public(), key);
783
784		// recovered key is useable
785		assert!(Pair::verify_prehashed(&sig, &msg, &key));
786
787		// recovered key and signing key don't match
788		let msg = sp_crypto_hashing::blake2_256(b"this is a different message");
789		let key = sig.recover_prehashed(&msg).unwrap();
790		assert_ne!(pair.public(), key);
791	}
792
793	#[test]
794	fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
795		let owner = b"owner";
796		let not_owner = b"not owner";
797		let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
798		let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
799		let proof_of_possession = pair.generate_proof_of_possession(owner);
800		assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
801		assert_eq!(
802			Pair::verify_proof_of_possession(owner, &proof_of_possession, &other_pair.public()),
803			false
804		);
805		assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
806	}
807
808	#[test]
809	fn is_signature_normalized_accepts_low_s() {
810		// A real low-S signature produced by sign_prehashed
811		let pair = Pair::from_seed(b"12345678901234567890123456789012");
812		let msg = sp_crypto_hashing::blake2_256(b"low-s test");
813		let sig = pair.sign_prehashed(&msg);
814		assert!(is_signature_normalized(&sig.0));
815	}
816
817	#[test]
818	fn is_signature_normalized_rejects_high_s() {
819		// Create a high-S signature by computing S' = N - S from a valid low-S signature
820		let pair = Pair::from_seed(b"12345678901234567890123456789012");
821		let msg = sp_crypto_hashing::blake2_256(b"high-s test");
822		let sig = pair.sign_prehashed(&msg);
823
824		let order: [u8; 32] = [
825			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
826			0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c,
827			0xd0, 0x36, 0x41, 0x41,
828		];
829
830		let s_bytes: [u8; 32] = sig.0[32..64].try_into().unwrap();
831		let mut s_prime = [0u8; 32];
832		let mut borrow = 0i16;
833		for i in (0..32).rev() {
834			let diff = order[i] as i16 - s_bytes[i] as i16 - borrow;
835			if diff < 0 {
836				s_prime[i] = (diff + 256) as u8;
837				borrow = 1;
838			} else {
839				s_prime[i] = diff as u8;
840				borrow = 0;
841			}
842		}
843
844		let mut high_s_sig = [0u8; 65];
845		high_s_sig[0..32].copy_from_slice(&sig.0[0..32]);
846		high_s_sig[32..64].copy_from_slice(&s_prime);
847		high_s_sig[64] = sig.0[64] ^ 1;
848		assert!(!is_signature_normalized(&high_s_sig));
849	}
850
851	#[test]
852	fn sign_prehashed_produces_low_s() {
853		for i in 1..21u8 {
854			let seed = [i; 32];
855			let pair = Pair::from_seed(&seed);
856			let msg = sp_crypto_hashing::blake2_256(&[i]);
857			let sig = pair.sign_prehashed(&msg);
858			assert!(
859				is_signature_normalized(&sig.0),
860				"sign_prehashed produced high-S for seed {}",
861				i
862			);
863		}
864	}
865
866	#[test]
867	fn malleable_signature_is_rejected_by_normalization_check() {
868		let order: [u8; 32] = [
869			0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
870			0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c,
871			0xd0, 0x36, 0x41, 0x41,
872		];
873
874		let pair = Pair::from_seed(b"12345678901234567890123456789012");
875		let msg = sp_crypto_hashing::blake2_256(b"malleable test");
876		let sig = pair.sign_prehashed(&msg);
877
878		// Original signature should be low-S
879		assert!(is_signature_normalized(&sig.0));
880
881		let s_bytes: [u8; 32] = sig.0[32..64].try_into().unwrap();
882		let mut s_prime = [0u8; 32];
883		let mut borrow = 0i16;
884		for i in (0..32).rev() {
885			let diff = order[i] as i16 - s_bytes[i] as i16 - borrow;
886			if diff < 0 {
887				s_prime[i] = (diff + 256) as u8;
888				borrow = 1;
889			} else {
890				s_prime[i] = diff as u8;
891				borrow = 0;
892			}
893		}
894
895		let mut malleable_sig_bytes = [0u8; 65];
896		malleable_sig_bytes[0..32].copy_from_slice(&sig.0[0..32]);
897		malleable_sig_bytes[32..64].copy_from_slice(&s_prime);
898		malleable_sig_bytes[64] = sig.0[64] ^ 1;
899
900		// The malleable signature should have high-S (since original was low-S, complement is high)
901		assert!(
902			!is_signature_normalized(&malleable_sig_bytes),
903			"malleable signature should be rejected as high-S"
904		);
905
906		// Raw recovery continues to accept high-S signatures. Protocols that require low-S enforce
907		// it before calling recovery.
908		let malleable_sig = Signature::from_raw(malleable_sig_bytes);
909		assert!(Pair::verify_prehashed(&malleable_sig, &msg, &pair.public()));
910	}
911}