1#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(missing_docs)]
20
21extern crate alloc;
24
25use alloc::{format, string::String, vec::Vec};
26use codec::{Compact, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
27use core::{ops::Deref, str::FromStr};
28use scale_info::{build::Fields, Path, Type, TypeInfo};
29use sp_application_crypto::RuntimeAppPublic;
30#[cfg(feature = "std")]
31use sp_core::Pair;
32
33#[derive(
37 Clone,
38 Copy,
39 Debug,
40 Default,
41 PartialEq,
42 Eq,
43 PartialOrd,
44 Ord,
45 Hash,
46 Encode,
47 Decode,
48 DecodeWithMemTracking,
49 MaxEncodedLen,
50 TypeInfo,
51)]
52pub struct Topic(pub [u8; 32]);
53
54#[cfg(feature = "serde")]
55impl serde::Serialize for Topic {
56 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
57 where
58 S: serde::Serializer,
59 {
60 sp_core::bytes::serialize(&self.0, serializer)
61 }
62}
63
64#[cfg(feature = "serde")]
65impl<'de> serde::Deserialize<'de> for Topic {
66 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
67 where
68 D: serde::Deserializer<'de>,
69 {
70 let mut arr = [0u8; 32];
71 sp_core::bytes::deserialize_check_len(
72 deserializer,
73 sp_core::bytes::ExpectedLen::Exact(&mut arr[..]),
74 )?;
75 Ok(Topic(arr))
76 }
77}
78
79impl From<[u8; 32]> for Topic {
80 fn from(inner: [u8; 32]) -> Self {
81 Topic(inner)
82 }
83}
84
85impl From<Topic> for [u8; 32] {
86 fn from(topic: Topic) -> Self {
87 topic.0
88 }
89}
90
91impl AsRef<[u8; 32]> for Topic {
92 fn as_ref(&self) -> &[u8; 32] {
93 &self.0
94 }
95}
96
97impl AsRef<[u8]> for Topic {
98 fn as_ref(&self) -> &[u8] {
99 &self.0
100 }
101}
102
103impl Deref for Topic {
104 type Target = [u8; 32];
105
106 fn deref(&self) -> &Self::Target {
107 &self.0
108 }
109}
110
111impl FromStr for Topic {
112 type Err = String;
113
114 fn from_str(input: &str) -> core::result::Result<Self, Self::Err> {
116 array_bytes::hex2array(input)
117 .map(Topic)
118 .map_err(|e| format!("invalid topic '{input}': {e:?}"))
119 }
120}
121
122pub type DecryptionKey = [u8; 32];
124pub type Hash = [u8; 32];
126pub type BlockHash = [u8; 32];
128pub type AccountId = [u8; 32];
130pub type Channel = [u8; 32];
137
138pub const MAX_TOPICS: usize = 4;
140pub const MAX_ANY_TOPICS: usize = 128;
143
144#[derive(
159 Clone,
160 Default,
161 PartialEq,
162 Eq,
163 Encode,
164 Decode,
165 DecodeWithMemTracking,
166 Debug,
167 TypeInfo,
168 MaxEncodedLen,
169)]
170pub struct StatementAllowance {
171 pub max_count: u32,
173 pub max_size: u32,
175}
176
177impl StatementAllowance {
178 pub fn new(max_count: u32, max_size: u32) -> Self {
180 Self { max_count, max_size }
181 }
182
183 pub const fn saturating_add(self, rhs: StatementAllowance) -> StatementAllowance {
185 StatementAllowance {
186 max_count: self.max_count.saturating_add(rhs.max_count),
187 max_size: self.max_size.saturating_add(rhs.max_size),
188 }
189 }
190
191 pub const fn saturating_sub(self, rhs: StatementAllowance) -> StatementAllowance {
193 StatementAllowance {
194 max_count: self.max_count.saturating_sub(rhs.max_count),
195 max_size: self.max_size.saturating_sub(rhs.max_size),
196 }
197 }
198
199 pub fn is_depleted(&self) -> bool {
202 self.max_count == 0 || self.max_size == 0
203 }
204}
205
206pub const STATEMENT_ALLOWANCE_PREFIX: &[u8] = b":statement_allowance:";
208
209pub fn statement_allowance_key(account_id: impl AsRef<[u8]>) -> Vec<u8> {
217 let mut key = STATEMENT_ALLOWANCE_PREFIX.to_vec();
218 key.extend_from_slice(account_id.as_ref());
219 key
220}
221
222pub fn increase_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
224 let key = statement_allowance_key(account_id);
225 let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
226 allowance = allowance.saturating_add(by);
227 frame_support::storage::unhashed::put(&key, &allowance);
228}
229
230pub fn decrease_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
232 let key = statement_allowance_key(account_id);
233 let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
234 allowance = allowance.saturating_sub(by);
235 if allowance.is_depleted() {
236 frame_support::storage::unhashed::kill(&key);
237 } else {
238 frame_support::storage::unhashed::put(&key, &allowance);
239 }
240}
241
242pub fn get_allowance(account_id: impl AsRef<[u8]>) -> StatementAllowance {
244 let key = statement_allowance_key(account_id);
245 frame_support::storage::unhashed::get_or_default(&key)
246}
247
248pub use event::{
249 AddFilterResponse, LimitReachedResult, LimitReachedTag, NewStatementEntry, SubscribeEvent,
250};
251#[cfg(feature = "std")]
252pub use store_api::{
253 AdmittedBatch, Error, FilterDecision, FilterId, InvalidReason, LiveStatementEvent,
254 OptimizedTopicFilter, RejectionReason, Result, StatementEvent, StatementSource, StatementStore,
255 SubmitInvalidReason, SubmitOutcome, SubmitRejectionReason, SubmitResult, TopicFilter,
256};
257
258#[cfg(feature = "std")]
259mod ecies;
260mod event;
261pub mod runtime_api;
262#[cfg(feature = "std")]
263mod store_api;
264
265mod sr25519 {
266 mod app_sr25519 {
267 use sp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519};
268 app_crypto!(sr25519, STATEMENT);
269 }
270 pub type Public = app_sr25519::Public;
271}
272
273pub mod ed25519 {
275 mod app_ed25519 {
276 use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT};
277 app_crypto!(ed25519, STATEMENT);
278 }
279 pub type Public = app_ed25519::Public;
281 #[cfg(feature = "std")]
283 pub type Pair = app_ed25519::Pair;
284}
285
286mod ecdsa {
287 mod app_ecdsa {
288 use sp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT};
289 app_crypto!(ecdsa, STATEMENT);
290 }
291 pub type Public = app_ecdsa::Public;
292}
293
294#[cfg(feature = "std")]
296pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
297 sp_crypto_hashing::blake2_256(data)
298}
299
300#[derive(
302 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, Clone, PartialEq, Eq,
303)]
304pub enum Proof {
305 Sr25519 {
307 signature: [u8; 64],
309 signer: [u8; 32],
311 },
312 Ed25519 {
314 signature: [u8; 64],
316 signer: [u8; 32],
318 },
319 Secp256k1Ecdsa {
321 signature: [u8; 65],
323 signer: [u8; 33],
325 },
326}
327
328impl Proof {
329 pub fn account_id(&self) -> AccountId {
331 match self {
332 Proof::Sr25519 { signer, .. } => *signer,
333 Proof::Ed25519 { signer, .. } => *signer,
334 Proof::Secp256k1Ecdsa { signer, .. } => {
335 <sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer).into()
336 },
337 }
338 }
339}
340
341#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
344#[repr(u8)]
345pub enum Field {
346 AuthenticityProof(Proof) = 0,
348 DecryptionKey(DecryptionKey) = 1,
350 Expiry(u64) = 2,
352 Channel(Channel) = 3,
354 Topic1(Topic) = 4,
356 Topic2(Topic) = 5,
358 Topic3(Topic) = 6,
360 Topic4(Topic) = 7,
362 Data(Vec<u8>) = 8,
364}
365
366impl Field {
367 fn discriminant(&self) -> u8 {
368 unsafe { *(self as *const Self as *const u8) }
371 }
372}
373
374#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
376pub struct Statement {
377 proof: Option<Proof>,
379 #[deprecated(note = "Experimental feature, may be removed/changed in future releases")]
381 decryption_key: Option<DecryptionKey>,
382 channel: Option<Channel>,
393 expiry: u64,
408 num_topics: u8,
410 topics: [Topic; MAX_TOPICS],
412 data: Option<Vec<u8>>,
414}
415
416impl TypeInfo for Statement {
419 type Identity = Self;
420
421 fn type_info() -> Type {
422 Type::builder()
424 .path(Path::new("Statement", module_path!()))
425 .docs(&["Statement structure"])
426 .composite(Fields::unnamed().field(|f| f.ty::<Vec<Field>>()))
427 }
428}
429
430impl Decode for Statement {
431 fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
432 let num_fields: codec::Compact<u32> = Decode::decode(input)?;
435 let mut tag = 0;
436 let mut statement = Statement::new();
437 for i in 0..num_fields.into() {
438 let field: Field = Decode::decode(input)?;
439 if i > 0 && field.discriminant() <= tag {
440 return Err("Invalid field order or duplicate fields".into());
441 }
442 tag = field.discriminant();
443 match field {
444 Field::AuthenticityProof(p) => statement.set_proof(p),
445 Field::DecryptionKey(key) => statement.set_decryption_key(key),
446 Field::Expiry(p) => statement.set_expiry(p),
447 Field::Channel(c) => statement.set_channel(c),
448 Field::Topic1(t) => statement.set_topic(0, t),
449 Field::Topic2(t) => statement.set_topic(1, t),
450 Field::Topic3(t) => statement.set_topic(2, t),
451 Field::Topic4(t) => statement.set_topic(3, t),
452 Field::Data(data) => statement.set_plain_data(data),
453 }
454 }
455 Ok(statement)
456 }
457}
458
459impl Encode for Statement {
460 fn encode(&self) -> Vec<u8> {
461 self.encoded(false)
462 }
463}
464
465#[derive(Clone, Copy, PartialEq, Eq, Debug)]
466pub enum SignatureVerificationResult {
468 Valid(AccountId),
470 Invalid,
472 NoSignature,
474}
475
476impl Statement {
477 pub fn new() -> Statement {
479 Default::default()
480 }
481
482 pub fn new_with_proof(proof: Proof) -> Statement {
484 let mut statement = Self::new();
485 statement.set_proof(proof);
486 statement
487 }
488
489 pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool {
495 let to_sign = self.signature_material();
496 if let Some(signature) = key.sign(&to_sign) {
497 let proof = Proof::Sr25519 {
498 signature: signature.into_inner().into(),
499 signer: key.clone().into_inner().into(),
500 };
501 self.set_proof(proof);
502 true
503 } else {
504 false
505 }
506 }
507
508 pub fn topics(&self) -> &[Topic] {
510 &self.topics[..self.num_topics as usize]
511 }
512
513 #[cfg(feature = "std")]
515 pub fn sign_sr25519_private(&mut self, key: &sp_core::sr25519::Pair) {
516 let to_sign = self.signature_material();
517 let proof =
518 Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
519 self.set_proof(proof);
520 }
521
522 pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool {
528 let to_sign = self.signature_material();
529 if let Some(signature) = key.sign(&to_sign) {
530 let proof = Proof::Ed25519 {
531 signature: signature.into_inner().into(),
532 signer: key.clone().into_inner().into(),
533 };
534 self.set_proof(proof);
535 true
536 } else {
537 false
538 }
539 }
540
541 #[cfg(feature = "std")]
543 pub fn sign_ed25519_private(&mut self, key: &sp_core::ed25519::Pair) {
544 let to_sign = self.signature_material();
545 let proof =
546 Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
547 self.set_proof(proof);
548 }
549
550 pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool {
556 let to_sign = self.signature_material();
557 if let Some(signature) = key.sign(&to_sign) {
558 let proof = Proof::Secp256k1Ecdsa {
559 signature: signature.into_inner().into(),
560 signer: key.clone().into_inner().0,
561 };
562 self.set_proof(proof);
563 true
564 } else {
565 false
566 }
567 }
568
569 #[cfg(feature = "std")]
571 pub fn sign_ecdsa_private(&mut self, key: &sp_core::ecdsa::Pair) {
572 let to_sign = self.signature_material();
573 let proof =
574 Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 };
575 self.set_proof(proof);
576 }
577
578 pub fn verify_signature(&self) -> SignatureVerificationResult {
585 use sp_runtime::traits::Verify;
586
587 match self.proof() {
588 None => SignatureVerificationResult::NoSignature,
589 Some(Proof::Sr25519 { signature, signer }) => {
590 let to_sign = self.signature_material();
591 let signature = sp_core::sr25519::Signature::from(*signature);
592 let public = sp_core::sr25519::Public::from(*signer);
593 if signature.verify(to_sign.as_slice(), &public) {
594 SignatureVerificationResult::Valid(*signer)
595 } else {
596 SignatureVerificationResult::Invalid
597 }
598 },
599 Some(Proof::Ed25519 { signature, signer }) => {
600 let to_sign = self.signature_material();
601 let signature = sp_core::ed25519::Signature::from(*signature);
602 let public = sp_core::ed25519::Public::from(*signer);
603 if signature.verify(to_sign.as_slice(), &public) {
604 SignatureVerificationResult::Valid(*signer)
605 } else {
606 SignatureVerificationResult::Invalid
607 }
608 },
609 Some(Proof::Secp256k1Ecdsa { signature, signer }) => {
610 let to_sign = self.signature_material();
611 let signature = sp_core::ecdsa::Signature::from(*signature);
612 let public = sp_core::ecdsa::Public::from(*signer);
613 if signature.verify(to_sign.as_slice(), &public) {
614 let sender_hash =
615 <sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer);
616 SignatureVerificationResult::Valid(sender_hash.into())
617 } else {
618 SignatureVerificationResult::Invalid
619 }
620 },
621 }
622 }
623
624 #[cfg(feature = "std")]
630 pub fn hash(&self) -> [u8; 32] {
631 self.using_encoded(hash_encoded)
632 }
633
634 pub fn topic(&self, index: usize) -> Option<Topic> {
636 if index < self.num_topics as usize {
637 Some(self.topics[index])
638 } else {
639 None
640 }
641 }
642
643 #[allow(deprecated)]
645 pub fn decryption_key(&self) -> Option<DecryptionKey> {
646 self.decryption_key
647 }
648
649 pub fn into_data(self) -> Option<Vec<u8>> {
651 self.data
652 }
653
654 pub fn proof(&self) -> Option<&Proof> {
656 self.proof.as_ref()
657 }
658
659 pub fn account_id(&self) -> Option<AccountId> {
661 self.proof.as_ref().map(Proof::account_id)
662 }
663
664 pub fn data(&self) -> Option<&Vec<u8>> {
668 self.data.as_ref()
669 }
670
671 pub fn data_len(&self) -> usize {
676 self.data().map_or(0, Vec::len)
677 }
678
679 pub fn channel(&self) -> Option<Channel> {
681 self.channel
682 }
683
684 pub fn expiry(&self) -> u64 {
686 self.expiry
687 }
688
689 pub fn get_expiration_timestamp_secs(&self) -> u32 {
694 (self.expiry >> 32) as u32
695 }
696
697 pub fn is_expired(&self, now_secs: u64) -> bool {
701 now_secs >= u64::from(self.get_expiration_timestamp_secs())
702 }
703
704 fn signature_material(&self) -> Vec<u8> {
706 self.encoded(true)
707 }
708
709 pub fn remove_proof(&mut self) {
711 self.proof = None;
712 }
713
714 pub fn set_proof(&mut self, proof: Proof) {
716 self.proof = Some(proof)
717 }
718
719 pub fn set_expiry(&mut self, expiry: u64) {
721 self.expiry = expiry;
722 }
723
724 pub fn set_expiry_from_parts(&mut self, expiration_timestamp_secs: u32, sequence_number: u32) {
726 self.expiry = (expiration_timestamp_secs as u64) << 32 | sequence_number as u64;
727 }
728
729 pub fn set_channel(&mut self, channel: Channel) {
731 self.channel = Some(channel)
732 }
733
734 pub fn set_topic(&mut self, index: usize, topic: Topic) {
738 if index < MAX_TOPICS {
739 self.topics[index] = topic;
740 self.num_topics = self.num_topics.max(index as u8 + 1);
741 }
742 }
743
744 #[allow(deprecated)]
746 pub fn set_decryption_key(&mut self, key: DecryptionKey) {
747 self.decryption_key = Some(key);
748 }
749
750 pub fn set_plain_data(&mut self, data: Vec<u8>) {
752 self.data = Some(data)
753 }
754
755 #[allow(deprecated)]
767 fn estimated_encoded_size(&self, for_signing: bool) -> usize {
768 let proof_size =
769 if !for_signing && self.proof.is_some() { 1 + Proof::max_encoded_len() } else { 0 };
770 let decryption_key_size =
771 if self.decryption_key.is_some() { 1 + DecryptionKey::max_encoded_len() } else { 0 };
772 let expiry_size = 1 + u64::max_encoded_len();
773 let channel_size = if self.channel.is_some() { 1 + Channel::max_encoded_len() } else { 0 };
774 let topics_size = self.num_topics as usize * (1 + Topic::max_encoded_len());
775 let data_size = self
776 .data
777 .as_ref()
778 .map_or(0, |d| 1 + Compact::<u32>::max_encoded_len() + d.len());
779 let compact_prefix_size = if !for_signing { Compact::<u32>::max_encoded_len() } else { 0 };
780
781 compact_prefix_size +
782 proof_size +
783 decryption_key_size +
784 expiry_size +
785 channel_size +
786 topics_size +
787 data_size
788 }
789
790 #[allow(deprecated)]
791 fn encoded(&self, for_signing: bool) -> Vec<u8> {
792 let num_fields = if !for_signing && self.proof.is_some() { 2 } else { 1 } +
796 if self.decryption_key.is_some() { 1 } else { 0 } +
797 if self.channel.is_some() { 1 } else { 0 } +
798 if self.data.is_some() { 1 } else { 0 } +
799 self.num_topics as u32;
800
801 let mut output = Vec::with_capacity(self.estimated_encoded_size(for_signing));
802 if !for_signing {
806 let compact_len = codec::Compact::<u32>(num_fields);
807 compact_len.encode_to(&mut output);
808
809 if let Some(proof) = &self.proof {
810 0u8.encode_to(&mut output);
811 proof.encode_to(&mut output);
812 }
813 }
814 if let Some(decryption_key) = &self.decryption_key {
815 1u8.encode_to(&mut output);
816 decryption_key.encode_to(&mut output);
817 }
818
819 2u8.encode_to(&mut output);
820 self.expiry().encode_to(&mut output);
821
822 if let Some(channel) = &self.channel {
823 3u8.encode_to(&mut output);
824 channel.encode_to(&mut output);
825 }
826 for t in 0..self.num_topics {
827 (4u8 + t).encode_to(&mut output);
828 self.topics[t as usize].encode_to(&mut output);
829 }
830 if let Some(data) = &self.data {
831 8u8.encode_to(&mut output);
832 data.encode_to(&mut output);
833 }
834 output
835 }
836
837 #[allow(deprecated)]
842 #[cfg(feature = "std")]
843 pub fn encrypt(
844 &mut self,
845 data: &[u8],
846 key: &sp_core::ed25519::Public,
847 ) -> core::result::Result<(), ecies::Error> {
848 let encrypted = ecies::encrypt_ed25519(key, data)?;
849 self.data = Some(encrypted);
850 self.decryption_key = Some((*key).into());
851 Ok(())
852 }
853
854 #[cfg(feature = "std")]
859 pub fn decrypt_private(
860 &self,
861 key: &sp_core::ed25519::Pair,
862 ) -> core::result::Result<Option<Vec<u8>>, ecies::Error> {
863 self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose()
864 }
865}
866
867#[cfg(test)]
868mod test {
869 use crate::{
870 hash_encoded, Field, Proof, SignatureVerificationResult, Statement, Topic, MAX_TOPICS,
871 };
872 use codec::{Decode, Encode, MaxEncodedLen};
873 use core::str::FromStr;
874 use scale_info::{MetaType, TypeInfo};
875 use sp_application_crypto::Pair;
876 use sp_core::sr25519;
877
878 #[test]
879 fn topic_from_str_accepts_either_case_with_optional_prefix() {
880 let expected = Topic([0xAB; 32]);
881 let lower = "ab".repeat(32);
882 let upper = "AB".repeat(32);
883 assert_eq!(Topic::from_str(&lower), Ok(expected));
884 assert_eq!(Topic::from_str(&upper), Ok(expected));
885 assert_eq!(Topic::from_str(&format!("0x{lower}")), Ok(expected));
886 assert_eq!(Topic::from_str(&format!("0x{upper}")), Ok(expected));
887 }
888
889 #[test]
890 fn topic_from_str_rejects_empty_wrong_length_and_bad_hex() {
891 assert!(Topic::from_str("").is_err());
892 assert!(Topic::from_str("0xdead").is_err());
893 assert!(Topic::from_str(&"zz".repeat(32)).is_err());
894 }
895
896 #[test]
897 fn statement_encoding_matches_vec() {
898 let mut statement = Statement::new();
899 assert!(statement.proof().is_none());
900 let proof = Proof::Sr25519 { signature: [42u8; 64], signer: [24u8; 32] };
901
902 let decryption_key = [0xde; 32];
903 let topic1: Topic = [0x01; 32].into();
904 let topic2: Topic = [0x02; 32].into();
905 let data = vec![55, 99];
906 let expiry = 999;
907 let channel = [0xcc; 32];
908
909 statement.set_proof(proof.clone());
910 statement.set_decryption_key(decryption_key);
911 statement.set_expiry(expiry);
912 statement.set_channel(channel);
913 statement.set_topic(0, topic1);
914 statement.set_topic(1, topic2);
915 statement.set_plain_data(data.clone());
916
917 statement.set_topic(5, [0x55; 32].into());
918 assert_eq!(statement.topic(5), None);
919
920 let fields = vec![
921 Field::AuthenticityProof(proof.clone()),
922 Field::DecryptionKey(decryption_key),
923 Field::Expiry(expiry),
924 Field::Channel(channel),
925 Field::Topic1(topic1),
926 Field::Topic2(topic2),
927 Field::Data(data.clone()),
928 ];
929
930 let encoded = statement.encode();
931 assert_eq!(statement.hash(), hash_encoded(&encoded));
932 assert_eq!(encoded, fields.encode());
933
934 let decoded = Statement::decode(&mut encoded.as_slice()).unwrap();
935 assert_eq!(decoded, statement);
936 }
937
938 #[test]
939 fn decode_checks_fields() {
940 let topic1: Topic = [0x01; 32].into();
941 let topic2: Topic = [0x02; 32].into();
942 let priority = 999;
943
944 let dup_topic1 = vec![
945 Field::Expiry(priority),
946 Field::Topic1(topic1),
947 Field::Topic1(topic1),
948 Field::Topic2(topic2),
949 ]
950 .encode();
951 assert!(Statement::decode(&mut dup_topic1.as_slice()).is_err());
952
953 let topic1_before_expiry =
954 vec![Field::Topic1(topic1), Field::Expiry(priority), Field::Topic2(topic2)].encode();
955 assert!(Statement::decode(&mut topic1_before_expiry.as_slice()).is_err());
956
957 let dup_expiry = vec![Field::Expiry(1), Field::Expiry(2)].encode();
958 assert!(Statement::decode(&mut dup_expiry.as_slice()).is_err());
959
960 let dup_data = vec![Field::Data(vec![1]), Field::Data(vec![2])].encode();
961 assert!(Statement::decode(&mut dup_data.as_slice()).is_err());
962
963 let data_before_expiry = vec![Field::Data(vec![1]), Field::Expiry(42)].encode();
964 assert!(Statement::decode(&mut data_before_expiry.as_slice()).is_err());
965
966 let channel_before_expiry = vec![Field::Channel([0; 32]), Field::Expiry(1)].encode();
967 assert!(Statement::decode(&mut channel_before_expiry.as_slice()).is_err());
968
969 let topic2_before_topic1 =
970 vec![Field::Expiry(1), Field::Topic2(topic1), Field::Topic1(topic2)].encode();
971 assert!(Statement::decode(&mut topic2_before_topic1.as_slice()).is_err());
972 }
973
974 #[test]
975 fn decode_rejects_malformed_bytes() {
976 assert!(Statement::decode(&mut &[][..]).is_err());
977
978 let valid = vec![Field::Expiry(42)].encode();
980 let decoded = Statement::decode(&mut valid.as_slice()).unwrap();
981 assert_eq!(decoded.expiry(), 42);
982
983 assert!(Statement::decode(&mut &valid[..1][..]).is_err());
985
986 let mut invalid_discriminant = valid.clone();
988 invalid_discriminant[1] = 9;
989 assert!(Statement::decode(&mut invalid_discriminant.as_slice()).is_err());
990
991 invalid_discriminant[1] = 255;
992 assert!(Statement::decode(&mut invalid_discriminant.as_slice()).is_err());
993
994 assert!(Statement::decode(&mut &valid[..5][..]).is_err());
996
997 let with_proof = vec![
999 Field::AuthenticityProof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] }),
1000 Field::Expiry(42),
1001 ]
1002 .encode();
1003 assert!(Statement::decode(&mut with_proof.as_slice()).is_ok());
1004
1005 let mut invalid_proof_variant = with_proof.clone();
1006 invalid_proof_variant[2] = 99;
1007 assert!(Statement::decode(&mut invalid_proof_variant.as_slice()).is_err());
1008
1009 assert!(Statement::decode(&mut &with_proof[..6][..]).is_err());
1011
1012 let mut inflated_count = valid.clone();
1014 inflated_count[0] = 5 << 2; assert!(Statement::decode(&mut inflated_count.as_slice()).is_err());
1016 }
1017
1018 #[test]
1019 fn sign_and_verify() {
1020 let mut statement = Statement::new();
1021 statement.set_plain_data(vec![42]);
1022
1023 let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
1024 let ed25519_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
1025 let secp256k1_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
1026
1027 statement.sign_sr25519_private(&sr25519_kp);
1028 assert_eq!(
1029 statement.verify_signature(),
1030 SignatureVerificationResult::Valid(sr25519_kp.public().0)
1031 );
1032
1033 statement.sign_ed25519_private(&ed25519_kp);
1034 assert_eq!(
1035 statement.verify_signature(),
1036 SignatureVerificationResult::Valid(ed25519_kp.public().0)
1037 );
1038
1039 statement.sign_ecdsa_private(&secp256k1_kp);
1040 assert_eq!(
1041 statement.verify_signature(),
1042 SignatureVerificationResult::Valid(sp_crypto_hashing::blake2_256(
1043 &secp256k1_kp.public().0
1044 ))
1045 );
1046
1047 statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] });
1049 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1050
1051 statement.set_proof(Proof::Ed25519 { signature: [0xAB; 64], signer: [0xCD; 32] });
1053 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1054
1055 statement.set_proof(Proof::Secp256k1Ecdsa { signature: [0u8; 65], signer: [0u8; 33] });
1057 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1058
1059 statement.remove_proof();
1060 assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature);
1061 }
1062
1063 #[test]
1064 fn encrypt_decrypt() {
1065 let mut statement = Statement::new();
1066 let (pair, _) = sp_core::ed25519::Pair::generate();
1067 let plain = b"test data".to_vec();
1068
1069 statement.encrypt(&plain, &pair.public()).unwrap();
1071 assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice());
1072
1073 let decrypted = statement.decrypt_private(&pair).unwrap();
1074 assert_eq!(decrypted, Some(plain));
1075 }
1076
1077 #[test]
1078 fn check_matches() {
1079 let mut statement = Statement::new();
1080 let topic1: Topic = [0x01; 32].into();
1081 let topic2: Topic = [0x02; 32].into();
1082 let topic3: Topic = [0x03; 32].into();
1083
1084 statement.set_topic(0, topic1);
1085 statement.set_topic(1, topic2);
1086
1087 let filter_any = crate::OptimizedTopicFilter::Any;
1088 assert!(filter_any.matches(&statement));
1089
1090 let filter_all =
1091 crate::OptimizedTopicFilter::MatchAll([topic1, topic2].iter().cloned().collect());
1092 assert!(filter_all.matches(&statement));
1093
1094 let filter_all_fail =
1095 crate::OptimizedTopicFilter::MatchAll([topic1, topic3].iter().cloned().collect());
1096 assert!(!filter_all_fail.matches(&statement));
1097
1098 let filter_any_match =
1099 crate::OptimizedTopicFilter::MatchAny([topic2, topic3].iter().cloned().collect());
1100 assert!(filter_any_match.matches(&statement));
1101
1102 let filter_any_fail =
1103 crate::OptimizedTopicFilter::MatchAny([topic3].iter().cloned().collect());
1104 assert!(!filter_any_fail.matches(&statement));
1105 }
1106
1107 #[test]
1108 fn statement_type_info_matches_encoding() {
1109 let statement_type = Statement::type_info();
1112 let vec_field_meta = MetaType::new::<Vec<Field>>();
1113
1114 match statement_type.type_def {
1116 scale_info::TypeDef::Composite(composite) => {
1117 assert_eq!(composite.fields.len(), 1, "Statement should have exactly one field");
1118 let field = &composite.fields[0];
1119 assert!(field.name.is_none(), "Field should be unnamed (newtype pattern)");
1120 assert_eq!(field.ty, vec_field_meta, "Statement's inner type should be Vec<Field>");
1121 },
1122 _ => panic!("Statement TypeInfo should be a Composite"),
1123 }
1124 }
1125
1126 #[test]
1127 fn measure_hash_30_000_statements() {
1128 use std::time::Instant;
1129 const NUM_STATEMENTS: usize = 30_000;
1130 let (keyring, _) = sr25519::Pair::generate();
1131
1132 let statements: Vec<Statement> = (0..NUM_STATEMENTS)
1134 .map(|i| {
1135 let mut statement = Statement::new();
1136
1137 statement.set_expiry(i as u64);
1138 statement.set_topic(0, [(i % 256) as u8; 32].into());
1139 statement.set_plain_data(vec![i as u8; 512]);
1140 statement.sign_sr25519_private(&keyring);
1141
1142 statement.sign_sr25519_private(&keyring);
1143 statement
1144 })
1145 .collect();
1146 let start = Instant::now();
1148 let hashes: Vec<[u8; 32]> = statements.iter().map(|s| s.hash()).collect();
1149 let elapsed = start.elapsed();
1150 println!("Time to hash {} statements: {:?}", NUM_STATEMENTS, elapsed);
1151 println!("Average time per statement: {:?}", elapsed / NUM_STATEMENTS as u32);
1152 let unique_hashes: std::collections::HashSet<_> = hashes.iter().collect();
1154 assert_eq!(unique_hashes.len(), NUM_STATEMENTS);
1155 }
1156
1157 #[test]
1158 fn estimated_encoded_size_is_sufficient() {
1159 const MAX_ACCEPTED_OVERHEAD: usize = 33;
1161
1162 let proof = Proof::Secp256k1Ecdsa { signature: [42u8; 65], signer: [24u8; 33] };
1164 let decryption_key = [0xde; 32];
1165 let data = vec![55; 1000];
1166 let expiry = 999;
1167 let channel = [0xcc; 32];
1168
1169 let mut statement = Statement::new();
1171 statement.set_proof(proof);
1172 statement.set_decryption_key(decryption_key);
1173 statement.set_expiry(expiry);
1174 statement.set_channel(channel);
1175 for i in 0..MAX_TOPICS {
1176 statement.set_topic(i, [i as u8; 32].into());
1177 }
1178 statement.set_plain_data(data);
1179
1180 let encoded = statement.encode();
1181 let estimated = statement.estimated_encoded_size(false);
1182 assert!(
1183 estimated >= encoded.len(),
1184 "estimated_encoded_size ({}) should be >= actual encoded length ({})",
1185 estimated,
1186 encoded.len()
1187 );
1188 let overhead = estimated - encoded.len();
1189 assert!(
1190 overhead <= MAX_ACCEPTED_OVERHEAD,
1191 "estimated overhead ({}) should be small, estimated: {}, actual: {}",
1192 overhead,
1193 estimated,
1194 encoded.len()
1195 );
1196
1197 let signing_payload = statement.encoded(true);
1199 let signing_estimated = statement.estimated_encoded_size(true);
1200 assert!(
1201 signing_estimated >= signing_payload.len(),
1202 "estimated_encoded_size for signing ({}) should be >= actual signing payload length ({})",
1203 signing_estimated,
1204 signing_payload.len()
1205 );
1206 let signing_overhead = signing_estimated - signing_payload.len();
1207 assert!(
1208 signing_overhead <= MAX_ACCEPTED_OVERHEAD,
1209 "signing overhead ({}) should be small, estimated: {}, actual: {}",
1210 signing_overhead,
1211 signing_estimated,
1212 signing_payload.len()
1213 );
1214
1215 let empty_statement = Statement::new();
1217 let empty_encoded = empty_statement.encode();
1218 let empty_estimated = empty_statement.estimated_encoded_size(false);
1219 assert!(
1220 empty_estimated >= empty_encoded.len(),
1221 "estimated_encoded_size for empty ({}) should be >= actual encoded length ({})",
1222 empty_estimated,
1223 empty_encoded.len()
1224 );
1225 let empty_overhead = empty_estimated - empty_encoded.len();
1226 assert!(
1227 empty_overhead <= MAX_ACCEPTED_OVERHEAD,
1228 "empty overhead ({}) should be minimal, estimated: {}, actual: {}",
1229 empty_overhead,
1230 empty_estimated,
1231 empty_encoded.len()
1232 );
1233 }
1234
1235 fn populate_canonical_fixture(stmt: &mut Statement) {
1244 stmt.set_topic(0, [0x01; 32].into());
1245 stmt.set_topic(1, [0x02; 32].into());
1246 stmt.set_topic(2, [0x03; 32].into());
1247 stmt.set_channel([0xcc; 32]);
1248 stmt.set_expiry_from_parts(0x7fff_ffff, 0xabcd_1234);
1249 stmt.set_plain_data(vec![0xde, 0xad, 0xbe, 0xef]);
1250 }
1251
1252 fn canonical_tail() -> Vec<u8> {
1256 let mut v = Vec::new();
1257 v.push(0x02); v.extend_from_slice(&[0x34, 0x12, 0xcd, 0xab, 0xff, 0xff, 0xff, 0x7f]); v.push(0x03); v.extend_from_slice(&[0xcc; 32]);
1261 v.push(0x04); v.extend_from_slice(&[0x01; 32]);
1263 v.push(0x05); v.extend_from_slice(&[0x02; 32]);
1265 v.push(0x06); v.extend_from_slice(&[0x03; 32]);
1267 v.push(0x08); v.push(0x10); v.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
1270 v
1271 }
1272
1273 #[test]
1275 fn wire_format_sr25519_pinned() {
1276 let mut stmt = Statement::new();
1277 populate_canonical_fixture(&mut stmt);
1278 stmt.set_proof(Proof::Sr25519 { signature: [0x11; 64], signer: [0xAA; 32] });
1279
1280 let mut expected = Vec::new();
1281 expected.push(0x1c); expected.push(0x00); expected.push(0x00); expected.extend_from_slice(&[0x11; 64]); expected.extend_from_slice(&[0xAA; 32]); expected.extend(canonical_tail());
1287
1288 assert_eq!(stmt.encode(), expected, "Sr25519 wire format drifted");
1289 assert_eq!(expected.len(), 246);
1290 assert_eq!(Statement::decode(&mut expected.as_slice()).unwrap(), stmt);
1292 }
1293
1294 #[test]
1296 fn wire_format_legacy_onchain_proof_is_rejected() {
1297 let mut legacy = Vec::new();
1299 legacy.push(0x08); legacy.push(0x00); legacy.push(0x03); legacy.extend_from_slice(&[0xdd; 32]); legacy.extend_from_slice(&[0xee; 32]); legacy.extend_from_slice(&[0xbe, 0xba, 0xfe, 0xca, 0xef, 0xbe, 0xad, 0xde]); legacy.push(0x02); legacy.extend_from_slice(&[0x2a, 0, 0, 0, 0, 0, 0, 0]); assert!(
1309 Statement::decode(&mut legacy.as_slice()).is_err(),
1310 "legacy OnChain bytes must no longer decode into a Statement",
1311 );
1312
1313 let mut survivor = Vec::new();
1317 survivor.push(0x08);
1318 survivor.push(0x00);
1319 survivor.push(0x00); survivor.extend_from_slice(&[0xdd; 64]);
1321 survivor.extend_from_slice(&[0xee; 32]);
1322 survivor.push(0x02);
1323 survivor.extend_from_slice(&[0x2a, 0, 0, 0, 0, 0, 0, 0]);
1324 assert!(
1325 Statement::decode(&mut survivor.as_slice()).is_ok(),
1326 "surviving variant in the same byte layout must still decode",
1327 );
1328 }
1329
1330 #[test]
1332 fn proof_max_encoded_len_after_onchain_removal() {
1333 assert_eq!(
1334 Proof::max_encoded_len(),
1335 1 + 65 + 33,
1336 "max_encoded_len must equal Secp256k1Ecdsa's payload + 1-byte discriminant",
1337 );
1338 }
1339}