Skip to main content

soil_statement_store/
lib.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7#![cfg_attr(not(feature = "std"), no_std)]
8#![warn(missing_docs)]
9
10//! A crate which contains statement-store primitives.
11
12extern crate alloc;
13
14use alloc::vec::Vec;
15use codec::{Compact, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
16use core::ops::Deref;
17use scale_info::{build::Fields, Path, Type, TypeInfo};
18use subsoil::application_crypto::RuntimeAppPublic;
19#[cfg(feature = "std")]
20use subsoil::core::Pair;
21
22/// Statement topic.
23///
24/// A 32-byte topic identifier that serializes as a hex string (like `subsoil::core::Bytes`).
25#[derive(
26	Clone,
27	Copy,
28	Debug,
29	Default,
30	PartialEq,
31	Eq,
32	PartialOrd,
33	Ord,
34	Hash,
35	Encode,
36	Decode,
37	DecodeWithMemTracking,
38	MaxEncodedLen,
39	TypeInfo,
40)]
41pub struct Topic(pub [u8; 32]);
42
43#[cfg(feature = "serde")]
44impl serde::Serialize for Topic {
45	fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
46	where
47		S: serde::Serializer,
48	{
49		subsoil::core::bytes::serialize(&self.0, serializer)
50	}
51}
52
53#[cfg(feature = "serde")]
54impl<'de> serde::Deserialize<'de> for Topic {
55	fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
56	where
57		D: serde::Deserializer<'de>,
58	{
59		let mut arr = [0u8; 32];
60		subsoil::core::bytes::deserialize_check_len(
61			deserializer,
62			subsoil::core::bytes::ExpectedLen::Exact(&mut arr[..]),
63		)?;
64		Ok(Topic(arr))
65	}
66}
67
68impl From<[u8; 32]> for Topic {
69	fn from(inner: [u8; 32]) -> Self {
70		Topic(inner)
71	}
72}
73
74impl From<Topic> for [u8; 32] {
75	fn from(topic: Topic) -> Self {
76		topic.0
77	}
78}
79
80impl AsRef<[u8; 32]> for Topic {
81	fn as_ref(&self) -> &[u8; 32] {
82		&self.0
83	}
84}
85
86impl AsRef<[u8]> for Topic {
87	fn as_ref(&self) -> &[u8] {
88		&self.0
89	}
90}
91
92impl Deref for Topic {
93	type Target = [u8; 32];
94
95	fn deref(&self) -> &Self::Target {
96		&self.0
97	}
98}
99
100/// Decryption key identifier.
101pub type DecryptionKey = [u8; 32];
102/// Statement hash.
103pub type Hash = [u8; 32];
104/// Block hash.
105pub type BlockHash = [u8; 32];
106/// Account id
107pub type AccountId = [u8; 32];
108/// Statement channel.
109pub type Channel = [u8; 32];
110
111/// Total number of topic fields allowed in a statement and in `MatchAll` filters.
112pub const MAX_TOPICS: usize = 4;
113/// `MatchAny` allows to provide a list of topics match against. This is the maximum number of
114/// topics allowed.
115pub const MAX_ANY_TOPICS: usize = 128;
116
117/// Statement allowance limits for an account.
118#[derive(Clone, Default, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
119pub struct StatementAllowance {
120	/// Maximum number of statements allowed
121	pub max_count: u32,
122	/// Maximum total size of statements in bytes
123	pub max_size: u32,
124}
125
126impl StatementAllowance {
127	/// Create a new statement allowance.
128	pub fn new(max_count: u32, max_size: u32) -> Self {
129		Self { max_count, max_size }
130	}
131
132	/// Saturating addition of statement allowances.
133	pub const fn saturating_add(self, rhs: StatementAllowance) -> StatementAllowance {
134		StatementAllowance {
135			max_count: self.max_count.saturating_add(rhs.max_count),
136			max_size: self.max_size.saturating_add(rhs.max_size),
137		}
138	}
139
140	/// Saturating subtraction of statement allowances.
141	pub const fn saturating_sub(self, rhs: StatementAllowance) -> StatementAllowance {
142		StatementAllowance {
143			max_count: self.max_count.saturating_sub(rhs.max_count),
144			max_size: self.max_size.saturating_sub(rhs.max_size),
145		}
146	}
147
148	/// Check if the statement allowance is depleted.
149	pub fn is_depleted(&self) -> bool {
150		self.max_count == 0 || self.max_size == 0
151	}
152}
153
154/// Storage key prefix for per-account statement allowances.
155pub const STATEMENT_ALLOWANCE_PREFIX: &[u8] = b":statement_allowance:";
156
157/// Constructs a per-account statement allowance storage key.
158///
159/// # Arguments
160/// * `account_id` - Account identifier as byte slice
161///
162/// # Returns
163/// Storage key: `":statement_allowance:" ++ account_id`
164pub fn statement_allowance_key(account_id: impl AsRef<[u8]>) -> Vec<u8> {
165	let mut key = STATEMENT_ALLOWANCE_PREFIX.to_vec();
166	key.extend_from_slice(account_id.as_ref());
167	key
168}
169
170/// Increase the statement allowance by the given amount.
171pub fn increase_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
172	let key = statement_allowance_key(account_id);
173	let mut allowance: StatementAllowance =
174		topsoil_core::storage::unhashed::get_or_default(&key);
175	allowance = allowance.saturating_add(by);
176	topsoil_core::storage::unhashed::put(&key, &allowance);
177}
178
179/// Decrease the statement allowance by the given amount.
180pub fn decrease_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
181	let key = statement_allowance_key(account_id);
182	let mut allowance: StatementAllowance =
183		topsoil_core::storage::unhashed::get_or_default(&key);
184	allowance = allowance.saturating_sub(by);
185	if allowance.is_depleted() {
186		topsoil_core::storage::unhashed::kill(&key);
187	} else {
188		topsoil_core::storage::unhashed::put(&key, &allowance);
189	}
190}
191
192/// Get the statement allowance for the given account.
193pub fn get_allowance(account_id: impl AsRef<[u8]>) -> StatementAllowance {
194	let key = statement_allowance_key(account_id);
195	topsoil_core::storage::unhashed::get_or_default(&key)
196}
197
198#[cfg(feature = "std")]
199pub use store_api::{
200	Error, FilterDecision, InvalidReason, OptimizedTopicFilter, RejectionReason, Result,
201	StatementEvent, StatementSource, StatementStore, SubmitResult, TopicFilter,
202};
203
204#[cfg(feature = "std")]
205mod ecies;
206pub mod runtime_api;
207#[cfg(feature = "std")]
208mod store_api;
209
210mod sr25519 {
211	mod app_sr25519 {
212		use subsoil::application_crypto::{key_types::STATEMENT, sr25519};
213		subsoil::app_crypto!(sr25519, STATEMENT);
214	}
215	pub type Public = app_sr25519::Public;
216}
217
218/// Statement-store specific ed25519 crypto primitives.
219pub mod ed25519 {
220	mod app_ed25519 {
221		use subsoil::application_crypto::{ed25519, key_types::STATEMENT};
222		subsoil::app_crypto!(ed25519, STATEMENT);
223	}
224	/// Statement-store specific ed25519 public key.
225	pub type Public = app_ed25519::Public;
226	/// Statement-store specific ed25519 key pair.
227	#[cfg(feature = "std")]
228	pub type Pair = app_ed25519::Pair;
229}
230
231mod ecdsa {
232	mod app_ecdsa {
233		use subsoil::application_crypto::{ecdsa, key_types::STATEMENT};
234		subsoil::app_crypto!(ecdsa, STATEMENT);
235	}
236	pub type Public = app_ecdsa::Public;
237}
238
239/// Returns blake2-256 hash for the encoded statement.
240#[cfg(feature = "std")]
241pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
242	subsoil_crypto_hashing::blake2_256(data)
243}
244
245/// Statement proof.
246#[derive(
247	Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, Clone, PartialEq, Eq,
248)]
249pub enum Proof {
250	/// Sr25519 Signature.
251	Sr25519 {
252		/// Signature.
253		signature: [u8; 64],
254		/// Public key.
255		signer: [u8; 32],
256	},
257	/// Ed25519 Signature.
258	Ed25519 {
259		/// Signature.
260		signature: [u8; 64],
261		/// Public key.
262		signer: [u8; 32],
263	},
264	/// Secp256k1 Signature.
265	Secp256k1Ecdsa {
266		/// Signature.
267		signature: [u8; 65],
268		/// Public key.
269		signer: [u8; 33],
270	},
271	/// On-chain event proof.
272	OnChain {
273		/// Account identifier associated with the event.
274		who: AccountId,
275		/// Hash of block that contains the event.
276		block_hash: BlockHash,
277		/// Index of the event in the event list.
278		event_index: u64,
279	},
280}
281
282impl Proof {
283	/// Return account id for the proof creator.
284	pub fn account_id(&self) -> AccountId {
285		match self {
286			Proof::Sr25519 { signer, .. } => *signer,
287			Proof::Ed25519 { signer, .. } => *signer,
288			Proof::Secp256k1Ecdsa { signer, .. } => {
289				<subsoil::runtime::traits::BlakeTwo256 as subsoil::core::Hasher>::hash(signer)
290					.into()
291			},
292			Proof::OnChain { who, .. } => *who,
293		}
294	}
295}
296
297/// Statement attributes. Each statement is a list of 0 or more fields. Fields may only appear once
298/// and in the order declared here.
299#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
300#[repr(u8)]
301pub enum Field {
302	/// Statement proof.
303	AuthenticityProof(Proof) = 0,
304	/// An identifier for the key that `Data` field may be decrypted with.
305	DecryptionKey(DecryptionKey) = 1,
306	/// Expiry of the statement. See [`Statement::expiry`] for details on the format.
307	Expiry(u64) = 2,
308	/// Account channel to use. Only one message per `(account, channel)` pair is allowed.
309	Channel(Channel) = 3,
310	/// First statement topic.
311	Topic1(Topic) = 4,
312	/// Second statement topic.
313	Topic2(Topic) = 5,
314	/// Third statement topic.
315	Topic3(Topic) = 6,
316	/// Fourth statement topic.
317	Topic4(Topic) = 7,
318	/// Additional data.
319	Data(Vec<u8>) = 8,
320}
321
322impl Field {
323	fn discriminant(&self) -> u8 {
324		// This is safe for repr(u8)
325		// see https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
326		unsafe { *(self as *const Self as *const u8) }
327	}
328}
329
330/// Statement structure.
331#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
332pub struct Statement {
333	/// Proof used for authorizing the statement.
334	proof: Option<Proof>,
335	/// An identifier for the key that `Data` field may be decrypted with.
336	#[deprecated(note = "Experimental feature, may be removed/changed in future releases")]
337	decryption_key: Option<DecryptionKey>,
338	/// Used for identifying a distinct communication channel, only a message per channel is
339	/// stored.
340	///
341	/// This can be used to implement message replacement, submitting a new message with a
342	/// different topic/data on the same channel and a greater expiry replaces the previous one.
343	///
344	/// If the new statement data is bigger than the old one, submitting a statement with the same
345	/// channel does not guarantee that **ONLY** the old one will be replaced, as it might not fit
346	/// in the account quota. In that case, other statements from the same account with the lowest
347	/// expiry will be removed.
348	channel: Option<Channel>,
349	/// Message expiry, used for determining which statements to keep.
350	///
351	/// The most significant 32 bits represents the expiration timestamp (in seconds since
352	/// UNIX epoch) after which the statement gets removed. These ensure that statements with a
353	/// higher expiration time have a higher priority.
354	/// The lower 32 bits represents an arbitrary sequence number used to order statements with the
355	/// same expiration time.
356	///
357	/// Higher values indicate a higher priority.
358	/// This is used in two cases:
359	/// 1) When an account exceeds its quota and some statements need to be removed. Statements
360	///    with the lowest `expiry` are removed first.
361	/// 2) When multiple statements are submitted on the same channel, the one with the highest
362	///    expiry replaces the one with the same channel.
363	expiry: u64,
364	/// Number of topics present.
365	num_topics: u8,
366	/// Topics, used for querying and filtering statements.
367	topics: [Topic; MAX_TOPICS],
368	/// Statement data.
369	data: Option<Vec<u8>>,
370}
371
372/// Note: The `TypeInfo` implementation reflects the actual encoding format (`Vec<Field>`)
373/// rather than the struct fields, since `Statement` has custom `Encode`/`Decode` implementations.
374impl TypeInfo for Statement {
375	type Identity = Self;
376
377	fn type_info() -> Type {
378		// Statement encodes as Vec<Field>, so we report the same type info
379		Type::builder()
380			.path(Path::new("Statement", module_path!()))
381			.docs(&["Statement structure"])
382			.composite(Fields::unnamed().field(|f| f.ty::<Vec<Field>>()))
383	}
384}
385
386impl Decode for Statement {
387	fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
388		// Encoding matches that of Vec<Field>. Basically this just means accepting that there
389		// will be a prefix of vector length.
390		let num_fields: codec::Compact<u32> = Decode::decode(input)?;
391		let mut tag = 0;
392		let mut statement = Statement::new();
393		for i in 0..num_fields.into() {
394			let field: Field = Decode::decode(input)?;
395			if i > 0 && field.discriminant() <= tag {
396				return Err("Invalid field order or duplicate fields".into());
397			}
398			tag = field.discriminant();
399			match field {
400				Field::AuthenticityProof(p) => statement.set_proof(p),
401				Field::DecryptionKey(key) => statement.set_decryption_key(key),
402				Field::Expiry(p) => statement.set_expiry(p),
403				Field::Channel(c) => statement.set_channel(c),
404				Field::Topic1(t) => statement.set_topic(0, t),
405				Field::Topic2(t) => statement.set_topic(1, t),
406				Field::Topic3(t) => statement.set_topic(2, t),
407				Field::Topic4(t) => statement.set_topic(3, t),
408				Field::Data(data) => statement.set_plain_data(data),
409			}
410		}
411		Ok(statement)
412	}
413}
414
415impl Encode for Statement {
416	fn encode(&self) -> Vec<u8> {
417		self.encoded(false)
418	}
419}
420
421#[derive(Clone, Copy, PartialEq, Eq, Debug)]
422/// Result returned by `Statement::verify_signature`
423pub enum SignatureVerificationResult {
424	/// Signature is valid and matches this account id.
425	Valid(AccountId),
426	/// Signature has failed verification.
427	Invalid,
428	/// No signature in the proof or no proof.
429	NoSignature,
430}
431
432impl Statement {
433	/// Create a new empty statement with no proof.
434	pub fn new() -> Statement {
435		Default::default()
436	}
437
438	/// Create a new statement with a proof.
439	pub fn new_with_proof(proof: Proof) -> Statement {
440		let mut statement = Self::new();
441		statement.set_proof(proof);
442		statement
443	}
444
445	/// Sign with a key that matches given public key in the keystore.
446	///
447	/// Returns `true` if signing worked (private key present etc).
448	///
449	/// NOTE: This can only be called from the runtime.
450	pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool {
451		let to_sign = self.signature_material();
452		if let Some(signature) = key.sign(&to_sign) {
453			let proof = Proof::Sr25519 {
454				signature: signature.into_inner().into(),
455				signer: key.clone().into_inner().into(),
456			};
457			self.set_proof(proof);
458			true
459		} else {
460			false
461		}
462	}
463
464	/// Returns slice of all topics set in the statement.
465	pub fn topics(&self) -> &[Topic] {
466		&self.topics[..self.num_topics as usize]
467	}
468
469	/// Sign with a given private key and add the signature proof field.
470	#[cfg(feature = "std")]
471	pub fn sign_sr25519_private(&mut self, key: &subsoil::core::sr25519::Pair) {
472		let to_sign = self.signature_material();
473		let proof =
474			Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
475		self.set_proof(proof);
476	}
477
478	/// Sign with a key that matches given public key in the keystore.
479	///
480	/// Returns `true` if signing worked (private key present etc).
481	///
482	/// NOTE: This can only be called from the runtime.
483	pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool {
484		let to_sign = self.signature_material();
485		if let Some(signature) = key.sign(&to_sign) {
486			let proof = Proof::Ed25519 {
487				signature: signature.into_inner().into(),
488				signer: key.clone().into_inner().into(),
489			};
490			self.set_proof(proof);
491			true
492		} else {
493			false
494		}
495	}
496
497	/// Sign with a given private key and add the signature proof field.
498	#[cfg(feature = "std")]
499	pub fn sign_ed25519_private(&mut self, key: &subsoil::core::ed25519::Pair) {
500		let to_sign = self.signature_material();
501		let proof =
502			Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
503		self.set_proof(proof);
504	}
505
506	/// Sign with a key that matches given public key in the keystore.
507	///
508	/// Returns `true` if signing worked (private key present etc).
509	///
510	/// NOTE: This can only be called from the runtime.
511	///
512	/// Returns `true` if signing worked (private key present etc).
513	///
514	/// NOTE: This can only be called from the runtime.
515	pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool {
516		let to_sign = self.signature_material();
517		if let Some(signature) = key.sign(&to_sign) {
518			let proof = Proof::Secp256k1Ecdsa {
519				signature: signature.into_inner().into(),
520				signer: key.clone().into_inner().0,
521			};
522			self.set_proof(proof);
523			true
524		} else {
525			false
526		}
527	}
528
529	/// Sign with a given private key and add the signature proof field.
530	#[cfg(feature = "std")]
531	pub fn sign_ecdsa_private(&mut self, key: &subsoil::core::ecdsa::Pair) {
532		let to_sign = self.signature_material();
533		let proof =
534			Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 };
535		self.set_proof(proof);
536	}
537
538	/// Check proof signature, if any.
539	pub fn verify_signature(&self) -> SignatureVerificationResult {
540		use subsoil::runtime::traits::Verify;
541
542		match self.proof() {
543			Some(Proof::OnChain { .. }) | None => SignatureVerificationResult::NoSignature,
544			Some(Proof::Sr25519 { signature, signer }) => {
545				let to_sign = self.signature_material();
546				let signature = subsoil::core::sr25519::Signature::from(*signature);
547				let public = subsoil::core::sr25519::Public::from(*signer);
548				if signature.verify(to_sign.as_slice(), &public) {
549					SignatureVerificationResult::Valid(*signer)
550				} else {
551					SignatureVerificationResult::Invalid
552				}
553			},
554			Some(Proof::Ed25519 { signature, signer }) => {
555				let to_sign = self.signature_material();
556				let signature = subsoil::core::ed25519::Signature::from(*signature);
557				let public = subsoil::core::ed25519::Public::from(*signer);
558				if signature.verify(to_sign.as_slice(), &public) {
559					SignatureVerificationResult::Valid(*signer)
560				} else {
561					SignatureVerificationResult::Invalid
562				}
563			},
564			Some(Proof::Secp256k1Ecdsa { signature, signer }) => {
565				let to_sign = self.signature_material();
566				let signature = subsoil::core::ecdsa::Signature::from(*signature);
567				let public = subsoil::core::ecdsa::Public::from(*signer);
568				if signature.verify(to_sign.as_slice(), &public) {
569					let sender_hash =
570						<subsoil::runtime::traits::BlakeTwo256 as subsoil::core::Hasher>::hash(
571							signer,
572						);
573					SignatureVerificationResult::Valid(sender_hash.into())
574				} else {
575					SignatureVerificationResult::Invalid
576				}
577			},
578		}
579	}
580
581	/// Calculate statement hash.
582	#[cfg(feature = "std")]
583	pub fn hash(&self) -> [u8; 32] {
584		self.using_encoded(hash_encoded)
585	}
586
587	/// Returns a topic by topic index.
588	pub fn topic(&self, index: usize) -> Option<Topic> {
589		if index < self.num_topics as usize {
590			Some(self.topics[index])
591		} else {
592			None
593		}
594	}
595
596	/// Returns decryption key if any.
597	#[allow(deprecated)]
598	pub fn decryption_key(&self) -> Option<DecryptionKey> {
599		self.decryption_key
600	}
601
602	/// Convert to internal data.
603	pub fn into_data(self) -> Option<Vec<u8>> {
604		self.data
605	}
606
607	/// Get a reference to the statement proof, if any.
608	pub fn proof(&self) -> Option<&Proof> {
609		self.proof.as_ref()
610	}
611
612	/// Get proof account id, if any
613	pub fn account_id(&self) -> Option<AccountId> {
614		self.proof.as_ref().map(Proof::account_id)
615	}
616
617	/// Get plain data.
618	pub fn data(&self) -> Option<&Vec<u8>> {
619		self.data.as_ref()
620	}
621
622	/// Get plain data len.
623	pub fn data_len(&self) -> usize {
624		self.data().map_or(0, Vec::len)
625	}
626
627	/// Get channel, if any.
628	pub fn channel(&self) -> Option<Channel> {
629		self.channel
630	}
631
632	/// Get expiry.
633	pub fn expiry(&self) -> u64 {
634		self.expiry
635	}
636
637	/// Get expiration timestamp in seconds.
638	///
639	/// The expiration timestamp in seconds is stored in the most significant 32 bits of the expiry
640	/// field.
641	pub fn get_expiration_timestamp_secs(&self) -> u32 {
642		(self.expiry >> 32) as u32
643	}
644
645	/// Return encoded fields that can be signed to construct or verify a proof
646	fn signature_material(&self) -> Vec<u8> {
647		self.encoded(true)
648	}
649
650	/// Remove the proof of this statement.
651	pub fn remove_proof(&mut self) {
652		self.proof = None;
653	}
654
655	/// Set statement proof. Any existing proof is overwritten.
656	pub fn set_proof(&mut self, proof: Proof) {
657		self.proof = Some(proof)
658	}
659
660	/// Set statement expiry.
661	pub fn set_expiry(&mut self, expiry: u64) {
662		self.expiry = expiry;
663	}
664
665	/// Set statement expiry from its parts. See [`Statement::expiry`] for details on the format.
666	pub fn set_expiry_from_parts(&mut self, expiration_timestamp_secs: u32, sequence_number: u32) {
667		self.expiry = (expiration_timestamp_secs as u64) << 32 | sequence_number as u64;
668	}
669
670	/// Set statement channel.
671	pub fn set_channel(&mut self, channel: Channel) {
672		self.channel = Some(channel)
673	}
674
675	/// Set topic by index. Does noting if index is over `MAX_TOPICS`.
676	pub fn set_topic(&mut self, index: usize, topic: Topic) {
677		if index < MAX_TOPICS {
678			self.topics[index] = topic;
679			self.num_topics = self.num_topics.max(index as u8 + 1);
680		}
681	}
682
683	/// Set decryption key.
684	#[allow(deprecated)]
685	pub fn set_decryption_key(&mut self, key: DecryptionKey) {
686		self.decryption_key = Some(key);
687	}
688
689	/// Set unencrypted statement data.
690	pub fn set_plain_data(&mut self, data: Vec<u8>) {
691		self.data = Some(data)
692	}
693
694	/// Estimate the encoded size for preallocation.
695	///
696	/// Returns a close approximation of the SCALE-encoded size without actually performing the
697	/// encoding. Uses max_encoded_len() for type sizes:
698	/// - Compact length prefix: max_encoded_len() bytes
699	/// - Proof field: 1 (tag) + max_encoded_len()
700	/// - DecryptionKey: 1 (tag) + max_encoded_len()
701	/// - Expiry: 1 (tag) + max_encoded_len()
702	/// - Channel: 1 (tag) + max_encoded_len()
703	/// - Each topic: 1 (tag) + max_encoded_len()
704	/// - Data: 1 (tag) + max_encoded_len() (compact len) + data.len()
705	#[allow(deprecated)]
706	fn estimated_encoded_size(&self, for_signing: bool) -> usize {
707		let proof_size =
708			if !for_signing && self.proof.is_some() { 1 + Proof::max_encoded_len() } else { 0 };
709		let decryption_key_size =
710			if self.decryption_key.is_some() { 1 + DecryptionKey::max_encoded_len() } else { 0 };
711		let expiry_size = 1 + u64::max_encoded_len();
712		let channel_size = if self.channel.is_some() { 1 + Channel::max_encoded_len() } else { 0 };
713		let topics_size = self.num_topics as usize * (1 + Topic::max_encoded_len());
714		let data_size = self
715			.data
716			.as_ref()
717			.map_or(0, |d| 1 + Compact::<u32>::max_encoded_len() + d.len());
718		let compact_prefix_size = if !for_signing { Compact::<u32>::max_encoded_len() } else { 0 };
719
720		compact_prefix_size
721			+ proof_size
722			+ decryption_key_size
723			+ expiry_size
724			+ channel_size
725			+ topics_size
726			+ data_size
727	}
728
729	#[allow(deprecated)]
730	fn encoded(&self, for_signing: bool) -> Vec<u8> {
731		// Encoding matches that of Vec<Field>. Basically this just means accepting that there
732		// will be a prefix of vector length.
733		// Expiry field is always present.
734		let num_fields = if !for_signing && self.proof.is_some() { 2 } else { 1 }
735			+ if self.decryption_key.is_some() { 1 } else { 0 }
736			+ if self.channel.is_some() { 1 } else { 0 }
737			+ if self.data.is_some() { 1 } else { 0 }
738			+ self.num_topics as u32;
739
740		let mut output = Vec::with_capacity(self.estimated_encoded_size(for_signing));
741		// When encoding signature payload, the length prefix is omitted.
742		// This is so that the signature for encoded statement can potentially be derived without
743		// needing to re-encode the statement.
744		if !for_signing {
745			let compact_len = codec::Compact::<u32>(num_fields);
746			compact_len.encode_to(&mut output);
747
748			if let Some(proof) = &self.proof {
749				0u8.encode_to(&mut output);
750				proof.encode_to(&mut output);
751			}
752		}
753		if let Some(decryption_key) = &self.decryption_key {
754			1u8.encode_to(&mut output);
755			decryption_key.encode_to(&mut output);
756		}
757
758		2u8.encode_to(&mut output);
759		self.expiry().encode_to(&mut output);
760
761		if let Some(channel) = &self.channel {
762			3u8.encode_to(&mut output);
763			channel.encode_to(&mut output);
764		}
765		for t in 0..self.num_topics {
766			(4u8 + t).encode_to(&mut output);
767			self.topics[t as usize].encode_to(&mut output);
768		}
769		if let Some(data) = &self.data {
770			8u8.encode_to(&mut output);
771			data.encode_to(&mut output);
772		}
773		output
774	}
775
776	/// Encrypt give data with given key and store both in the statements.
777	#[allow(deprecated)]
778	#[cfg(feature = "std")]
779	pub fn encrypt(
780		&mut self,
781		data: &[u8],
782		key: &subsoil::core::ed25519::Public,
783	) -> core::result::Result<(), ecies::Error> {
784		let encrypted = ecies::encrypt_ed25519(key, data)?;
785		self.data = Some(encrypted);
786		self.decryption_key = Some((*key).into());
787		Ok(())
788	}
789
790	/// Decrypt data (if any) with the given private key.
791	#[cfg(feature = "std")]
792	pub fn decrypt_private(
793		&self,
794		key: &subsoil::core::ed25519::Pair,
795	) -> core::result::Result<Option<Vec<u8>>, ecies::Error> {
796		self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose()
797	}
798}
799
800#[cfg(test)]
801mod test {
802	use crate::{
803		hash_encoded, Field, Proof, SignatureVerificationResult, Statement, Topic, MAX_TOPICS,
804	};
805	use codec::{Decode, Encode};
806	use scale_info::{MetaType, TypeInfo};
807	use subsoil::application_crypto::Pair;
808	use subsoil::core::sr25519;
809
810	#[test]
811	fn statement_encoding_matches_vec() {
812		let mut statement = Statement::new();
813		assert!(statement.proof().is_none());
814		let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 };
815
816		let decryption_key = [0xde; 32];
817		let topic1: Topic = [0x01; 32].into();
818		let topic2: Topic = [0x02; 32].into();
819		let data = vec![55, 99];
820		let expiry = 999;
821		let channel = [0xcc; 32];
822
823		statement.set_proof(proof.clone());
824		statement.set_decryption_key(decryption_key);
825		statement.set_expiry(expiry);
826		statement.set_channel(channel);
827		statement.set_topic(0, topic1);
828		statement.set_topic(1, topic2);
829		statement.set_plain_data(data.clone());
830
831		statement.set_topic(5, [0x55; 32].into());
832		assert_eq!(statement.topic(5), None);
833
834		let fields = vec![
835			Field::AuthenticityProof(proof.clone()),
836			Field::DecryptionKey(decryption_key),
837			Field::Expiry(expiry),
838			Field::Channel(channel),
839			Field::Topic1(topic1),
840			Field::Topic2(topic2),
841			Field::Data(data.clone()),
842		];
843
844		let encoded = statement.encode();
845		assert_eq!(statement.hash(), hash_encoded(&encoded));
846		assert_eq!(encoded, fields.encode());
847
848		let decoded = Statement::decode(&mut encoded.as_slice()).unwrap();
849		assert_eq!(decoded, statement);
850	}
851
852	#[test]
853	fn decode_checks_fields() {
854		let topic1: Topic = [0x01; 32].into();
855		let topic2: Topic = [0x02; 32].into();
856		let priority = 999;
857
858		let fields = vec![
859			Field::Expiry(priority),
860			Field::Topic1(topic1),
861			Field::Topic1(topic1),
862			Field::Topic2(topic2),
863		]
864		.encode();
865
866		assert!(Statement::decode(&mut fields.as_slice()).is_err());
867
868		let fields =
869			vec![Field::Topic1(topic1), Field::Expiry(priority), Field::Topic2(topic2)].encode();
870
871		assert!(Statement::decode(&mut fields.as_slice()).is_err());
872	}
873
874	#[test]
875	fn sign_and_verify() {
876		let mut statement = Statement::new();
877		statement.set_plain_data(vec![42]);
878
879		let sr25519_kp = subsoil::core::sr25519::Pair::from_string("//Alice", None).unwrap();
880		let ed25519_kp = subsoil::core::ed25519::Pair::from_string("//Alice", None).unwrap();
881		let secp256k1_kp = subsoil::core::ecdsa::Pair::from_string("//Alice", None).unwrap();
882
883		statement.sign_sr25519_private(&sr25519_kp);
884		assert_eq!(
885			statement.verify_signature(),
886			SignatureVerificationResult::Valid(sr25519_kp.public().0)
887		);
888
889		statement.sign_ed25519_private(&ed25519_kp);
890		assert_eq!(
891			statement.verify_signature(),
892			SignatureVerificationResult::Valid(ed25519_kp.public().0)
893		);
894
895		statement.sign_ecdsa_private(&secp256k1_kp);
896		assert_eq!(
897			statement.verify_signature(),
898			SignatureVerificationResult::Valid(subsoil_crypto_hashing::blake2_256(
899				&secp256k1_kp.public().0
900			))
901		);
902
903		// set an invalid signature
904		statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] });
905		assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
906
907		statement.remove_proof();
908		assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature);
909	}
910
911	#[test]
912	fn encrypt_decrypt() {
913		let mut statement = Statement::new();
914		let (pair, _) = subsoil::core::ed25519::Pair::generate();
915		let plain = b"test data".to_vec();
916
917		// let sr25519_kp = subsoil::core::sr25519::Pair::from_string("//Alice", None).unwrap();
918		statement.encrypt(&plain, &pair.public()).unwrap();
919		assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice());
920
921		let decrypted = statement.decrypt_private(&pair).unwrap();
922		assert_eq!(decrypted, Some(plain));
923	}
924
925	#[test]
926	fn check_matches() {
927		let mut statement = Statement::new();
928		let topic1: Topic = [0x01; 32].into();
929		let topic2: Topic = [0x02; 32].into();
930		let topic3: Topic = [0x03; 32].into();
931
932		statement.set_topic(0, topic1);
933		statement.set_topic(1, topic2);
934
935		let filter_any = crate::OptimizedTopicFilter::Any;
936		assert!(filter_any.matches(&statement));
937
938		let filter_all =
939			crate::OptimizedTopicFilter::MatchAll([topic1, topic2].iter().cloned().collect());
940		assert!(filter_all.matches(&statement));
941
942		let filter_all_fail =
943			crate::OptimizedTopicFilter::MatchAll([topic1, topic3].iter().cloned().collect());
944		assert!(!filter_all_fail.matches(&statement));
945
946		let filter_any_match =
947			crate::OptimizedTopicFilter::MatchAny([topic2, topic3].iter().cloned().collect());
948		assert!(filter_any_match.matches(&statement));
949
950		let filter_any_fail =
951			crate::OptimizedTopicFilter::MatchAny([topic3].iter().cloned().collect());
952		assert!(!filter_any_fail.matches(&statement));
953	}
954
955	#[test]
956	fn statement_type_info_matches_encoding() {
957		// Statement has custom Encode/Decode that encodes as Vec<Field>.
958		// Verify that TypeInfo reflects this by containing a reference to Vec<Field>.
959		let statement_type = Statement::type_info();
960		let vec_field_meta = MetaType::new::<Vec<Field>>();
961
962		// The Statement type should be a composite with one unnamed field of type Vec<Field>
963		match statement_type.type_def {
964			scale_info::TypeDef::Composite(composite) => {
965				assert_eq!(composite.fields.len(), 1, "Statement should have exactly one field");
966				let field = &composite.fields[0];
967				assert!(field.name.is_none(), "Field should be unnamed (newtype pattern)");
968				assert_eq!(field.ty, vec_field_meta, "Statement's inner type should be Vec<Field>");
969			},
970			_ => panic!("Statement TypeInfo should be a Composite"),
971		}
972	}
973
974	#[test]
975	fn measure_hash_30_000_statements() {
976		use std::time::Instant;
977		const NUM_STATEMENTS: usize = 30_000;
978		let (keyring, _) = sr25519::Pair::generate();
979
980		// Create 2000 statements with varying data
981		let statements: Vec<Statement> = (0..NUM_STATEMENTS)
982			.map(|i| {
983				let mut statement = Statement::new();
984
985				statement.set_expiry(i as u64);
986				statement.set_topic(0, [(i % 256) as u8; 32].into());
987				statement.set_plain_data(vec![i as u8; 512]);
988				statement.sign_sr25519_private(&keyring);
989
990				statement.sign_sr25519_private(&keyring);
991				statement
992			})
993			.collect();
994		// Measure time to hash all statements
995		let start = Instant::now();
996		let hashes: Vec<[u8; 32]> = statements.iter().map(|s| s.hash()).collect();
997		let elapsed = start.elapsed();
998		println!("Time to hash {} statements: {:?}", NUM_STATEMENTS, elapsed);
999		println!("Average time per statement: {:?}", elapsed / NUM_STATEMENTS as u32);
1000		// Verify hashes are unique
1001		let unique_hashes: std::collections::HashSet<_> = hashes.iter().collect();
1002		assert_eq!(unique_hashes.len(), NUM_STATEMENTS);
1003	}
1004
1005	#[test]
1006	fn estimated_encoded_size_is_sufficient() {
1007		// Allow some overhead due to using max_encoded_len() approximations.
1008		const MAX_ACCEPTED_OVERHEAD: usize = 33;
1009
1010		let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 };
1011		let decryption_key = [0xde; 32];
1012		let data = vec![55; 1000];
1013		let expiry = 999;
1014		let channel = [0xcc; 32];
1015
1016		// Test with all fields populated
1017		let mut statement = Statement::new();
1018		statement.set_proof(proof);
1019		statement.set_decryption_key(decryption_key);
1020		statement.set_expiry(expiry);
1021		statement.set_channel(channel);
1022		for i in 0..MAX_TOPICS {
1023			statement.set_topic(i, [i as u8; 32].into());
1024		}
1025		statement.set_plain_data(data);
1026
1027		let encoded = statement.encode();
1028		let estimated = statement.estimated_encoded_size(false);
1029		assert!(
1030			estimated >= encoded.len(),
1031			"estimated_encoded_size ({}) should be >= actual encoded length ({})",
1032			estimated,
1033			encoded.len()
1034		);
1035		let overhead = estimated - encoded.len();
1036		assert!(
1037			overhead <= MAX_ACCEPTED_OVERHEAD,
1038			"estimated overhead ({}) should be small, estimated: {}, actual: {}",
1039			overhead,
1040			estimated,
1041			encoded.len()
1042		);
1043
1044		// Test for_signing = true (no proof, no compact prefix)
1045		let signing_payload = statement.encoded(true);
1046		let signing_estimated = statement.estimated_encoded_size(true);
1047		assert!(
1048			signing_estimated >= signing_payload.len(),
1049			"estimated_encoded_size for signing ({}) should be >= actual signing payload length ({})",
1050			signing_estimated,
1051			signing_payload.len()
1052		);
1053		let signing_overhead = signing_estimated - signing_payload.len();
1054		assert!(
1055			signing_overhead <= MAX_ACCEPTED_OVERHEAD,
1056			"signing overhead ({}) should be small, estimated: {}, actual: {}",
1057			signing_overhead,
1058			signing_estimated,
1059			signing_payload.len()
1060		);
1061
1062		// Test with minimal statement (empty)
1063		let empty_statement = Statement::new();
1064		let empty_encoded = empty_statement.encode();
1065		let empty_estimated = empty_statement.estimated_encoded_size(false);
1066		assert!(
1067			empty_estimated >= empty_encoded.len(),
1068			"estimated_encoded_size for empty ({}) should be >= actual encoded length ({})",
1069			empty_estimated,
1070			empty_encoded.len()
1071		);
1072		let empty_overhead = empty_estimated - empty_encoded.len();
1073		assert!(
1074			empty_overhead <= MAX_ACCEPTED_OVERHEAD,
1075			"empty overhead ({}) should be minimal, estimated: {}, actual: {}",
1076			empty_overhead,
1077			empty_estimated,
1078			empty_encoded.len()
1079		);
1080	}
1081}