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,76 @@
[package]
name = "pezsp-statement-store"
version = "10.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "A crate which contains primitives related to the statement store"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
pezsp-api = { workspace = true }
pezsp-application-crypto = { workspace = true }
pezsp-core = { workspace = true }
pezsp-crypto-hashing = { workspace = true }
pezsp-externalities = { workspace = true }
pezsp-runtime = { workspace = true }
pezsp-runtime-interface = { workspace = true }
thiserror = { optional = true, workspace = true }
# ECIES dependencies
aes-gcm = { optional = true, workspace = true }
curve25519-dalek = { optional = true, workspace = true }
ed25519-dalek = { optional = true, workspace = true, default-features = true }
hkdf = { optional = true, workspace = true }
rand = { features = [
"small_rng",
], optional = true, workspace = true, default-features = true }
sha2 = { optional = true, workspace = true, default-features = true }
x25519-dalek = { optional = true, features = [
"static_secrets",
], workspace = true }
[features]
default = ["std"]
std = [
"aes-gcm",
"aes-gcm?/std",
"codec/std",
"curve25519-dalek",
"ed25519-dalek",
"hkdf",
"hkdf?/std",
"rand",
"scale-info/std",
"sha2",
"pezsp-api/std",
"pezsp-application-crypto/std",
"pezsp-core/std",
"pezsp-crypto-hashing/std",
"pezsp-externalities/std",
"pezsp-runtime-interface/std",
"pezsp-runtime/std",
"thiserror",
"x25519-dalek",
]
serde = [
"scale-info/serde",
"pezsp-application-crypto/serde",
"pezsp-core/serde",
"pezsp-runtime/serde",
]
runtime-benchmarks = [
"pezsp-api/runtime-benchmarks",
"pezsp-runtime-interface/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
@@ -0,0 +1,56 @@
# Statement store
Statement store is an off-chain data-store for signed statements accessible via RPC and OCW.
Nodes hold a number of statements with a proof of authenticity owing to an account ID. OCWs can place items in the
data-store (with valid signatures) for any accounts whose keys they control. Users can also submit pre-signed statements
via RPC. Statements can also be submitted from on-chain logic through an on-chain event.
A new system event `NewStatement` is added to the runtime. This event allows any account on-chain to declare that they
want to make a statement for the store. Within the node store and for broadcasting, the statement would be accompanied
with the hash of the block and index of the event within it, essentially taking the place of a real signature.
Statements comprise an optional proof of authenticity (e.g. a signature) and a number of fields. For statements without
a proof, nodes would gossip statements randomly with a rate-limiter to minimise the chance of being overrun by a
misbehaving node. These will generally be disregarded by nodes unless they are gossiped by several different peers or if
a peer pays for it somehow (e.g. gossiping something valuable).
Each field is effectively a key/value pair. Fields must be sorted and the same field type may not be repeated. Depending
on which keys are present, clients may index the message for ease of retrieval.
Formally, `Statement` is equivalent to the type `Vec<Field>` and `Field` is the SCALE-encoded enumeration:
- 0: `AuthenticityProof(Proof)`: The signature of the message. For cryptography where the public key cannot be derived
from the signature together with the message data, then this will also include the signer's public key. The message
data is all fields of the messages fields except the signature concatenated together *without the length prefix that a
`Vec` would usually imply*. This is so that the signature can be derived without needing to re-encode the statement.
- 1: `DecryptionKey([u8; 32])`: The decryption key identifier which should be used to decrypt the statement's data. In
the absence of this field `Data` should be treated as not encrypted.
- 2: `Priority(u32)`: Priority specifier. Higher priority statements should be kept around at the cost of lower priority
statements if multiple statements from the same sender are competing for persistence or transport. Nodes should
disregard when considering unsigned statements.
- 3: `Channel([u8; 32])`: The channel identifier. Only one message of a given channel should be retained at once (the
one of highest priority). Nodes should disregard when considering unsigned statements.
- 4: `Topic1([u8; 32]))`: First topic identifier.
- 5: `Topic2([u8; 32]))`: Second topic identifier.
- 6: `Topic3([u8; 32]))`: Third topic identifier.
- 7: `Topic4([u8; 32]))`: Fourth topic identifier.
- 8: `Data(Vec<u8>)`: General data segment. No special meaning.
`Proof` is defined as the SCALE-encoded enumeration:
- 0: `Sr25519 { signature: [u8; 64], signer: [u8; 32] }`
- 1: `Ed25519 { signature: [u8; 64], signer: [u8; 32] )`
- 2: `Secp256k1Ecdsa { signature: [u8; 65], signer: [u8; 33] )`
- 3: `OnChain { who: [u8; 32], block_hash: [u8; 32], event_index: u64 }`
## Potential uses
Potential use-cases are various and include:
- ring-signature aggregation;
- messaging;
- state-channels;
- deferral of the initial "advertising" phase of multi-party transactions;
- publication of preimage data whose hash is referenced on-chain;
- effective transferal of fee payment to a second-party.
License: Apache-2.0
@@ -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<()>;
}