Skip to main content

qp_dilithium_crypto/
traits.rs

1use super::types::{
2	DilithiumPair, DilithiumPublic, DilithiumSignatureScheme, DilithiumSigner, Error,
3	WrappedPublicBytes, WrappedSignatureBytes,
4};
5
6use crate::{DilithiumSignature, DilithiumSignatureWithPublic};
7use alloc::vec::Vec;
8use qp_poseidon_core::hash_bytes;
9use sp_core::{
10	crypto::{Derive, Public, PublicBytes, Signature, SignatureBytes},
11	ByteArray, H256,
12};
13use sp_runtime::{
14	traits::{IdentifyAccount, Verify},
15	AccountId32, CryptoType,
16};
17
18/// Verifies a Dilithium ML-DSA-87 signature
19///
20/// This function performs signature verification using the Dilithium post-quantum
21/// cryptographic signature scheme (ML-DSA-87). It validates that the given signature
22/// was created by the holder of the private key corresponding to the public key.
23///
24/// # Arguments
25/// * `pub_key` - The public key bytes (must be valid Dilithium public key)
26/// * `msg` - The message that was signed
27/// * `sig` - The signature bytes to verify
28///
29/// # Returns
30/// `true` if the signature is valid and verification succeeds, `false` otherwise
31///
32/// # Examples
33/// ```ignore
34/// use qp_dilithium_crypto::verify;
35///
36/// let valid = verify(&public_key_bytes, &message, &signature_bytes);
37/// if valid {
38///     println!("Signature is valid!");
39/// }
40/// ```
41pub fn verify(pub_key: &[u8], msg: &[u8], sig: &[u8]) -> bool {
42	use qp_rusty_crystals_dilithium::ml_dsa_87::PublicKey;
43	match PublicKey::from_bytes(pub_key) {
44		Ok(pk) => pk.verify(msg, sig, None),
45		Err(e) => {
46			log::warn!("public key failed to deserialize {:?}", e);
47			false
48		},
49	}
50}
51
52//
53// Trait implementations for WrappedPublicBytes
54//
55
56impl<const N: usize, SubTag> Derive for WrappedPublicBytes<N, SubTag> {}
57impl<const N: usize, SubTag> AsMut<[u8]> for WrappedPublicBytes<N, SubTag> {
58	fn as_mut(&mut self) -> &mut [u8] {
59		self.0.as_mut()
60	}
61}
62impl<const N: usize, SubTag> AsRef<[u8]> for WrappedPublicBytes<N, SubTag> {
63	fn as_ref(&self) -> &[u8] {
64		self.0.as_slice()
65	}
66}
67impl<const N: usize, SubTag> TryFrom<&[u8]> for WrappedPublicBytes<N, SubTag> {
68	type Error = ();
69	fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
70		PublicBytes::from_slice(data)
71			.map(|bytes| WrappedPublicBytes(bytes))
72			.map_err(|_| ())
73	}
74}
75impl<const N: usize, SubTag> ByteArray for WrappedPublicBytes<N, SubTag> {
76	fn as_slice(&self) -> &[u8] {
77		self.0.as_slice()
78	}
79	const LEN: usize = N;
80	fn from_slice(data: &[u8]) -> Result<Self, ()> {
81		PublicBytes::from_slice(data)
82			.map(|bytes| WrappedPublicBytes(bytes))
83			.map_err(|_| ())
84	}
85	fn to_raw_vec(&self) -> Vec<u8> {
86		self.0.as_slice().to_vec()
87	}
88}
89impl<const N: usize, SubTag> CryptoType for WrappedPublicBytes<N, SubTag> {
90	type Pair = DilithiumPair;
91}
92impl<const N: usize, SubTag: Clone + Eq> Public for WrappedPublicBytes<N, SubTag> {}
93
94impl<const N: usize, SubTag> Default for WrappedPublicBytes<N, SubTag> {
95	fn default() -> Self {
96		WrappedPublicBytes(PublicBytes::default())
97	}
98}
99impl<const N: usize, SubTag> alloc::fmt::Debug for WrappedPublicBytes<N, SubTag> {
100	#[cfg(feature = "std")]
101	fn fmt(&self, f: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
102		use sp_core::bytes::to_hex;
103
104		write!(f, "{}", to_hex(self.0.as_ref(), false))
105	}
106
107	#[cfg(not(feature = "std"))]
108	fn fmt(&self, _: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
109		Ok(())
110	}
111}
112
113impl IdentifyAccount for DilithiumPublic {
114	type AccountId = AccountId32;
115	fn into_account(self) -> Self::AccountId {
116		// Use injective encoding for account ID derivation (collision-resistant for security)
117		AccountId32::new(hash_bytes(self.0.as_slice()))
118	}
119}
120
121pub struct WormholeAddress(pub H256);
122
123// AccountID32 for a wormhole address is the same as the address itself
124impl IdentifyAccount for WormholeAddress {
125	type AccountId = AccountId32;
126	fn into_account(self) -> Self::AccountId {
127		AccountId32::new(self.0.as_bytes().try_into().unwrap())
128	}
129}
130
131//
132// Trait implementations for WrappedSignatureBytes
133//
134impl<const N: usize, SubTag> Derive for WrappedSignatureBytes<N, SubTag> {}
135impl<const N: usize, SubTag> AsMut<[u8]> for WrappedSignatureBytes<N, SubTag> {
136	fn as_mut(&mut self) -> &mut [u8] {
137		self.0.as_mut()
138	}
139}
140impl<const N: usize, SubTag> AsRef<[u8]> for WrappedSignatureBytes<N, SubTag> {
141	fn as_ref(&self) -> &[u8] {
142		self.0.as_slice()
143	}
144}
145impl<const N: usize, SubTag> TryFrom<&[u8]> for WrappedSignatureBytes<N, SubTag> {
146	type Error = ();
147	fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
148		SignatureBytes::from_slice(data)
149			.map(|bytes| WrappedSignatureBytes(bytes))
150			.map_err(|_| ())
151	}
152}
153impl<const N: usize, SubTag> ByteArray for WrappedSignatureBytes<N, SubTag> {
154	fn as_slice(&self) -> &[u8] {
155		self.0.as_slice()
156	}
157	const LEN: usize = N;
158	fn from_slice(data: &[u8]) -> Result<Self, ()> {
159		SignatureBytes::from_slice(data)
160			.map(|bytes| WrappedSignatureBytes(bytes))
161			.map_err(|_| ())
162	}
163	fn to_raw_vec(&self) -> Vec<u8> {
164		self.0.as_slice().to_vec()
165	}
166}
167impl<const N: usize, SubTag> CryptoType for WrappedSignatureBytes<N, SubTag> {
168	type Pair = DilithiumPair;
169}
170impl<const N: usize, SubTag: Clone + Eq> Signature for WrappedSignatureBytes<N, SubTag> {}
171
172impl<const N: usize, SubTag> Default for WrappedSignatureBytes<N, SubTag> {
173	fn default() -> Self {
174		WrappedSignatureBytes(SignatureBytes::default())
175	}
176}
177
178impl<const N: usize, SubTag> alloc::fmt::Debug for WrappedSignatureBytes<N, SubTag> {
179	#[cfg(feature = "std")]
180	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
181		use sp_core::bytes::to_hex;
182
183		write!(f, "{}", to_hex(self.0.as_slice(), false))
184	}
185
186	#[cfg(not(feature = "std"))]
187	fn fmt(&self, _: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
188		Ok(())
189	}
190}
191
192//
193// Trait implementations for DilithiumPair
194//
195
196impl CryptoType for DilithiumPair {
197	type Pair = Self;
198}
199
200//
201// Trait implementations for DilithiumSignatureScheme
202//
203
204impl Verify for DilithiumSignatureScheme {
205	type Signer = DilithiumSigner;
206
207	fn verify<L: sp_runtime::traits::Lazy<[u8]>>(
208		&self,
209		mut msg: L,
210		signer: &<Self::Signer as IdentifyAccount>::AccountId,
211	) -> bool {
212		let Self::Dilithium(sig_public) = self;
213		let account = sig_public.public().clone().into_account();
214		if account != *signer {
215			return false;
216		}
217		verify(sig_public.public().as_ref(), msg.get(), sig_public.signature().as_ref())
218	}
219}
220
221//
222// Trait implementations for DilithiumSigner
223//
224impl From<DilithiumPublic> for DilithiumSigner {
225	fn from(x: DilithiumPublic) -> Self {
226		Self::Dilithium(x)
227	}
228}
229
230impl IdentifyAccount for DilithiumSigner {
231	type AccountId = AccountId32;
232
233	fn into_account(self) -> AccountId32 {
234		// Use injective encoding for account ID derivation (collision-resistant for security)
235		let Self::Dilithium(who) = self;
236		hash_bytes(who.as_ref()).into()
237	}
238}
239
240impl From<DilithiumPublic> for AccountId32 {
241	fn from(public: DilithiumPublic) -> Self {
242		public.into_account()
243	}
244}
245
246//
247// Implementation methods for DilithiumPair
248//
249
250impl DilithiumPair {
251	pub fn from_seed(seed: &[u8]) -> Result<Self, Error> {
252		let keypair = crate::pair::generate(seed)?;
253		Ok(DilithiumPair { secret: keypair.secret.to_bytes(), public: keypair.public.to_bytes() })
254	}
255
256	pub fn from_keypair(keypair: qp_rusty_crystals_dilithium::ml_dsa_87::Keypair) -> Self {
257		DilithiumPair { secret: keypair.secret.to_bytes(), public: keypair.public.to_bytes() }
258	}
259
260	/// Create DilithiumPair from raw public and secret key bytes.
261	/// Use when reconstructing a pair from stored/serialized key material (e.g. wallet restore).
262	///
263	/// Verifies that the public key corresponds to the secret by signing a test message and
264	/// verifying it. Rejects mismatched or corrupted key pairs that would otherwise cause
265	/// non-obvious signature verification failures downstream.
266	pub fn from_raw(public: &[u8], secret: &[u8]) -> Result<Self, Error> {
267		let keypair = crate::pair::create_keypair(public, secret)?;
268		// Verify public corresponds to secret (create_keypair only deserializes, does not validate)
269		const VALIDATION_MSG: &[u8] = b"qp_dilithium_crypto::from_raw_validation";
270		let sig = keypair.sign(VALIDATION_MSG, None, None).map_err(|_| Error::InvalidSecretKey)?;
271		if !keypair.verify(VALIDATION_MSG, sig.as_ref(), None) {
272			return Err(Error::InvalidPublicKey);
273		}
274		Ok(DilithiumPair { secret: keypair.secret.to_bytes(), public: keypair.public.to_bytes() })
275	}
276
277	pub fn secret_bytes(&self) -> &[u8] {
278		&self.secret
279	}
280
281	pub fn public_bytes(&self) -> &[u8] {
282		&self.public
283	}
284}
285
286impl alloc::fmt::Debug for DilithiumSignatureWithPublic {
287	#[cfg(feature = "std")]
288	fn fmt(&self, f: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
289		write!(
290			f,
291			"DilithiumSignatureWithPublic {{ signature: {:?}, public: {:?} }}",
292			self.signature(),
293			self.public()
294		)
295	}
296
297	#[cfg(not(feature = "std"))]
298	fn fmt(&self, f: &mut alloc::fmt::Formatter) -> alloc::fmt::Result {
299		write!(f, "DilithiumSignatureWithPublic")
300	}
301}
302
303impl From<DilithiumSignatureWithPublic> for DilithiumSignatureScheme {
304	fn from(x: DilithiumSignatureWithPublic) -> Self {
305		Self::Dilithium(x)
306	}
307}
308
309impl TryFrom<DilithiumSignatureScheme> for DilithiumSignatureWithPublic {
310	type Error = ();
311	fn try_from(m: DilithiumSignatureScheme) -> Result<Self, Self::Error> {
312		let DilithiumSignatureScheme::Dilithium(sig_with_public) = m;
313		Ok(sig_with_public)
314	}
315}
316
317impl AsMut<[u8]> for DilithiumSignatureWithPublic {
318	fn as_mut(&mut self) -> &mut [u8] {
319		self.bytes.as_mut()
320	}
321}
322impl TryFrom<&[u8]> for DilithiumSignatureWithPublic {
323	type Error = ();
324	fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
325		if data.len() != Self::TOTAL_LEN {
326			return Err(());
327		}
328		let (sig_bytes, pub_bytes) = data.split_at(<DilithiumSignature as ByteArray>::LEN);
329		let signature = DilithiumSignature::from_slice(sig_bytes).map_err(|_| ())?;
330		let public = DilithiumPublic::from_slice(pub_bytes).map_err(|_| ())?;
331		Ok(Self::new(signature, public))
332	}
333}
334
335impl ByteArray for DilithiumSignatureWithPublic {
336	const LEN: usize = Self::TOTAL_LEN;
337
338	fn to_raw_vec(&self) -> Vec<u8> {
339		self.to_bytes().to_vec()
340	}
341
342	fn from_slice(data: &[u8]) -> Result<Self, ()> {
343		if data.len() != Self::LEN {
344			return Err(());
345		}
346		let bytes = <[u8; Self::LEN]>::try_from(data).map_err(|_| ())?;
347		Self::from_bytes(&bytes).map_err(|_| ())
348	}
349
350	fn as_slice(&self) -> &[u8] {
351		self.bytes.as_slice()
352	}
353}
354impl AsRef<[u8; Self::LEN]> for DilithiumSignatureWithPublic {
355	fn as_ref(&self) -> &[u8; Self::LEN] {
356		&self.bytes
357	}
358}
359
360impl AsRef<[u8]> for DilithiumSignatureWithPublic {
361	fn as_ref(&self) -> &[u8] {
362		&self.bytes
363	}
364}
365impl Signature for DilithiumSignatureWithPublic {}
366
367impl CryptoType for DilithiumSignatureWithPublic {
368	type Pair = DilithiumPair;
369}