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(Clone, Default, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
159pub struct StatementAllowance {
160 pub max_count: u32,
162 pub max_size: u32,
164}
165
166impl StatementAllowance {
167 pub fn new(max_count: u32, max_size: u32) -> Self {
169 Self { max_count, max_size }
170 }
171
172 pub const fn saturating_add(self, rhs: StatementAllowance) -> StatementAllowance {
174 StatementAllowance {
175 max_count: self.max_count.saturating_add(rhs.max_count),
176 max_size: self.max_size.saturating_add(rhs.max_size),
177 }
178 }
179
180 pub const fn saturating_sub(self, rhs: StatementAllowance) -> StatementAllowance {
182 StatementAllowance {
183 max_count: self.max_count.saturating_sub(rhs.max_count),
184 max_size: self.max_size.saturating_sub(rhs.max_size),
185 }
186 }
187
188 pub fn is_depleted(&self) -> bool {
191 self.max_count == 0 || self.max_size == 0
192 }
193}
194
195pub const STATEMENT_ALLOWANCE_PREFIX: &[u8] = b":statement_allowance:";
197
198pub fn statement_allowance_key(account_id: impl AsRef<[u8]>) -> Vec<u8> {
206 let mut key = STATEMENT_ALLOWANCE_PREFIX.to_vec();
207 key.extend_from_slice(account_id.as_ref());
208 key
209}
210
211pub fn increase_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
213 let key = statement_allowance_key(account_id);
214 let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
215 allowance = allowance.saturating_add(by);
216 frame_support::storage::unhashed::put(&key, &allowance);
217}
218
219pub fn decrease_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
221 let key = statement_allowance_key(account_id);
222 let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
223 allowance = allowance.saturating_sub(by);
224 if allowance.is_depleted() {
225 frame_support::storage::unhashed::kill(&key);
226 } else {
227 frame_support::storage::unhashed::put(&key, &allowance);
228 }
229}
230
231pub fn get_allowance(account_id: impl AsRef<[u8]>) -> StatementAllowance {
233 let key = statement_allowance_key(account_id);
234 frame_support::storage::unhashed::get_or_default(&key)
235}
236
237pub use event::{
238 AddFilterResponse, LimitReachedResult, LimitReachedTag, NewStatementEntry, SubscribeEvent,
239};
240#[cfg(feature = "std")]
241pub use store_api::{
242 AdmittedBatch, Error, FilterDecision, FilterId, InvalidReason, LiveStatementEvent,
243 OptimizedTopicFilter, RejectionReason, Result, StatementEvent, StatementSource, StatementStore,
244 SubmitInvalidReason, SubmitOutcome, SubmitRejectionReason, SubmitResult, TopicFilter,
245};
246
247#[cfg(feature = "std")]
248mod ecies;
249mod event;
250pub mod runtime_api;
251#[cfg(feature = "std")]
252mod store_api;
253
254mod sr25519 {
255 mod app_sr25519 {
256 use sp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519};
257 app_crypto!(sr25519, STATEMENT);
258 }
259 pub type Public = app_sr25519::Public;
260}
261
262pub mod ed25519 {
264 mod app_ed25519 {
265 use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT};
266 app_crypto!(ed25519, STATEMENT);
267 }
268 pub type Public = app_ed25519::Public;
270 #[cfg(feature = "std")]
272 pub type Pair = app_ed25519::Pair;
273}
274
275mod ecdsa {
276 mod app_ecdsa {
277 use sp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT};
278 app_crypto!(ecdsa, STATEMENT);
279 }
280 pub type Public = app_ecdsa::Public;
281}
282
283#[cfg(feature = "std")]
285pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
286 sp_crypto_hashing::blake2_256(data)
287}
288
289#[derive(
291 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, Clone, PartialEq, Eq,
292)]
293pub enum Proof {
294 Sr25519 {
296 signature: [u8; 64],
298 signer: [u8; 32],
300 },
301 Ed25519 {
303 signature: [u8; 64],
305 signer: [u8; 32],
307 },
308 Secp256k1Ecdsa {
310 signature: [u8; 65],
312 signer: [u8; 33],
314 },
315}
316
317impl Proof {
318 pub fn account_id(&self) -> AccountId {
320 match self {
321 Proof::Sr25519 { signer, .. } => *signer,
322 Proof::Ed25519 { signer, .. } => *signer,
323 Proof::Secp256k1Ecdsa { signer, .. } => {
324 <sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer).into()
325 },
326 }
327 }
328}
329
330#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
333#[repr(u8)]
334pub enum Field {
335 AuthenticityProof(Proof) = 0,
337 DecryptionKey(DecryptionKey) = 1,
339 Expiry(u64) = 2,
341 Channel(Channel) = 3,
343 Topic1(Topic) = 4,
345 Topic2(Topic) = 5,
347 Topic3(Topic) = 6,
349 Topic4(Topic) = 7,
351 Data(Vec<u8>) = 8,
353}
354
355impl Field {
356 fn discriminant(&self) -> u8 {
357 unsafe { *(self as *const Self as *const u8) }
360 }
361}
362
363#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
365pub struct Statement {
366 proof: Option<Proof>,
368 #[deprecated(note = "Experimental feature, may be removed/changed in future releases")]
370 decryption_key: Option<DecryptionKey>,
371 channel: Option<Channel>,
382 expiry: u64,
397 num_topics: u8,
399 topics: [Topic; MAX_TOPICS],
401 data: Option<Vec<u8>>,
403}
404
405impl TypeInfo for Statement {
408 type Identity = Self;
409
410 fn type_info() -> Type {
411 Type::builder()
413 .path(Path::new("Statement", module_path!()))
414 .docs(&["Statement structure"])
415 .composite(Fields::unnamed().field(|f| f.ty::<Vec<Field>>()))
416 }
417}
418
419impl Decode for Statement {
420 fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
421 let num_fields: codec::Compact<u32> = Decode::decode(input)?;
424 let mut tag = 0;
425 let mut statement = Statement::new();
426 for i in 0..num_fields.into() {
427 let field: Field = Decode::decode(input)?;
428 if i > 0 && field.discriminant() <= tag {
429 return Err("Invalid field order or duplicate fields".into());
430 }
431 tag = field.discriminant();
432 match field {
433 Field::AuthenticityProof(p) => statement.set_proof(p),
434 Field::DecryptionKey(key) => statement.set_decryption_key(key),
435 Field::Expiry(p) => statement.set_expiry(p),
436 Field::Channel(c) => statement.set_channel(c),
437 Field::Topic1(t) => statement.set_topic(0, t),
438 Field::Topic2(t) => statement.set_topic(1, t),
439 Field::Topic3(t) => statement.set_topic(2, t),
440 Field::Topic4(t) => statement.set_topic(3, t),
441 Field::Data(data) => statement.set_plain_data(data),
442 }
443 }
444 Ok(statement)
445 }
446}
447
448impl Encode for Statement {
449 fn encode(&self) -> Vec<u8> {
450 self.encoded(false)
451 }
452}
453
454#[derive(Clone, Copy, PartialEq, Eq, Debug)]
455pub enum SignatureVerificationResult {
457 Valid(AccountId),
459 Invalid,
461 NoSignature,
463}
464
465impl Statement {
466 pub fn new() -> Statement {
468 Default::default()
469 }
470
471 pub fn new_with_proof(proof: Proof) -> Statement {
473 let mut statement = Self::new();
474 statement.set_proof(proof);
475 statement
476 }
477
478 pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool {
484 let to_sign = self.signature_material();
485 if let Some(signature) = key.sign(&to_sign) {
486 let proof = Proof::Sr25519 {
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 pub fn topics(&self) -> &[Topic] {
499 &self.topics[..self.num_topics as usize]
500 }
501
502 #[cfg(feature = "std")]
504 pub fn sign_sr25519_private(&mut self, key: &sp_core::sr25519::Pair) {
505 let to_sign = self.signature_material();
506 let proof =
507 Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
508 self.set_proof(proof);
509 }
510
511 pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool {
517 let to_sign = self.signature_material();
518 if let Some(signature) = key.sign(&to_sign) {
519 let proof = Proof::Ed25519 {
520 signature: signature.into_inner().into(),
521 signer: key.clone().into_inner().into(),
522 };
523 self.set_proof(proof);
524 true
525 } else {
526 false
527 }
528 }
529
530 #[cfg(feature = "std")]
532 pub fn sign_ed25519_private(&mut self, key: &sp_core::ed25519::Pair) {
533 let to_sign = self.signature_material();
534 let proof =
535 Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
536 self.set_proof(proof);
537 }
538
539 pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool {
545 let to_sign = self.signature_material();
546 if let Some(signature) = key.sign(&to_sign) {
547 let proof = Proof::Secp256k1Ecdsa {
548 signature: signature.into_inner().into(),
549 signer: key.clone().into_inner().0,
550 };
551 self.set_proof(proof);
552 true
553 } else {
554 false
555 }
556 }
557
558 #[cfg(feature = "std")]
560 pub fn sign_ecdsa_private(&mut self, key: &sp_core::ecdsa::Pair) {
561 let to_sign = self.signature_material();
562 let proof =
563 Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 };
564 self.set_proof(proof);
565 }
566
567 pub fn verify_signature(&self) -> SignatureVerificationResult {
574 use sp_runtime::traits::Verify;
575
576 match self.proof() {
577 None => SignatureVerificationResult::NoSignature,
578 Some(Proof::Sr25519 { signature, signer }) => {
579 let to_sign = self.signature_material();
580 let signature = sp_core::sr25519::Signature::from(*signature);
581 let public = sp_core::sr25519::Public::from(*signer);
582 if signature.verify(to_sign.as_slice(), &public) {
583 SignatureVerificationResult::Valid(*signer)
584 } else {
585 SignatureVerificationResult::Invalid
586 }
587 },
588 Some(Proof::Ed25519 { signature, signer }) => {
589 let to_sign = self.signature_material();
590 let signature = sp_core::ed25519::Signature::from(*signature);
591 let public = sp_core::ed25519::Public::from(*signer);
592 if signature.verify(to_sign.as_slice(), &public) {
593 SignatureVerificationResult::Valid(*signer)
594 } else {
595 SignatureVerificationResult::Invalid
596 }
597 },
598 Some(Proof::Secp256k1Ecdsa { signature, signer }) => {
599 let to_sign = self.signature_material();
600 let signature = sp_core::ecdsa::Signature::from(*signature);
601 let public = sp_core::ecdsa::Public::from(*signer);
602 if signature.verify(to_sign.as_slice(), &public) {
603 let sender_hash =
604 <sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer);
605 SignatureVerificationResult::Valid(sender_hash.into())
606 } else {
607 SignatureVerificationResult::Invalid
608 }
609 },
610 }
611 }
612
613 #[cfg(feature = "std")]
619 pub fn hash(&self) -> [u8; 32] {
620 self.using_encoded(hash_encoded)
621 }
622
623 pub fn topic(&self, index: usize) -> Option<Topic> {
625 if index < self.num_topics as usize {
626 Some(self.topics[index])
627 } else {
628 None
629 }
630 }
631
632 #[allow(deprecated)]
634 pub fn decryption_key(&self) -> Option<DecryptionKey> {
635 self.decryption_key
636 }
637
638 pub fn into_data(self) -> Option<Vec<u8>> {
640 self.data
641 }
642
643 pub fn proof(&self) -> Option<&Proof> {
645 self.proof.as_ref()
646 }
647
648 pub fn account_id(&self) -> Option<AccountId> {
650 self.proof.as_ref().map(Proof::account_id)
651 }
652
653 pub fn data(&self) -> Option<&Vec<u8>> {
657 self.data.as_ref()
658 }
659
660 pub fn data_len(&self) -> usize {
665 self.data().map_or(0, Vec::len)
666 }
667
668 pub fn channel(&self) -> Option<Channel> {
670 self.channel
671 }
672
673 pub fn expiry(&self) -> u64 {
675 self.expiry
676 }
677
678 pub fn get_expiration_timestamp_secs(&self) -> u32 {
683 (self.expiry >> 32) as u32
684 }
685
686 pub fn is_expired(&self, now_secs: u64) -> bool {
690 now_secs >= u64::from(self.get_expiration_timestamp_secs())
691 }
692
693 fn signature_material(&self) -> Vec<u8> {
695 self.encoded(true)
696 }
697
698 pub fn remove_proof(&mut self) {
700 self.proof = None;
701 }
702
703 pub fn set_proof(&mut self, proof: Proof) {
705 self.proof = Some(proof)
706 }
707
708 pub fn set_expiry(&mut self, expiry: u64) {
710 self.expiry = expiry;
711 }
712
713 pub fn set_expiry_from_parts(&mut self, expiration_timestamp_secs: u32, sequence_number: u32) {
715 self.expiry = (expiration_timestamp_secs as u64) << 32 | sequence_number as u64;
716 }
717
718 pub fn set_channel(&mut self, channel: Channel) {
720 self.channel = Some(channel)
721 }
722
723 pub fn set_topic(&mut self, index: usize, topic: Topic) {
727 if index < MAX_TOPICS {
728 self.topics[index] = topic;
729 self.num_topics = self.num_topics.max(index as u8 + 1);
730 }
731 }
732
733 #[allow(deprecated)]
735 pub fn set_decryption_key(&mut self, key: DecryptionKey) {
736 self.decryption_key = Some(key);
737 }
738
739 pub fn set_plain_data(&mut self, data: Vec<u8>) {
741 self.data = Some(data)
742 }
743
744 #[allow(deprecated)]
756 fn estimated_encoded_size(&self, for_signing: bool) -> usize {
757 let proof_size =
758 if !for_signing && self.proof.is_some() { 1 + Proof::max_encoded_len() } else { 0 };
759 let decryption_key_size =
760 if self.decryption_key.is_some() { 1 + DecryptionKey::max_encoded_len() } else { 0 };
761 let expiry_size = 1 + u64::max_encoded_len();
762 let channel_size = if self.channel.is_some() { 1 + Channel::max_encoded_len() } else { 0 };
763 let topics_size = self.num_topics as usize * (1 + Topic::max_encoded_len());
764 let data_size = self
765 .data
766 .as_ref()
767 .map_or(0, |d| 1 + Compact::<u32>::max_encoded_len() + d.len());
768 let compact_prefix_size = if !for_signing { Compact::<u32>::max_encoded_len() } else { 0 };
769
770 compact_prefix_size +
771 proof_size +
772 decryption_key_size +
773 expiry_size +
774 channel_size +
775 topics_size +
776 data_size
777 }
778
779 #[allow(deprecated)]
780 fn encoded(&self, for_signing: bool) -> Vec<u8> {
781 let num_fields = if !for_signing && self.proof.is_some() { 2 } else { 1 } +
785 if self.decryption_key.is_some() { 1 } else { 0 } +
786 if self.channel.is_some() { 1 } else { 0 } +
787 if self.data.is_some() { 1 } else { 0 } +
788 self.num_topics as u32;
789
790 let mut output = Vec::with_capacity(self.estimated_encoded_size(for_signing));
791 if !for_signing {
795 let compact_len = codec::Compact::<u32>(num_fields);
796 compact_len.encode_to(&mut output);
797
798 if let Some(proof) = &self.proof {
799 0u8.encode_to(&mut output);
800 proof.encode_to(&mut output);
801 }
802 }
803 if let Some(decryption_key) = &self.decryption_key {
804 1u8.encode_to(&mut output);
805 decryption_key.encode_to(&mut output);
806 }
807
808 2u8.encode_to(&mut output);
809 self.expiry().encode_to(&mut output);
810
811 if let Some(channel) = &self.channel {
812 3u8.encode_to(&mut output);
813 channel.encode_to(&mut output);
814 }
815 for t in 0..self.num_topics {
816 (4u8 + t).encode_to(&mut output);
817 self.topics[t as usize].encode_to(&mut output);
818 }
819 if let Some(data) = &self.data {
820 8u8.encode_to(&mut output);
821 data.encode_to(&mut output);
822 }
823 output
824 }
825
826 #[allow(deprecated)]
831 #[cfg(feature = "std")]
832 pub fn encrypt(
833 &mut self,
834 data: &[u8],
835 key: &sp_core::ed25519::Public,
836 ) -> core::result::Result<(), ecies::Error> {
837 let encrypted = ecies::encrypt_ed25519(key, data)?;
838 self.data = Some(encrypted);
839 self.decryption_key = Some((*key).into());
840 Ok(())
841 }
842
843 #[cfg(feature = "std")]
848 pub fn decrypt_private(
849 &self,
850 key: &sp_core::ed25519::Pair,
851 ) -> core::result::Result<Option<Vec<u8>>, ecies::Error> {
852 self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose()
853 }
854}
855
856#[cfg(test)]
857mod test {
858 use crate::{
859 hash_encoded, Field, Proof, SignatureVerificationResult, Statement, Topic, MAX_TOPICS,
860 };
861 use codec::{Decode, Encode, MaxEncodedLen};
862 use core::str::FromStr;
863 use scale_info::{MetaType, TypeInfo};
864 use sp_application_crypto::Pair;
865 use sp_core::sr25519;
866
867 #[test]
868 fn topic_from_str_accepts_either_case_with_optional_prefix() {
869 let expected = Topic([0xAB; 32]);
870 let lower = "ab".repeat(32);
871 let upper = "AB".repeat(32);
872 assert_eq!(Topic::from_str(&lower), Ok(expected));
873 assert_eq!(Topic::from_str(&upper), Ok(expected));
874 assert_eq!(Topic::from_str(&format!("0x{lower}")), Ok(expected));
875 assert_eq!(Topic::from_str(&format!("0x{upper}")), Ok(expected));
876 }
877
878 #[test]
879 fn topic_from_str_rejects_empty_wrong_length_and_bad_hex() {
880 assert!(Topic::from_str("").is_err());
881 assert!(Topic::from_str("0xdead").is_err());
882 assert!(Topic::from_str(&"zz".repeat(32)).is_err());
883 }
884
885 #[test]
886 fn statement_encoding_matches_vec() {
887 let mut statement = Statement::new();
888 assert!(statement.proof().is_none());
889 let proof = Proof::Sr25519 { signature: [42u8; 64], signer: [24u8; 32] };
890
891 let decryption_key = [0xde; 32];
892 let topic1: Topic = [0x01; 32].into();
893 let topic2: Topic = [0x02; 32].into();
894 let data = vec![55, 99];
895 let expiry = 999;
896 let channel = [0xcc; 32];
897
898 statement.set_proof(proof.clone());
899 statement.set_decryption_key(decryption_key);
900 statement.set_expiry(expiry);
901 statement.set_channel(channel);
902 statement.set_topic(0, topic1);
903 statement.set_topic(1, topic2);
904 statement.set_plain_data(data.clone());
905
906 statement.set_topic(5, [0x55; 32].into());
907 assert_eq!(statement.topic(5), None);
908
909 let fields = vec![
910 Field::AuthenticityProof(proof.clone()),
911 Field::DecryptionKey(decryption_key),
912 Field::Expiry(expiry),
913 Field::Channel(channel),
914 Field::Topic1(topic1),
915 Field::Topic2(topic2),
916 Field::Data(data.clone()),
917 ];
918
919 let encoded = statement.encode();
920 assert_eq!(statement.hash(), hash_encoded(&encoded));
921 assert_eq!(encoded, fields.encode());
922
923 let decoded = Statement::decode(&mut encoded.as_slice()).unwrap();
924 assert_eq!(decoded, statement);
925 }
926
927 #[test]
928 fn decode_checks_fields() {
929 let topic1: Topic = [0x01; 32].into();
930 let topic2: Topic = [0x02; 32].into();
931 let priority = 999;
932
933 let dup_topic1 = vec![
934 Field::Expiry(priority),
935 Field::Topic1(topic1),
936 Field::Topic1(topic1),
937 Field::Topic2(topic2),
938 ]
939 .encode();
940 assert!(Statement::decode(&mut dup_topic1.as_slice()).is_err());
941
942 let topic1_before_expiry =
943 vec![Field::Topic1(topic1), Field::Expiry(priority), Field::Topic2(topic2)].encode();
944 assert!(Statement::decode(&mut topic1_before_expiry.as_slice()).is_err());
945
946 let dup_expiry = vec![Field::Expiry(1), Field::Expiry(2)].encode();
947 assert!(Statement::decode(&mut dup_expiry.as_slice()).is_err());
948
949 let dup_data = vec![Field::Data(vec![1]), Field::Data(vec![2])].encode();
950 assert!(Statement::decode(&mut dup_data.as_slice()).is_err());
951
952 let data_before_expiry = vec![Field::Data(vec![1]), Field::Expiry(42)].encode();
953 assert!(Statement::decode(&mut data_before_expiry.as_slice()).is_err());
954
955 let channel_before_expiry = vec![Field::Channel([0; 32]), Field::Expiry(1)].encode();
956 assert!(Statement::decode(&mut channel_before_expiry.as_slice()).is_err());
957
958 let topic2_before_topic1 =
959 vec![Field::Expiry(1), Field::Topic2(topic1), Field::Topic1(topic2)].encode();
960 assert!(Statement::decode(&mut topic2_before_topic1.as_slice()).is_err());
961 }
962
963 #[test]
964 fn decode_rejects_malformed_bytes() {
965 assert!(Statement::decode(&mut &[][..]).is_err());
966
967 let valid = vec![Field::Expiry(42)].encode();
969 let decoded = Statement::decode(&mut valid.as_slice()).unwrap();
970 assert_eq!(decoded.expiry(), 42);
971
972 assert!(Statement::decode(&mut &valid[..1][..]).is_err());
974
975 let mut invalid_discriminant = valid.clone();
977 invalid_discriminant[1] = 9;
978 assert!(Statement::decode(&mut invalid_discriminant.as_slice()).is_err());
979
980 invalid_discriminant[1] = 255;
981 assert!(Statement::decode(&mut invalid_discriminant.as_slice()).is_err());
982
983 assert!(Statement::decode(&mut &valid[..5][..]).is_err());
985
986 let with_proof = vec![
988 Field::AuthenticityProof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] }),
989 Field::Expiry(42),
990 ]
991 .encode();
992 assert!(Statement::decode(&mut with_proof.as_slice()).is_ok());
993
994 let mut invalid_proof_variant = with_proof.clone();
995 invalid_proof_variant[2] = 99;
996 assert!(Statement::decode(&mut invalid_proof_variant.as_slice()).is_err());
997
998 assert!(Statement::decode(&mut &with_proof[..6][..]).is_err());
1000
1001 let mut inflated_count = valid.clone();
1003 inflated_count[0] = 5 << 2; assert!(Statement::decode(&mut inflated_count.as_slice()).is_err());
1005 }
1006
1007 #[test]
1008 fn sign_and_verify() {
1009 let mut statement = Statement::new();
1010 statement.set_plain_data(vec![42]);
1011
1012 let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
1013 let ed25519_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
1014 let secp256k1_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
1015
1016 statement.sign_sr25519_private(&sr25519_kp);
1017 assert_eq!(
1018 statement.verify_signature(),
1019 SignatureVerificationResult::Valid(sr25519_kp.public().0)
1020 );
1021
1022 statement.sign_ed25519_private(&ed25519_kp);
1023 assert_eq!(
1024 statement.verify_signature(),
1025 SignatureVerificationResult::Valid(ed25519_kp.public().0)
1026 );
1027
1028 statement.sign_ecdsa_private(&secp256k1_kp);
1029 assert_eq!(
1030 statement.verify_signature(),
1031 SignatureVerificationResult::Valid(sp_crypto_hashing::blake2_256(
1032 &secp256k1_kp.public().0
1033 ))
1034 );
1035
1036 statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] });
1038 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1039
1040 statement.set_proof(Proof::Ed25519 { signature: [0xAB; 64], signer: [0xCD; 32] });
1042 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1043
1044 statement.set_proof(Proof::Secp256k1Ecdsa { signature: [0u8; 65], signer: [0u8; 33] });
1046 assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
1047
1048 statement.remove_proof();
1049 assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature);
1050 }
1051
1052 #[test]
1053 fn encrypt_decrypt() {
1054 let mut statement = Statement::new();
1055 let (pair, _) = sp_core::ed25519::Pair::generate();
1056 let plain = b"test data".to_vec();
1057
1058 statement.encrypt(&plain, &pair.public()).unwrap();
1060 assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice());
1061
1062 let decrypted = statement.decrypt_private(&pair).unwrap();
1063 assert_eq!(decrypted, Some(plain));
1064 }
1065
1066 #[test]
1067 fn check_matches() {
1068 let mut statement = Statement::new();
1069 let topic1: Topic = [0x01; 32].into();
1070 let topic2: Topic = [0x02; 32].into();
1071 let topic3: Topic = [0x03; 32].into();
1072
1073 statement.set_topic(0, topic1);
1074 statement.set_topic(1, topic2);
1075
1076 let filter_any = crate::OptimizedTopicFilter::Any;
1077 assert!(filter_any.matches(&statement));
1078
1079 let filter_all =
1080 crate::OptimizedTopicFilter::MatchAll([topic1, topic2].iter().cloned().collect());
1081 assert!(filter_all.matches(&statement));
1082
1083 let filter_all_fail =
1084 crate::OptimizedTopicFilter::MatchAll([topic1, topic3].iter().cloned().collect());
1085 assert!(!filter_all_fail.matches(&statement));
1086
1087 let filter_any_match =
1088 crate::OptimizedTopicFilter::MatchAny([topic2, topic3].iter().cloned().collect());
1089 assert!(filter_any_match.matches(&statement));
1090
1091 let filter_any_fail =
1092 crate::OptimizedTopicFilter::MatchAny([topic3].iter().cloned().collect());
1093 assert!(!filter_any_fail.matches(&statement));
1094 }
1095
1096 #[test]
1097 fn statement_type_info_matches_encoding() {
1098 let statement_type = Statement::type_info();
1101 let vec_field_meta = MetaType::new::<Vec<Field>>();
1102
1103 match statement_type.type_def {
1105 scale_info::TypeDef::Composite(composite) => {
1106 assert_eq!(composite.fields.len(), 1, "Statement should have exactly one field");
1107 let field = &composite.fields[0];
1108 assert!(field.name.is_none(), "Field should be unnamed (newtype pattern)");
1109 assert_eq!(field.ty, vec_field_meta, "Statement's inner type should be Vec<Field>");
1110 },
1111 _ => panic!("Statement TypeInfo should be a Composite"),
1112 }
1113 }
1114
1115 #[test]
1116 fn measure_hash_30_000_statements() {
1117 use std::time::Instant;
1118 const NUM_STATEMENTS: usize = 30_000;
1119 let (keyring, _) = sr25519::Pair::generate();
1120
1121 let statements: Vec<Statement> = (0..NUM_STATEMENTS)
1123 .map(|i| {
1124 let mut statement = Statement::new();
1125
1126 statement.set_expiry(i as u64);
1127 statement.set_topic(0, [(i % 256) as u8; 32].into());
1128 statement.set_plain_data(vec![i as u8; 512]);
1129 statement.sign_sr25519_private(&keyring);
1130
1131 statement.sign_sr25519_private(&keyring);
1132 statement
1133 })
1134 .collect();
1135 let start = Instant::now();
1137 let hashes: Vec<[u8; 32]> = statements.iter().map(|s| s.hash()).collect();
1138 let elapsed = start.elapsed();
1139 println!("Time to hash {} statements: {:?}", NUM_STATEMENTS, elapsed);
1140 println!("Average time per statement: {:?}", elapsed / NUM_STATEMENTS as u32);
1141 let unique_hashes: std::collections::HashSet<_> = hashes.iter().collect();
1143 assert_eq!(unique_hashes.len(), NUM_STATEMENTS);
1144 }
1145
1146 #[test]
1147 fn estimated_encoded_size_is_sufficient() {
1148 const MAX_ACCEPTED_OVERHEAD: usize = 33;
1150
1151 let proof = Proof::Secp256k1Ecdsa { signature: [42u8; 65], signer: [24u8; 33] };
1153 let decryption_key = [0xde; 32];
1154 let data = vec![55; 1000];
1155 let expiry = 999;
1156 let channel = [0xcc; 32];
1157
1158 let mut statement = Statement::new();
1160 statement.set_proof(proof);
1161 statement.set_decryption_key(decryption_key);
1162 statement.set_expiry(expiry);
1163 statement.set_channel(channel);
1164 for i in 0..MAX_TOPICS {
1165 statement.set_topic(i, [i as u8; 32].into());
1166 }
1167 statement.set_plain_data(data);
1168
1169 let encoded = statement.encode();
1170 let estimated = statement.estimated_encoded_size(false);
1171 assert!(
1172 estimated >= encoded.len(),
1173 "estimated_encoded_size ({}) should be >= actual encoded length ({})",
1174 estimated,
1175 encoded.len()
1176 );
1177 let overhead = estimated - encoded.len();
1178 assert!(
1179 overhead <= MAX_ACCEPTED_OVERHEAD,
1180 "estimated overhead ({}) should be small, estimated: {}, actual: {}",
1181 overhead,
1182 estimated,
1183 encoded.len()
1184 );
1185
1186 let signing_payload = statement.encoded(true);
1188 let signing_estimated = statement.estimated_encoded_size(true);
1189 assert!(
1190 signing_estimated >= signing_payload.len(),
1191 "estimated_encoded_size for signing ({}) should be >= actual signing payload length ({})",
1192 signing_estimated,
1193 signing_payload.len()
1194 );
1195 let signing_overhead = signing_estimated - signing_payload.len();
1196 assert!(
1197 signing_overhead <= MAX_ACCEPTED_OVERHEAD,
1198 "signing overhead ({}) should be small, estimated: {}, actual: {}",
1199 signing_overhead,
1200 signing_estimated,
1201 signing_payload.len()
1202 );
1203
1204 let empty_statement = Statement::new();
1206 let empty_encoded = empty_statement.encode();
1207 let empty_estimated = empty_statement.estimated_encoded_size(false);
1208 assert!(
1209 empty_estimated >= empty_encoded.len(),
1210 "estimated_encoded_size for empty ({}) should be >= actual encoded length ({})",
1211 empty_estimated,
1212 empty_encoded.len()
1213 );
1214 let empty_overhead = empty_estimated - empty_encoded.len();
1215 assert!(
1216 empty_overhead <= MAX_ACCEPTED_OVERHEAD,
1217 "empty overhead ({}) should be minimal, estimated: {}, actual: {}",
1218 empty_overhead,
1219 empty_estimated,
1220 empty_encoded.len()
1221 );
1222 }
1223
1224 fn populate_canonical_fixture(stmt: &mut Statement) {
1233 stmt.set_topic(0, [0x01; 32].into());
1234 stmt.set_topic(1, [0x02; 32].into());
1235 stmt.set_topic(2, [0x03; 32].into());
1236 stmt.set_channel([0xcc; 32]);
1237 stmt.set_expiry_from_parts(0x7fff_ffff, 0xabcd_1234);
1238 stmt.set_plain_data(vec![0xde, 0xad, 0xbe, 0xef]);
1239 }
1240
1241 fn canonical_tail() -> Vec<u8> {
1245 let mut v = Vec::new();
1246 v.push(0x02); v.extend_from_slice(&[0x34, 0x12, 0xcd, 0xab, 0xff, 0xff, 0xff, 0x7f]); v.push(0x03); v.extend_from_slice(&[0xcc; 32]);
1250 v.push(0x04); v.extend_from_slice(&[0x01; 32]);
1252 v.push(0x05); v.extend_from_slice(&[0x02; 32]);
1254 v.push(0x06); v.extend_from_slice(&[0x03; 32]);
1256 v.push(0x08); v.push(0x10); v.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
1259 v
1260 }
1261
1262 #[test]
1264 fn wire_format_sr25519_pinned() {
1265 let mut stmt = Statement::new();
1266 populate_canonical_fixture(&mut stmt);
1267 stmt.set_proof(Proof::Sr25519 { signature: [0x11; 64], signer: [0xAA; 32] });
1268
1269 let mut expected = Vec::new();
1270 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());
1276
1277 assert_eq!(stmt.encode(), expected, "Sr25519 wire format drifted");
1278 assert_eq!(expected.len(), 246);
1279 assert_eq!(Statement::decode(&mut expected.as_slice()).unwrap(), stmt);
1281 }
1282
1283 #[test]
1285 fn wire_format_legacy_onchain_proof_is_rejected() {
1286 let mut legacy = Vec::new();
1288 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!(
1298 Statement::decode(&mut legacy.as_slice()).is_err(),
1299 "legacy OnChain bytes must no longer decode into a Statement",
1300 );
1301
1302 let mut survivor = Vec::new();
1306 survivor.push(0x08);
1307 survivor.push(0x00);
1308 survivor.push(0x00); survivor.extend_from_slice(&[0xdd; 64]);
1310 survivor.extend_from_slice(&[0xee; 32]);
1311 survivor.push(0x02);
1312 survivor.extend_from_slice(&[0x2a, 0, 0, 0, 0, 0, 0, 0]);
1313 assert!(
1314 Statement::decode(&mut survivor.as_slice()).is_ok(),
1315 "surviving variant in the same byte layout must still decode",
1316 );
1317 }
1318
1319 #[test]
1321 fn proof_max_encoded_len_after_onchain_removal() {
1322 assert_eq!(
1323 Proof::max_encoded_len(),
1324 1 + 65 + 33,
1325 "max_encoded_len must equal Secp256k1Ecdsa's payload + 1-byte discriminant",
1326 );
1327 }
1328}