feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,174 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// tag::description[]
//! ECIES encryption scheme using x25519 key exchange and AEAD.
// end::description[]
use aes_gcm::{aead::Aead, AeadCore, KeyInit};
use rand::rngs::OsRng;
use sha2::Digest;
use pezsp_core::crypto::Pair;
/// x25519 secret key.
pub type SecretKey = x25519_dalek::StaticSecret;
/// x25519 public key.
pub type PublicKey = x25519_dalek::PublicKey;
/// Encryption or decryption error.
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum Error {
/// Generic AES encryption error.
#[error("Encryption error")]
Encryption,
/// Generic AES decryption error.
#[error("Decryption error")]
Decryption,
/// Error reading key data. Not enough data in the buffer.
#[error("Bad cypher text")]
BadData,
}
const NONCE_LEN: usize = 12;
const PK_LEN: usize = 32;
const AES_KEY_LEN: usize = 32;
fn aes_encrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let enc = aes_gcm::Aes256Gcm::new(key.into());
enc.encrypt(nonce.into(), aes_gcm::aead::Payload { msg: plaintext, aad: b"" })
.map_err(|_| Error::Encryption)
}
fn aes_decrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
let dec = aes_gcm::Aes256Gcm::new(key.into());
dec.decrypt(nonce.into(), aes_gcm::aead::Payload { msg: ciphertext, aad: b"" })
.map_err(|_| Error::Decryption)
}
fn kdf(shared_secret: &[u8]) -> [u8; AES_KEY_LEN] {
let hkdf = hkdf::Hkdf::<sha2::Sha256>::new(None, shared_secret);
let mut aes_key = [0u8; AES_KEY_LEN];
hkdf.expand(b"", &mut aes_key)
.expect("There's always enough data for derivation. qed.");
aes_key
}
/// Encrypt `plaintext` with the given public x25519 public key. Decryption can be performed with
/// the matching secret key.
pub fn encrypt_x25519(pk: &PublicKey, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let ephemeral_sk = x25519_dalek::StaticSecret::random_from_rng(OsRng);
let ephemeral_pk = x25519_dalek::PublicKey::from(&ephemeral_sk);
let mut shared_secret = ephemeral_sk.diffie_hellman(pk).to_bytes().to_vec();
shared_secret.extend_from_slice(ephemeral_pk.as_bytes());
let aes_key = kdf(&shared_secret);
let nonce = aes_gcm::Aes256Gcm::generate_nonce(OsRng);
let ciphertext = aes_encrypt(&aes_key, &nonce, plaintext)?;
let mut out = Vec::with_capacity(ciphertext.len() + PK_LEN + NONCE_LEN);
out.extend_from_slice(ephemeral_pk.as_bytes());
out.extend_from_slice(nonce.as_slice());
out.extend_from_slice(ciphertext.as_slice());
Ok(out)
}
/// Encrypt `plaintext` with the given ed25519 public key. Decryption can be performed with the
/// matching secret key.
pub fn encrypt_ed25519(pk: &pezsp_core::ed25519::Public, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let ed25519 = curve25519_dalek::edwards::CompressedEdwardsY(pk.0);
let x25519 = ed25519.decompress().ok_or(Error::BadData)?.to_montgomery();
let montgomery = x25519_dalek::PublicKey::from(x25519.to_bytes());
encrypt_x25519(&montgomery, plaintext)
}
/// Decrypt with the given x25519 secret key.
pub fn decrypt_x25519(sk: &SecretKey, encrypted: &[u8]) -> Result<Vec<u8>, Error> {
if encrypted.len() < PK_LEN + NONCE_LEN {
return Err(Error::BadData);
}
let mut ephemeral_pk: [u8; PK_LEN] = Default::default();
ephemeral_pk.copy_from_slice(&encrypted[0..PK_LEN]);
let ephemeral_pk = PublicKey::from(ephemeral_pk);
let mut shared_secret = sk.diffie_hellman(&ephemeral_pk).to_bytes().to_vec();
shared_secret.extend_from_slice(ephemeral_pk.as_bytes());
let aes_key = kdf(&shared_secret);
let nonce = &encrypted[PK_LEN..PK_LEN + NONCE_LEN];
aes_decrypt(&aes_key, &nonce, &encrypted[PK_LEN + NONCE_LEN..])
}
/// Decrypt with the given ed25519 key pair.
pub fn decrypt_ed25519(pair: &pezsp_core::ed25519::Pair, encrypted: &[u8]) -> Result<Vec<u8>, Error> {
let raw = pair.to_raw_vec();
let hash: [u8; 32] = sha2::Sha512::digest(&raw).as_slice()[..32]
.try_into()
.map_err(|_| Error::Decryption)?;
let secret = x25519_dalek::StaticSecret::from(hash);
decrypt_x25519(&secret, encrypted)
}
#[cfg(test)]
mod test {
use super::*;
use rand::rngs::OsRng;
use pezsp_core::crypto::Pair;
#[test]
fn basic_x25519_encryption() {
let sk = SecretKey::random_from_rng(OsRng);
let pk = PublicKey::from(&sk);
let plain_message = b"An important secret message";
let encrypted = encrypt_x25519(&pk, plain_message).unwrap();
let decrypted = decrypt_x25519(&sk, &encrypted).unwrap();
assert_eq!(plain_message, decrypted.as_slice());
}
#[test]
fn basic_ed25519_encryption() {
let (pair, _) = pezsp_core::ed25519::Pair::generate();
let pk = pair.public();
let plain_message = b"An important secret message";
let encrypted = encrypt_ed25519(&pk, plain_message).unwrap();
let decrypted = decrypt_ed25519(&pair, &encrypted).unwrap();
assert_eq!(plain_message, decrypted.as_slice());
}
#[test]
fn fails_on_bad_data() {
let sk = SecretKey::random_from_rng(OsRng);
let pk = PublicKey::from(&sk);
let plain_message = b"An important secret message";
let encrypted = encrypt_x25519(&pk, plain_message).unwrap();
assert_eq!(decrypt_x25519(&sk, &[]), Err(Error::BadData));
assert_eq!(
decrypt_x25519(&sk, &encrypted[0..super::PK_LEN + super::NONCE_LEN - 1]),
Err(Error::BadData)
);
}
}
@@ -0,0 +1,664 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
//! A crate which contains statement-store primitives.
extern crate alloc;
use alloc::vec::Vec;
use codec::{Decode, DecodeWithMemTracking, Encode};
use scale_info::TypeInfo;
use pezsp_application_crypto::RuntimeAppPublic;
#[cfg(feature = "std")]
use pezsp_core::Pair;
/// Statement topic.
pub type Topic = [u8; 32];
/// Decryption key identifier.
pub type DecryptionKey = [u8; 32];
/// Statement hash.
pub type Hash = [u8; 32];
/// Block hash.
pub type BlockHash = [u8; 32];
/// Account id
pub type AccountId = [u8; 32];
/// Statement channel.
pub type Channel = [u8; 32];
/// Total number of topic fields allowed.
pub const MAX_TOPICS: usize = 4;
#[cfg(feature = "std")]
pub use store_api::{
Error, NetworkPriority, Result, StatementSource, StatementStore, SubmitResult,
};
#[cfg(feature = "std")]
mod ecies;
pub mod runtime_api;
#[cfg(feature = "std")]
mod store_api;
mod sr25519 {
mod app_sr25519 {
use pezsp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519};
app_crypto!(sr25519, STATEMENT);
}
pub type Public = app_sr25519::Public;
}
/// Statement-store specific ed25519 crypto primitives.
pub mod ed25519 {
mod app_ed25519 {
use pezsp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT};
app_crypto!(ed25519, STATEMENT);
}
/// Statement-store specific ed25519 public key.
pub type Public = app_ed25519::Public;
/// Statement-store specific ed25519 key pair.
#[cfg(feature = "std")]
pub type Pair = app_ed25519::Pair;
}
mod ecdsa {
mod app_ecdsa {
use pezsp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT};
app_crypto!(ecdsa, STATEMENT);
}
pub type Public = app_ecdsa::Public;
}
/// Returns blake2-256 hash for the encoded statement.
#[cfg(feature = "std")]
pub fn hash_encoded(data: &[u8]) -> [u8; 32] {
pezsp_crypto_hashing::blake2_256(data)
}
/// Statement proof.
#[derive(
Encode, Decode, DecodeWithMemTracking, TypeInfo, pezsp_core::RuntimeDebug, Clone, PartialEq, Eq,
)]
pub enum Proof {
/// Sr25519 Signature.
Sr25519 {
/// Signature.
signature: [u8; 64],
/// Public key.
signer: [u8; 32],
},
/// Ed25519 Signature.
Ed25519 {
/// Signature.
signature: [u8; 64],
/// Public key.
signer: [u8; 32],
},
/// Secp256k1 Signature.
Secp256k1Ecdsa {
/// Signature.
signature: [u8; 65],
/// Public key.
signer: [u8; 33],
},
/// On-chain event proof.
OnChain {
/// Account identifier associated with the event.
who: AccountId,
/// Hash of block that contains the event.
block_hash: BlockHash,
/// Index of the event in the event list.
event_index: u64,
},
}
impl Proof {
/// Return account id for the proof creator.
pub fn account_id(&self) -> AccountId {
match self {
Proof::Sr25519 { signer, .. } => *signer,
Proof::Ed25519 { signer, .. } => *signer,
Proof::Secp256k1Ecdsa { signer, .. } =>
<pezsp_runtime::traits::BlakeTwo256 as pezsp_core::Hasher>::hash(signer).into(),
Proof::OnChain { who, .. } => *who,
}
}
}
/// Statement attributes. Each statement is a list of 0 or more fields. Fields may only appear once
/// and in the order declared here.
#[derive(Encode, Decode, TypeInfo, pezsp_core::RuntimeDebug, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum Field {
/// Statement proof.
AuthenticityProof(Proof) = 0,
/// An identifier for the key that `Data` field may be decrypted with.
DecryptionKey(DecryptionKey) = 1,
/// Priority when competing with other messages from the same sender.
Priority(u32) = 2,
/// Account channel to use. Only one message per `(account, channel)` pair is allowed.
Channel(Channel) = 3,
/// First statement topic.
Topic1(Topic) = 4,
/// Second statement topic.
Topic2(Topic) = 5,
/// Third statement topic.
Topic3(Topic) = 6,
/// Fourth statement topic.
Topic4(Topic) = 7,
/// Additional data.
Data(Vec<u8>) = 8,
}
impl Field {
fn discriminant(&self) -> u8 {
// This is safe for repr(u8)
// see https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
unsafe { *(self as *const Self as *const u8) }
}
}
/// Statement structure.
#[derive(DecodeWithMemTracking, TypeInfo, pezsp_core::RuntimeDebug, Clone, PartialEq, Eq, Default)]
pub struct Statement {
proof: Option<Proof>,
decryption_key: Option<DecryptionKey>,
channel: Option<Channel>,
priority: Option<u32>,
num_topics: u8,
topics: [Topic; MAX_TOPICS],
data: Option<Vec<u8>>,
}
impl Decode for Statement {
fn decode<I: codec::Input>(input: &mut I) -> core::result::Result<Self, codec::Error> {
// Encoding matches that of Vec<Field>. Basically this just means accepting that there
// will be a prefix of vector length.
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::Priority(p) => statement.set_priority(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)]
/// Result returned by `Statement::verify_signature`
pub enum SignatureVerificationResult {
/// Signature is valid and matches this account id.
Valid(AccountId),
/// Signature has failed verification.
Invalid,
/// No signature in the proof or no proof.
NoSignature,
}
impl Statement {
/// Create a new empty statement with no proof.
pub fn new() -> Statement {
Default::default()
}
/// Create a new statement with a proof.
pub fn new_with_proof(proof: Proof) -> Statement {
let mut statement = Self::new();
statement.set_proof(proof);
statement
}
/// Sign with a key that matches given public key in the keystore.
///
/// Returns `true` if signing worked (private key present etc).
///
/// NOTE: This can only be called from the runtime.
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
}
}
/// Sign with a given private key and add the signature proof field.
#[cfg(feature = "std")]
pub fn sign_sr25519_private(&mut self, key: &pezsp_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);
}
/// Sign with a key that matches given public key in the keystore.
///
/// Returns `true` if signing worked (private key present etc).
///
/// NOTE: This can only be called from the runtime.
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
}
}
/// Sign with a given private key and add the signature proof field.
#[cfg(feature = "std")]
pub fn sign_ed25519_private(&mut self, key: &pezsp_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);
}
/// Sign with a key that matches given public key in the keystore.
///
/// Returns `true` if signing worked (private key present etc).
///
/// NOTE: This can only be called from the runtime.
///
/// Returns `true` if signing worked (private key present etc).
///
/// NOTE: This can only be called from the runtime.
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
}
}
/// Sign with a given private key and add the signature proof field.
#[cfg(feature = "std")]
pub fn sign_ecdsa_private(&mut self, key: &pezsp_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);
}
/// Check proof signature, if any.
pub fn verify_signature(&self) -> SignatureVerificationResult {
use pezsp_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 = pezsp_core::sr25519::Signature::from(*signature);
let public = pezsp_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 = pezsp_core::ed25519::Signature::from(*signature);
let public = pezsp_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 = pezsp_core::ecdsa::Signature::from(*signature);
let public = pezsp_core::ecdsa::Public::from(*signer);
if signature.verify(to_sign.as_slice(), &public) {
let sender_hash =
<pezsp_runtime::traits::BlakeTwo256 as pezsp_core::Hasher>::hash(signer);
SignatureVerificationResult::Valid(sender_hash.into())
} else {
SignatureVerificationResult::Invalid
}
},
}
}
/// Calculate statement hash.
#[cfg(feature = "std")]
pub fn hash(&self) -> [u8; 32] {
self.using_encoded(hash_encoded)
}
/// Returns a topic by topic index.
pub fn topic(&self, index: usize) -> Option<Topic> {
if index < self.num_topics as usize {
Some(self.topics[index])
} else {
None
}
}
/// Returns decryption key if any.
pub fn decryption_key(&self) -> Option<DecryptionKey> {
self.decryption_key
}
/// Convert to internal data.
pub fn into_data(self) -> Option<Vec<u8>> {
self.data
}
/// Get a reference to the statement proof, if any.
pub fn proof(&self) -> Option<&Proof> {
self.proof.as_ref()
}
/// Get proof account id, if any
pub fn account_id(&self) -> Option<AccountId> {
self.proof.as_ref().map(Proof::account_id)
}
/// Get plain data.
pub fn data(&self) -> Option<&Vec<u8>> {
self.data.as_ref()
}
/// Get plain data len.
pub fn data_len(&self) -> usize {
self.data().map_or(0, Vec::len)
}
/// Get channel, if any.
pub fn channel(&self) -> Option<Channel> {
self.channel
}
/// Get priority, if any.
pub fn priority(&self) -> Option<u32> {
self.priority
}
/// Return encoded fields that can be signed to construct or verify a proof
fn signature_material(&self) -> Vec<u8> {
self.encoded(true)
}
/// Remove the proof of this statement.
pub fn remove_proof(&mut self) {
self.proof = None;
}
/// Set statement proof. Any existing proof is overwritten.
pub fn set_proof(&mut self, proof: Proof) {
self.proof = Some(proof)
}
/// Set statement priority.
pub fn set_priority(&mut self, priority: u32) {
self.priority = Some(priority)
}
/// Set statement channel.
pub fn set_channel(&mut self, channel: Channel) {
self.channel = Some(channel)
}
/// Set topic by index. Does noting if index is over `MAX_TOPICS`.
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);
}
}
/// Set decryption key.
pub fn set_decryption_key(&mut self, key: DecryptionKey) {
self.decryption_key = Some(key);
}
/// Set unencrypted statement data.
pub fn set_plain_data(&mut self, data: Vec<u8>) {
self.data = Some(data)
}
fn encoded(&self, for_signing: bool) -> Vec<u8> {
// Encoding matches that of Vec<Field>. Basically this just means accepting that there
// will be a prefix of vector length.
let num_fields = if !for_signing && self.proof.is_some() { 1 } else { 0 } +
if self.decryption_key.is_some() { 1 } else { 0 } +
if self.priority.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::new();
// When encoding signature payload, the length prefix is omitted.
// This is so that the signature for encoded statement can potentially be derived without
// needing to re-encode the statement.
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);
}
if let Some(priority) = &self.priority {
2u8.encode_to(&mut output);
priority.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
}
/// Encrypt give data with given key and store both in the statements.
#[cfg(feature = "std")]
pub fn encrypt(
&mut self,
data: &[u8],
key: &pezsp_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(())
}
/// Decrypt data (if any) with the given private key.
#[cfg(feature = "std")]
pub fn decrypt_private(
&self,
key: &pezsp_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};
use codec::{Decode, Encode};
use pezsp_application_crypto::Pair;
#[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 = [0x01; 32];
let topic2 = [0x02; 32];
let data = vec![55, 99];
let priority = 999;
let channel = [0xcc; 32];
statement.set_proof(proof.clone());
statement.set_decryption_key(decryption_key);
statement.set_priority(priority);
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]);
assert_eq!(statement.topic(5), None);
let fields = vec![
Field::AuthenticityProof(proof.clone()),
Field::DecryptionKey(decryption_key),
Field::Priority(priority),
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 = [0x01; 32];
let topic2 = [0x02; 32];
let priority = 999;
let fields = vec![
Field::Priority(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::Priority(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 = pezsp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
let ed25519_kp = pezsp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
let secp256k1_kp = pezsp_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(pezsp_crypto_hashing::blake2_256(
&secp256k1_kp.public().0
))
);
// set an invalid signature
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, _) = pezsp_core::ed25519::Pair::generate();
let plain = b"test data".to_vec();
//let sr25519_kp = pezsp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
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));
}
}
@@ -0,0 +1,234 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Runtime support for the statement store.
use crate::{Hash, Statement, Topic};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use pezsp_runtime::RuntimeDebug;
use pezsp_runtime_interface::{
pass_by::{
AllocateAndReturnByCodec, PassFatPointerAndDecode, PassFatPointerAndDecodeSlice,
PassPointerAndRead, PassPointerAndReadCopy, ReturnAs,
},
runtime_interface,
};
#[cfg(feature = "std")]
use pezsp_externalities::ExternalitiesExt;
/// Information concerning a valid statement.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct ValidStatement {
/// Max statement count for this account, as calculated by the runtime.
pub max_count: u32,
/// Max total data size for this account, as calculated by the runtime.
pub max_size: u32,
}
/// An reason for an invalid statement.
#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)]
pub enum InvalidStatement {
/// Failed proof validation.
BadProof,
/// Missing proof.
NoProof,
/// Validity could not be checked because of internal error.
InternalError,
}
/// The source of the statement.
///
/// Depending on the source we might apply different validation schemes.
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum StatementSource {
/// Statement is coming from the on-chain worker.
Chain,
/// Statement has been received from the gossip network.
Network,
/// Statement has been submitted over the local api.
Local,
}
impl StatementSource {
/// Check if the source allows the statement to be resubmitted to the store, extending its
/// expiration date.
pub fn can_be_resubmitted(&self) -> bool {
match self {
StatementSource::Chain | StatementSource::Local => true,
StatementSource::Network => false,
}
}
}
pezsp_api::decl_runtime_apis! {
/// Runtime API trait for statement validation.
pub trait ValidateStatement {
/// Validate the statement.
fn validate_statement(
source: StatementSource,
statement: Statement,
) -> Result<ValidStatement, InvalidStatement>;
}
}
#[cfg(feature = "std")]
pezsp_externalities::decl_extension! {
/// The offchain database extension that will be registered at the Bizinikiwi externalities.
pub struct StatementStoreExt(std::sync::Arc<dyn crate::StatementStore>);
}
// Host extensions for the runtime.
#[cfg(feature = "std")]
impl StatementStoreExt {
/// Create new instance of externalities extensions.
pub fn new(store: std::sync::Arc<dyn crate::StatementStore>) -> Self {
Self(store)
}
}
/// Submission result.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Encode, Decode)]
pub enum SubmitResult {
/// Accepted as new.
OkNew = 0,
/// Known statement
OkKnown = 1,
/// Statement failed validation.
Bad = 2,
/// The store is not available.
NotAvailable = 3,
/// Statement could not be inserted because of priority or size checks.
Full = 4,
}
impl TryFrom<u8> for SubmitResult {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(SubmitResult::OkNew),
1 => Ok(SubmitResult::OkKnown),
2 => Ok(SubmitResult::Bad),
3 => Ok(SubmitResult::NotAvailable),
4 => Ok(SubmitResult::Full),
_ => Err(()),
}
}
}
impl From<SubmitResult> for u8 {
fn from(value: SubmitResult) -> Self {
value as u8
}
}
/// Export functions for the WASM host.
#[cfg(feature = "std")]
pub type HostFunctions = (statement_store::HostFunctions,);
/// Host interface
#[runtime_interface]
pub trait StatementStore {
/// Submit a new new statement. The statement will be broadcast to the network.
/// This is meant to be used by the offchain worker.
fn submit_statement(
&mut self,
statement: PassFatPointerAndDecode<Statement>,
) -> ReturnAs<SubmitResult, u8> {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
match store.submit(statement, StatementSource::Chain) {
crate::SubmitResult::New(_) => SubmitResult::OkNew,
crate::SubmitResult::Known => SubmitResult::OkKnown,
crate::SubmitResult::Ignored => SubmitResult::Full,
// This should not happen for `StatementSource::Chain`. An existing statement will
// be overwritten.
crate::SubmitResult::KnownExpired => SubmitResult::Bad,
crate::SubmitResult::Bad(_) => SubmitResult::Bad,
crate::SubmitResult::InternalError(_) => SubmitResult::Bad,
}
} else {
SubmitResult::NotAvailable
}
}
/// Return all statements.
fn statements(&mut self) -> AllocateAndReturnByCodec<Vec<(Hash, Statement)>> {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
store.statements().unwrap_or_default()
} else {
Vec::default()
}
}
/// Return the data of all known statements which include all topics and have no `DecryptionKey`
/// field.
fn broadcasts(
&mut self,
match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
store.broadcasts(match_all_topics).unwrap_or_default()
} else {
Vec::default()
}
}
/// Return the data of all known statements whose decryption key is identified as `dest` (this
/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
/// private key for symmetric ciphers).
fn posted(
&mut self,
match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
dest: PassPointerAndReadCopy<[u8; 32], 32>,
) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
store.posted(match_all_topics, dest).unwrap_or_default()
} else {
Vec::default()
}
}
/// Return the decrypted data of all known statements whose decryption key is identified as
/// `dest`. The key must be available to the client.
fn posted_clear(
&mut self,
match_all_topics: PassFatPointerAndDecodeSlice<&[Topic]>,
dest: PassPointerAndReadCopy<[u8; 32], 32>,
) -> AllocateAndReturnByCodec<Vec<Vec<u8>>> {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
store.posted_clear(match_all_topics, dest).unwrap_or_default()
} else {
Vec::default()
}
}
/// Remove a statement from the store by hash.
fn remove(&mut self, hash: PassPointerAndRead<&Hash, 32>) {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
store.remove(hash).unwrap_or_default()
}
}
/// Remove all statements from the store that were posted by the given public key.
fn remove_by(&mut self, who: PassPointerAndReadCopy<[u8; 32], 32>) {
if let Some(StatementStoreExt(store)) = self.extension::<StatementStoreExt>() {
let _ = store.remove_by(who);
}
}
}
@@ -0,0 +1,121 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub use crate::runtime_api::StatementSource;
use crate::{Hash, Statement, Topic};
/// Statement store error.
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
pub enum Error {
/// Database error.
#[error("Database error: {0:?}")]
Db(String),
/// Error decoding statement structure.
#[error("Error decoding statement: {0:?}")]
Decode(String),
/// Error making runtime call.
#[error("Error calling into the runtime")]
Runtime,
}
#[derive(Debug, PartialEq, Eq)]
/// Network propagation priority.
pub enum NetworkPriority {
/// High priority. Statement should be broadcast to all peers.
High,
/// Low priority.
Low,
}
/// Statement submission outcome
#[derive(Debug, Eq, PartialEq)]
pub enum SubmitResult {
/// Accepted as new with given score
New(NetworkPriority),
/// Known statement
Known,
/// Known statement that's already expired.
KnownExpired,
/// Priority is too low or the size is too big.
Ignored,
/// Statement failed validation.
Bad(&'static str),
/// Internal store error.
InternalError(Error),
}
/// Result type for `Error`
pub type Result<T> = std::result::Result<T, Error>;
/// Statement store API.
pub trait StatementStore: Send + Sync {
/// Return all statements.
fn statements(&self) -> Result<Vec<(Hash, Statement)>>;
/// Return recent statements and clear the internal index.
///
/// This consumes and clears the recently received statements,
/// allowing new statements to be collected from this point forward.
fn take_recent_statements(&self) -> Result<Vec<(Hash, Statement)>>;
/// Get statement by hash.
fn statement(&self, hash: &Hash) -> Result<Option<Statement>>;
/// Check if statement exists in the store
///
/// Fast index check without accessing the DB.
fn has_statement(&self, hash: &Hash) -> bool;
/// Return the data of all known statements which include all topics and have no `DecryptionKey`
/// field.
fn broadcasts(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>>;
/// Return the data of all known statements whose decryption key is identified as `dest` (this
/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
/// private key for symmetric ciphers).
fn posted(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>>;
/// Return the decrypted data of all known statements whose decryption key is identified as
/// `dest`. The key must be available to the client.
fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>>;
/// Return all known statements which include all topics and have no `DecryptionKey`
/// field.
fn broadcasts_stmt(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>>;
/// Return all known statements whose decryption key is identified as `dest` (this
/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
/// private key for symmetric ciphers).
fn posted_stmt(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>>;
/// Return the statement and the decrypted data of all known statements whose decryption key is
/// identified as `dest`. The key must be available to the client.
///
/// The result is for each statement: the SCALE-encoded statement concatenated to the
/// decrypted data.
fn posted_clear_stmt(&self, match_all_topics: &[Topic], dest: [u8; 32])
-> Result<Vec<Vec<u8>>>;
/// Submit a statement.
fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult;
/// Remove a statement from the store.
fn remove(&self, hash: &Hash) -> Result<()>;
/// Remove all statements authored by `who`.
fn remove_by(&self, who: [u8; 32]) -> Result<()>;
}