1#![cfg_attr(not(feature = "std"), no_std)]
8#![warn(missing_docs)]
9
10extern 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#[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
100pub type DecryptionKey = [u8; 32];
102pub type Hash = [u8; 32];
104pub type BlockHash = [u8; 32];
106pub type AccountId = [u8; 32];
108pub type Channel = [u8; 32];
110
111pub const MAX_TOPICS: usize = 4;
113pub const MAX_ANY_TOPICS: usize = 128;
116
117#[derive(Clone, Default, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
119pub struct StatementAllowance {
120 pub max_count: u32,
122 pub max_size: u32,
124}
125
126impl StatementAllowance {
127 pub fn new(max_count: u32, max_size: u32) -> Self {
129 Self { max_count, max_size }
130 }
131
132 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 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 pub fn is_depleted(&self) -> bool {
150 self.max_count == 0 || self.max_size == 0
151 }
152}
153
154pub const STATEMENT_ALLOWANCE_PREFIX: &[u8] = b":statement_allowance:";
156
157pub 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
170pub 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
179pub 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
192pub 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
218pub mod ed25519 {
220 mod app_ed25519 {
221 use subsoil::application_crypto::{ed25519, key_types::STATEMENT};
222 subsoil::app_crypto!(ed25519, STATEMENT);
223 }
224 pub type Public = app_ed25519::Public;
226 #[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#[cfg(feature = "std")]
241pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
242 subsoil_crypto_hashing::blake2_256(data)
243}
244
245#[derive(
247 Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, Clone, PartialEq, Eq,
248)]
249pub enum Proof {
250 Sr25519 {
252 signature: [u8; 64],
254 signer: [u8; 32],
256 },
257 Ed25519 {
259 signature: [u8; 64],
261 signer: [u8; 32],
263 },
264 Secp256k1Ecdsa {
266 signature: [u8; 65],
268 signer: [u8; 33],
270 },
271 OnChain {
273 who: AccountId,
275 block_hash: BlockHash,
277 event_index: u64,
279 },
280}
281
282impl Proof {
283 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#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
300#[repr(u8)]
301pub enum Field {
302 AuthenticityProof(Proof) = 0,
304 DecryptionKey(DecryptionKey) = 1,
306 Expiry(u64) = 2,
308 Channel(Channel) = 3,
310 Topic1(Topic) = 4,
312 Topic2(Topic) = 5,
314 Topic3(Topic) = 6,
316 Topic4(Topic) = 7,
318 Data(Vec<u8>) = 8,
320}
321
322impl Field {
323 fn discriminant(&self) -> u8 {
324 unsafe { *(self as *const Self as *const u8) }
327 }
328}
329
330#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
332pub struct Statement {
333 proof: Option<Proof>,
335 #[deprecated(note = "Experimental feature, may be removed/changed in future releases")]
337 decryption_key: Option<DecryptionKey>,
338 channel: Option<Channel>,
349 expiry: u64,
364 num_topics: u8,
366 topics: [Topic; MAX_TOPICS],
368 data: Option<Vec<u8>>,
370}
371
372impl TypeInfo for Statement {
375 type Identity = Self;
376
377 fn type_info() -> Type {
378 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 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)]
422pub enum SignatureVerificationResult {
424 Valid(AccountId),
426 Invalid,
428 NoSignature,
430}
431
432impl Statement {
433 pub fn new() -> Statement {
435 Default::default()
436 }
437
438 pub fn new_with_proof(proof: Proof) -> Statement {
440 let mut statement = Self::new();
441 statement.set_proof(proof);
442 statement
443 }
444
445 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 pub fn topics(&self) -> &[Topic] {
466 &self.topics[..self.num_topics as usize]
467 }
468
469 #[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 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 #[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 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 #[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 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 #[cfg(feature = "std")]
583 pub fn hash(&self) -> [u8; 32] {
584 self.using_encoded(hash_encoded)
585 }
586
587 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 #[allow(deprecated)]
598 pub fn decryption_key(&self) -> Option<DecryptionKey> {
599 self.decryption_key
600 }
601
602 pub fn into_data(self) -> Option<Vec<u8>> {
604 self.data
605 }
606
607 pub fn proof(&self) -> Option<&Proof> {
609 self.proof.as_ref()
610 }
611
612 pub fn account_id(&self) -> Option<AccountId> {
614 self.proof.as_ref().map(Proof::account_id)
615 }
616
617 pub fn data(&self) -> Option<&Vec<u8>> {
619 self.data.as_ref()
620 }
621
622 pub fn data_len(&self) -> usize {
624 self.data().map_or(0, Vec::len)
625 }
626
627 pub fn channel(&self) -> Option<Channel> {
629 self.channel
630 }
631
632 pub fn expiry(&self) -> u64 {
634 self.expiry
635 }
636
637 pub fn get_expiration_timestamp_secs(&self) -> u32 {
642 (self.expiry >> 32) as u32
643 }
644
645 fn signature_material(&self) -> Vec<u8> {
647 self.encoded(true)
648 }
649
650 pub fn remove_proof(&mut self) {
652 self.proof = None;
653 }
654
655 pub fn set_proof(&mut self, proof: Proof) {
657 self.proof = Some(proof)
658 }
659
660 pub fn set_expiry(&mut self, expiry: u64) {
662 self.expiry = expiry;
663 }
664
665 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 pub fn set_channel(&mut self, channel: Channel) {
672 self.channel = Some(channel)
673 }
674
675 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 #[allow(deprecated)]
685 pub fn set_decryption_key(&mut self, key: DecryptionKey) {
686 self.decryption_key = Some(key);
687 }
688
689 pub fn set_plain_data(&mut self, data: Vec<u8>) {
691 self.data = Some(data)
692 }
693
694 #[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 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 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 #[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 #[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 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 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 let statement_type = Statement::type_info();
960 let vec_field_meta = MetaType::new::<Vec<Field>>();
961
962 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 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 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 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 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 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 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 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}