#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
extern crate alloc;
use alloc::vec::Vec;
use codec::{Compact, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::ops::Deref;
use scale_info::{build::Fields, Path, Type, TypeInfo};
use sp_application_crypto::RuntimeAppPublic;
#[cfg(feature = "std")]
use sp_core::Pair;
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Encode,
Decode,
DecodeWithMemTracking,
MaxEncodedLen,
TypeInfo,
)]
pub struct Topic(pub [u8; 32]);
#[cfg(feature = "serde")]
impl serde::Serialize for Topic {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
sp_core::bytes::serialize(&self.0, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Topic {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let mut arr = [0u8; 32];
sp_core::bytes::deserialize_check_len(
deserializer,
sp_core::bytes::ExpectedLen::Exact(&mut arr[..]),
)?;
Ok(Topic(arr))
}
}
impl From<[u8; 32]> for Topic {
fn from(inner: [u8; 32]) -> Self {
Topic(inner)
}
}
impl From<Topic> for [u8; 32] {
fn from(topic: Topic) -> Self {
topic.0
}
}
impl AsRef<[u8; 32]> for Topic {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl AsRef<[u8]> for Topic {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl Deref for Topic {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type DecryptionKey = [u8; 32];
pub type Hash = [u8; 32];
pub type BlockHash = [u8; 32];
pub type AccountId = [u8; 32];
pub type Channel = [u8; 32];
pub const MAX_TOPICS: usize = 4;
pub const MAX_ANY_TOPICS: usize = 128;
#[derive(Clone, Default, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo)]
pub struct StatementAllowance {
pub max_count: u32,
pub max_size: u32,
}
impl StatementAllowance {
pub fn new(max_count: u32, max_size: u32) -> Self {
Self { max_count, max_size }
}
pub const fn saturating_add(self, rhs: StatementAllowance) -> StatementAllowance {
StatementAllowance {
max_count: self.max_count.saturating_add(rhs.max_count),
max_size: self.max_size.saturating_add(rhs.max_size),
}
}
pub const fn saturating_sub(self, rhs: StatementAllowance) -> StatementAllowance {
StatementAllowance {
max_count: self.max_count.saturating_sub(rhs.max_count),
max_size: self.max_size.saturating_sub(rhs.max_size),
}
}
pub fn is_depleted(&self) -> bool {
self.max_count == 0 || self.max_size == 0
}
}
pub const STATEMENT_ALLOWANCE_PREFIX: &[u8] = b":statement_allowance:";
pub fn statement_allowance_key(account_id: impl AsRef<[u8]>) -> Vec<u8> {
let mut key = STATEMENT_ALLOWANCE_PREFIX.to_vec();
key.extend_from_slice(account_id.as_ref());
key
}
pub fn increase_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
let key = statement_allowance_key(account_id);
let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
allowance = allowance.saturating_add(by);
frame_support::storage::unhashed::put(&key, &allowance);
}
pub fn decrease_allowance_by(account_id: impl AsRef<[u8]>, by: StatementAllowance) {
let key = statement_allowance_key(account_id);
let mut allowance: StatementAllowance = frame_support::storage::unhashed::get_or_default(&key);
allowance = allowance.saturating_sub(by);
if allowance.is_depleted() {
frame_support::storage::unhashed::kill(&key);
} else {
frame_support::storage::unhashed::put(&key, &allowance);
}
}
pub fn get_allowance(account_id: impl AsRef<[u8]>) -> StatementAllowance {
let key = statement_allowance_key(account_id);
frame_support::storage::unhashed::get_or_default(&key)
}
#[cfg(feature = "std")]
pub use store_api::{
Error, FilterDecision, InvalidReason, OptimizedTopicFilter, RejectionReason, Result,
StatementEvent, StatementSource, StatementStore, SubmitResult, TopicFilter,
};
#[cfg(feature = "std")]
mod ecies;
pub mod runtime_api;
#[cfg(feature = "std")]
mod store_api;
mod sr25519 {
mod app_sr25519 {
use sp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519};
app_crypto!(sr25519, STATEMENT);
}
pub type Public = app_sr25519::Public;
}
pub mod ed25519 {
mod app_ed25519 {
use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT};
app_crypto!(ed25519, STATEMENT);
}
pub type Public = app_ed25519::Public;
#[cfg(feature = "std")]
pub type Pair = app_ed25519::Pair;
}
mod ecdsa {
mod app_ecdsa {
use sp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT};
app_crypto!(ecdsa, STATEMENT);
}
pub type Public = app_ecdsa::Public;
}
#[cfg(feature = "std")]
pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
sp_crypto_hashing::blake2_256(data)
}
#[derive(
Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo, Debug, Clone, PartialEq, Eq,
)]
pub enum Proof {
Sr25519 {
signature: [u8; 64],
signer: [u8; 32],
},
Ed25519 {
signature: [u8; 64],
signer: [u8; 32],
},
Secp256k1Ecdsa {
signature: [u8; 65],
signer: [u8; 33],
},
OnChain {
who: AccountId,
block_hash: BlockHash,
event_index: u64,
},
}
impl Proof {
pub fn account_id(&self) -> AccountId {
match self {
Proof::Sr25519 { signer, .. } => *signer,
Proof::Ed25519 { signer, .. } => *signer,
Proof::Secp256k1Ecdsa { signer, .. } => {
<sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer).into()
},
Proof::OnChain { who, .. } => *who,
}
}
}
#[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum Field {
AuthenticityProof(Proof) = 0,
DecryptionKey(DecryptionKey) = 1,
Expiry(u64) = 2,
Channel(Channel) = 3,
Topic1(Topic) = 4,
Topic2(Topic) = 5,
Topic3(Topic) = 6,
Topic4(Topic) = 7,
Data(Vec<u8>) = 8,
}
impl Field {
fn discriminant(&self) -> u8 {
unsafe { *(self as *const Self as *const u8) }
}
}
#[derive(DecodeWithMemTracking, Debug, Clone, PartialEq, Eq, Default)]
pub struct Statement {
proof: Option<Proof>,
#[deprecated(note = "Experimental feature, may be removed/changed in future releases")]
decryption_key: Option<DecryptionKey>,
channel: Option<Channel>,
expiry: u64,
num_topics: u8,
topics: [Topic; MAX_TOPICS],
data: Option<Vec<u8>>,
}
impl TypeInfo for Statement {
type Identity = Self;
fn type_info() -> Type {
Type::builder()
.path(Path::new("Statement", module_path!()))
.docs(&["Statement structure"])
.composite(Fields::unnamed().field(|f| f.ty::<Vec<Field>>()))
}
}
impl Decode for Statement {
fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
let num_fields: codec::Compact<u32> = Decode::decode(input)?;
let mut tag = 0;
let mut statement = Statement::new();
for i in 0..num_fields.into() {
let field: Field = Decode::decode(input)?;
if i > 0 && field.discriminant() <= tag {
return Err("Invalid field order or duplicate fields".into());
}
tag = field.discriminant();
match field {
Field::AuthenticityProof(p) => statement.set_proof(p),
Field::DecryptionKey(key) => statement.set_decryption_key(key),
Field::Expiry(p) => statement.set_expiry(p),
Field::Channel(c) => statement.set_channel(c),
Field::Topic1(t) => statement.set_topic(0, t),
Field::Topic2(t) => statement.set_topic(1, t),
Field::Topic3(t) => statement.set_topic(2, t),
Field::Topic4(t) => statement.set_topic(3, t),
Field::Data(data) => statement.set_plain_data(data),
}
}
Ok(statement)
}
}
impl Encode for Statement {
fn encode(&self) -> Vec<u8> {
self.encoded(false)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SignatureVerificationResult {
Valid(AccountId),
Invalid,
NoSignature,
}
impl Statement {
pub fn new() -> Statement {
Default::default()
}
pub fn new_with_proof(proof: Proof) -> Statement {
let mut statement = Self::new();
statement.set_proof(proof);
statement
}
pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool {
let to_sign = self.signature_material();
if let Some(signature) = key.sign(&to_sign) {
let proof = Proof::Sr25519 {
signature: signature.into_inner().into(),
signer: key.clone().into_inner().into(),
};
self.set_proof(proof);
true
} else {
false
}
}
pub fn topics(&self) -> &[Topic] {
&self.topics[..self.num_topics as usize]
}
#[cfg(feature = "std")]
pub fn sign_sr25519_private(&mut self, key: &sp_core::sr25519::Pair) {
let to_sign = self.signature_material();
let proof =
Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
self.set_proof(proof);
}
pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool {
let to_sign = self.signature_material();
if let Some(signature) = key.sign(&to_sign) {
let proof = Proof::Ed25519 {
signature: signature.into_inner().into(),
signer: key.clone().into_inner().into(),
};
self.set_proof(proof);
true
} else {
false
}
}
#[cfg(feature = "std")]
pub fn sign_ed25519_private(&mut self, key: &sp_core::ed25519::Pair) {
let to_sign = self.signature_material();
let proof =
Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() };
self.set_proof(proof);
}
pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool {
let to_sign = self.signature_material();
if let Some(signature) = key.sign(&to_sign) {
let proof = Proof::Secp256k1Ecdsa {
signature: signature.into_inner().into(),
signer: key.clone().into_inner().0,
};
self.set_proof(proof);
true
} else {
false
}
}
#[cfg(feature = "std")]
pub fn sign_ecdsa_private(&mut self, key: &sp_core::ecdsa::Pair) {
let to_sign = self.signature_material();
let proof =
Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 };
self.set_proof(proof);
}
pub fn verify_signature(&self) -> SignatureVerificationResult {
use sp_runtime::traits::Verify;
match self.proof() {
Some(Proof::OnChain { .. }) | None => SignatureVerificationResult::NoSignature,
Some(Proof::Sr25519 { signature, signer }) => {
let to_sign = self.signature_material();
let signature = sp_core::sr25519::Signature::from(*signature);
let public = sp_core::sr25519::Public::from(*signer);
if signature.verify(to_sign.as_slice(), &public) {
SignatureVerificationResult::Valid(*signer)
} else {
SignatureVerificationResult::Invalid
}
},
Some(Proof::Ed25519 { signature, signer }) => {
let to_sign = self.signature_material();
let signature = sp_core::ed25519::Signature::from(*signature);
let public = sp_core::ed25519::Public::from(*signer);
if signature.verify(to_sign.as_slice(), &public) {
SignatureVerificationResult::Valid(*signer)
} else {
SignatureVerificationResult::Invalid
}
},
Some(Proof::Secp256k1Ecdsa { signature, signer }) => {
let to_sign = self.signature_material();
let signature = sp_core::ecdsa::Signature::from(*signature);
let public = sp_core::ecdsa::Public::from(*signer);
if signature.verify(to_sign.as_slice(), &public) {
let sender_hash =
<sp_runtime::traits::BlakeTwo256 as sp_core::Hasher>::hash(signer);
SignatureVerificationResult::Valid(sender_hash.into())
} else {
SignatureVerificationResult::Invalid
}
},
}
}
#[cfg(feature = "std")]
pub fn hash(&self) -> [u8; 32] {
self.using_encoded(hash_encoded)
}
pub fn topic(&self, index: usize) -> Option<Topic> {
if index < self.num_topics as usize {
Some(self.topics[index])
} else {
None
}
}
#[allow(deprecated)]
pub fn decryption_key(&self) -> Option<DecryptionKey> {
self.decryption_key
}
pub fn into_data(self) -> Option<Vec<u8>> {
self.data
}
pub fn proof(&self) -> Option<&Proof> {
self.proof.as_ref()
}
pub fn account_id(&self) -> Option<AccountId> {
self.proof.as_ref().map(Proof::account_id)
}
pub fn data(&self) -> Option<&Vec<u8>> {
self.data.as_ref()
}
pub fn data_len(&self) -> usize {
self.data().map_or(0, Vec::len)
}
pub fn channel(&self) -> Option<Channel> {
self.channel
}
pub fn expiry(&self) -> u64 {
self.expiry
}
pub fn get_expiration_timestamp_secs(&self) -> u32 {
(self.expiry >> 32) as u32
}
fn signature_material(&self) -> Vec<u8> {
self.encoded(true)
}
pub fn remove_proof(&mut self) {
self.proof = None;
}
pub fn set_proof(&mut self, proof: Proof) {
self.proof = Some(proof)
}
pub fn set_expiry(&mut self, expiry: u64) {
self.expiry = expiry;
}
pub fn set_expiry_from_parts(&mut self, expiration_timestamp_secs: u32, sequence_number: u32) {
self.expiry = (expiration_timestamp_secs as u64) << 32 | sequence_number as u64;
}
pub fn set_channel(&mut self, channel: Channel) {
self.channel = Some(channel)
}
pub fn set_topic(&mut self, index: usize, topic: Topic) {
if index < MAX_TOPICS {
self.topics[index] = topic;
self.num_topics = self.num_topics.max(index as u8 + 1);
}
}
#[allow(deprecated)]
pub fn set_decryption_key(&mut self, key: DecryptionKey) {
self.decryption_key = Some(key);
}
pub fn set_plain_data(&mut self, data: Vec<u8>) {
self.data = Some(data)
}
#[allow(deprecated)]
fn estimated_encoded_size(&self, for_signing: bool) -> usize {
let proof_size =
if !for_signing && self.proof.is_some() { 1 + Proof::max_encoded_len() } else { 0 };
let decryption_key_size =
if self.decryption_key.is_some() { 1 + DecryptionKey::max_encoded_len() } else { 0 };
let expiry_size = 1 + u64::max_encoded_len();
let channel_size = if self.channel.is_some() { 1 + Channel::max_encoded_len() } else { 0 };
let topics_size = self.num_topics as usize * (1 + Topic::max_encoded_len());
let data_size = self
.data
.as_ref()
.map_or(0, |d| 1 + Compact::<u32>::max_encoded_len() + d.len());
let compact_prefix_size = if !for_signing { Compact::<u32>::max_encoded_len() } else { 0 };
compact_prefix_size +
proof_size +
decryption_key_size +
expiry_size +
channel_size +
topics_size +
data_size
}
#[allow(deprecated)]
fn encoded(&self, for_signing: bool) -> Vec<u8> {
let num_fields = if !for_signing && self.proof.is_some() { 2 } else { 1 } +
if self.decryption_key.is_some() { 1 } else { 0 } +
if self.channel.is_some() { 1 } else { 0 } +
if self.data.is_some() { 1 } else { 0 } +
self.num_topics as u32;
let mut output = Vec::with_capacity(self.estimated_encoded_size(for_signing));
if !for_signing {
let compact_len = codec::Compact::<u32>(num_fields);
compact_len.encode_to(&mut output);
if let Some(proof) = &self.proof {
0u8.encode_to(&mut output);
proof.encode_to(&mut output);
}
}
if let Some(decryption_key) = &self.decryption_key {
1u8.encode_to(&mut output);
decryption_key.encode_to(&mut output);
}
2u8.encode_to(&mut output);
self.expiry().encode_to(&mut output);
if let Some(channel) = &self.channel {
3u8.encode_to(&mut output);
channel.encode_to(&mut output);
}
for t in 0..self.num_topics {
(4u8 + t).encode_to(&mut output);
self.topics[t as usize].encode_to(&mut output);
}
if let Some(data) = &self.data {
8u8.encode_to(&mut output);
data.encode_to(&mut output);
}
output
}
#[allow(deprecated)]
#[cfg(feature = "std")]
pub fn encrypt(
&mut self,
data: &[u8],
key: &sp_core::ed25519::Public,
) -> core::result::Result<(), ecies::Error> {
let encrypted = ecies::encrypt_ed25519(key, data)?;
self.data = Some(encrypted);
self.decryption_key = Some((*key).into());
Ok(())
}
#[cfg(feature = "std")]
pub fn decrypt_private(
&self,
key: &sp_core::ed25519::Pair,
) -> core::result::Result<Option<Vec<u8>>, ecies::Error> {
self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose()
}
}
#[cfg(test)]
mod test {
use crate::{
hash_encoded, Field, Proof, SignatureVerificationResult, Statement, Topic, MAX_TOPICS,
};
use codec::{Decode, Encode};
use scale_info::{MetaType, TypeInfo};
use sp_application_crypto::Pair;
use sp_core::sr25519;
#[test]
fn statement_encoding_matches_vec() {
let mut statement = Statement::new();
assert!(statement.proof().is_none());
let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 };
let decryption_key = [0xde; 32];
let topic1: Topic = [0x01; 32].into();
let topic2: Topic = [0x02; 32].into();
let data = vec![55, 99];
let expiry = 999;
let channel = [0xcc; 32];
statement.set_proof(proof.clone());
statement.set_decryption_key(decryption_key);
statement.set_expiry(expiry);
statement.set_channel(channel);
statement.set_topic(0, topic1);
statement.set_topic(1, topic2);
statement.set_plain_data(data.clone());
statement.set_topic(5, [0x55; 32].into());
assert_eq!(statement.topic(5), None);
let fields = vec![
Field::AuthenticityProof(proof.clone()),
Field::DecryptionKey(decryption_key),
Field::Expiry(expiry),
Field::Channel(channel),
Field::Topic1(topic1),
Field::Topic2(topic2),
Field::Data(data.clone()),
];
let encoded = statement.encode();
assert_eq!(statement.hash(), hash_encoded(&encoded));
assert_eq!(encoded, fields.encode());
let decoded = Statement::decode(&mut encoded.as_slice()).unwrap();
assert_eq!(decoded, statement);
}
#[test]
fn decode_checks_fields() {
let topic1: Topic = [0x01; 32].into();
let topic2: Topic = [0x02; 32].into();
let priority = 999;
let fields = vec![
Field::Expiry(priority),
Field::Topic1(topic1),
Field::Topic1(topic1),
Field::Topic2(topic2),
]
.encode();
assert!(Statement::decode(&mut fields.as_slice()).is_err());
let fields =
vec![Field::Topic1(topic1), Field::Expiry(priority), Field::Topic2(topic2)].encode();
assert!(Statement::decode(&mut fields.as_slice()).is_err());
}
#[test]
fn sign_and_verify() {
let mut statement = Statement::new();
statement.set_plain_data(vec![42]);
let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
let ed25519_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
let secp256k1_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
statement.sign_sr25519_private(&sr25519_kp);
assert_eq!(
statement.verify_signature(),
SignatureVerificationResult::Valid(sr25519_kp.public().0)
);
statement.sign_ed25519_private(&ed25519_kp);
assert_eq!(
statement.verify_signature(),
SignatureVerificationResult::Valid(ed25519_kp.public().0)
);
statement.sign_ecdsa_private(&secp256k1_kp);
assert_eq!(
statement.verify_signature(),
SignatureVerificationResult::Valid(sp_crypto_hashing::blake2_256(
&secp256k1_kp.public().0
))
);
statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] });
assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid);
statement.remove_proof();
assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature);
}
#[test]
fn encrypt_decrypt() {
let mut statement = Statement::new();
let (pair, _) = sp_core::ed25519::Pair::generate();
let plain = b"test data".to_vec();
statement.encrypt(&plain, &pair.public()).unwrap();
assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice());
let decrypted = statement.decrypt_private(&pair).unwrap();
assert_eq!(decrypted, Some(plain));
}
#[test]
fn check_matches() {
let mut statement = Statement::new();
let topic1: Topic = [0x01; 32].into();
let topic2: Topic = [0x02; 32].into();
let topic3: Topic = [0x03; 32].into();
statement.set_topic(0, topic1);
statement.set_topic(1, topic2);
let filter_any = crate::OptimizedTopicFilter::Any;
assert!(filter_any.matches(&statement));
let filter_all =
crate::OptimizedTopicFilter::MatchAll([topic1, topic2].iter().cloned().collect());
assert!(filter_all.matches(&statement));
let filter_all_fail =
crate::OptimizedTopicFilter::MatchAll([topic1, topic3].iter().cloned().collect());
assert!(!filter_all_fail.matches(&statement));
let filter_any_match =
crate::OptimizedTopicFilter::MatchAny([topic2, topic3].iter().cloned().collect());
assert!(filter_any_match.matches(&statement));
let filter_any_fail =
crate::OptimizedTopicFilter::MatchAny([topic3].iter().cloned().collect());
assert!(!filter_any_fail.matches(&statement));
}
#[test]
fn statement_type_info_matches_encoding() {
let statement_type = Statement::type_info();
let vec_field_meta = MetaType::new::<Vec<Field>>();
match statement_type.type_def {
scale_info::TypeDef::Composite(composite) => {
assert_eq!(composite.fields.len(), 1, "Statement should have exactly one field");
let field = &composite.fields[0];
assert!(field.name.is_none(), "Field should be unnamed (newtype pattern)");
assert_eq!(field.ty, vec_field_meta, "Statement's inner type should be Vec<Field>");
},
_ => panic!("Statement TypeInfo should be a Composite"),
}
}
#[test]
fn measure_hash_30_000_statements() {
use std::time::Instant;
const NUM_STATEMENTS: usize = 30_000;
let (keyring, _) = sr25519::Pair::generate();
let statements: Vec<Statement> = (0..NUM_STATEMENTS)
.map(|i| {
let mut statement = Statement::new();
statement.set_expiry(i as u64);
statement.set_topic(0, [(i % 256) as u8; 32].into());
statement.set_plain_data(vec![i as u8; 512]);
statement.sign_sr25519_private(&keyring);
statement.sign_sr25519_private(&keyring);
statement
})
.collect();
let start = Instant::now();
let hashes: Vec<[u8; 32]> = statements.iter().map(|s| s.hash()).collect();
let elapsed = start.elapsed();
println!("Time to hash {} statements: {:?}", NUM_STATEMENTS, elapsed);
println!("Average time per statement: {:?}", elapsed / NUM_STATEMENTS as u32);
let unique_hashes: std::collections::HashSet<_> = hashes.iter().collect();
assert_eq!(unique_hashes.len(), NUM_STATEMENTS);
}
#[test]
fn estimated_encoded_size_is_sufficient() {
const MAX_ACCEPTED_OVERHEAD: usize = 33;
let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 };
let decryption_key = [0xde; 32];
let data = vec![55; 1000];
let expiry = 999;
let channel = [0xcc; 32];
let mut statement = Statement::new();
statement.set_proof(proof);
statement.set_decryption_key(decryption_key);
statement.set_expiry(expiry);
statement.set_channel(channel);
for i in 0..MAX_TOPICS {
statement.set_topic(i, [i as u8; 32].into());
}
statement.set_plain_data(data);
let encoded = statement.encode();
let estimated = statement.estimated_encoded_size(false);
assert!(
estimated >= encoded.len(),
"estimated_encoded_size ({}) should be >= actual encoded length ({})",
estimated,
encoded.len()
);
let overhead = estimated - encoded.len();
assert!(
overhead <= MAX_ACCEPTED_OVERHEAD,
"estimated overhead ({}) should be small, estimated: {}, actual: {}",
overhead,
estimated,
encoded.len()
);
let signing_payload = statement.encoded(true);
let signing_estimated = statement.estimated_encoded_size(true);
assert!(
signing_estimated >= signing_payload.len(),
"estimated_encoded_size for signing ({}) should be >= actual signing payload length ({})",
signing_estimated,
signing_payload.len()
);
let signing_overhead = signing_estimated - signing_payload.len();
assert!(
signing_overhead <= MAX_ACCEPTED_OVERHEAD,
"signing overhead ({}) should be small, estimated: {}, actual: {}",
signing_overhead,
signing_estimated,
signing_payload.len()
);
let empty_statement = Statement::new();
let empty_encoded = empty_statement.encode();
let empty_estimated = empty_statement.estimated_encoded_size(false);
assert!(
empty_estimated >= empty_encoded.len(),
"estimated_encoded_size for empty ({}) should be >= actual encoded length ({})",
empty_estimated,
empty_encoded.len()
);
let empty_overhead = empty_estimated - empty_encoded.len();
assert!(
empty_overhead <= MAX_ACCEPTED_OVERHEAD,
"empty overhead ({}) should be minimal, estimated: {}, actual: {}",
empty_overhead,
empty_estimated,
empty_encoded.len()
);
}
}