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
+166
View File
@@ -0,0 +1,166 @@
[package]
name = "pezsp-core"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Shareable Bizinikiwi types."
documentation = "https://docs.rs/pezsp-core"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
bench = false
[[bench]]
name = "bench"
harness = false
[dependencies]
bip39 = { workspace = true, default-features = false, features = ["alloc"] }
bitflags = { workspace = true }
bounded-collections = { workspace = true, features = ["scale-codec"] }
bs58 = { optional = true, workspace = true }
codec = { features = ["derive", "max-encoded-len"], workspace = true }
futures = { optional = true, workspace = true }
hash-db = { workspace = true }
hash256-std-hasher = { workspace = true }
impl-serde = { optional = true, workspace = true }
itertools = { optional = true, workspace = true }
log = { workspace = true }
parking_lot = { optional = true, workspace = true, default-features = true }
paste = { workspace = true, default-features = true }
primitive-types = { features = ["codec", "scale-info"], workspace = true }
rand = { features = [
"small_rng",
], optional = true, workspace = true, default-features = true }
scale-info = { features = ["derive"], workspace = true }
secrecy = { features = ["alloc"], workspace = true }
serde = { optional = true, features = ["alloc", "derive"], workspace = true }
pezsp-debug-derive = { workspace = true }
pezsp-externalities = { optional = true, workspace = true }
pezsp-std = { workspace = true }
pezsp-storage = { workspace = true }
ss58-registry = { workspace = true }
bizinikiwi-bip39 = { workspace = true }
thiserror = { optional = true, workspace = true }
tracing = { optional = true, workspace = true, default-features = true }
zeroize = { workspace = true }
# full crypto
array-bytes = { workspace = true, default-features = true }
blake2 = { optional = true, workspace = true }
ed25519-zebra = { workspace = true }
libsecp256k1 = { features = ["static-context"], workspace = true }
merlin = { workspace = true }
schnorrkel = { features = ["preaudit_deprecated"], workspace = true }
pezsp-crypto-hashing = { workspace = true }
# k256 crate, better portability, intended to be used in bizinikiwi-runtimes (no-std)
k256 = { features = ["alloc", "ecdsa"], workspace = true }
# secp256k1 crate, better performance, intended to be used on host side (std)
secp256k1 = { features = [
"alloc",
"recovery",
], optional = true, workspace = true }
# bls crypto
sha2 = { optional = true, workspace = true }
w3f-bls = { optional = true, workspace = true }
# bandersnatch crypto
ark-vrf = { optional = true, workspace = true, features = [
"bandersnatch",
"ring",
] }
[target.'cfg(not(bizinikiwi_runtime))'.dependencies]
pezsp-externalities = { workspace = true, default-features = false }
futures = { version = "0.3.30", default-features = false, features = ["alloc"] }
dyn-clone = "1.0.17"
[dev-dependencies]
criterion = { workspace = true, default-features = true }
regex = { workspace = true }
serde_json = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"ark-vrf?/std",
"bip39/rand",
"bip39/std",
"blake2/std",
"bounded-collections/std",
"bs58/std",
"codec/std",
"ed25519-zebra/std",
"full_crypto",
"futures/std",
"futures/thread-pool",
"hash-db/std",
"hash256-std-hasher/std",
"impl-serde/std",
"itertools",
"k256/std",
"libsecp256k1/std",
"log/std",
"merlin/std",
"parking_lot",
"primitive-types/byteorder",
"primitive-types/rustc-hex",
"primitive-types/serde",
"primitive-types/std",
"rand",
"scale-info/std",
"schnorrkel/std",
"secp256k1/global-context",
"secp256k1/std",
"serde/std",
"sha2?/std",
"pezsp-crypto-hashing/std",
"pezsp-debug-derive/std",
"pezsp-externalities/std",
"pezsp-std/std",
"pezsp-storage/std",
"ss58-registry/std",
"bizinikiwi-bip39/std",
"thiserror",
"tracing",
"w3f-bls?/std",
"zeroize/alloc",
"zeroize/std",
]
# Serde support without relying on std features.
serde = [
"blake2",
"bounded-collections/serde",
"bs58/alloc",
"dep:serde",
"impl-serde",
"k256/serde",
"primitive-types/serde_no_std",
"scale-info/serde",
"pezsp-storage/serde",
]
# This feature enables all crypto primitives for `no_std` builds like microcontrollers
# or Intel SGX.
# For the regular wasm runtime builds this should not be used.
full_crypto = ["blake2"]
# This feature adds BLS crypto primitives.
# It should not be used in production since the implementation and interface may still
# be subject to significant changes.
bls-experimental = ["sha2", "w3f-bls"]
# This feature adds Bandersnatch crypto primitives.
# It should not be used in production since the implementation and interface may still
# be subject to significant changes.
bandersnatch-experimental = ["ark-vrf"]
@@ -0,0 +1,91 @@
// Copyright (C) Parity Technologies (UK) Ltd.
//
// 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.
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use pezsp_core::crypto::Pair as _;
fn bench_ed25519(c: &mut Criterion) {
let mut group = c.benchmark_group("ed25519");
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::ed25519::Pair::generate().0;
group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| {
b.iter(|| key.sign(&msg))
});
}
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::ed25519::Pair::generate().0;
let sig = key.sign(&msg);
let public = key.public();
group.bench_function(BenchmarkId::new("verifying", format!("{}", msg_size)), |b| {
b.iter(|| pezsp_core::ed25519::Pair::verify(&sig, &msg, &public))
});
}
group.finish();
}
fn bench_sr25519(c: &mut Criterion) {
let mut group = c.benchmark_group("sr25519");
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::sr25519::Pair::generate().0;
group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| {
b.iter(|| key.sign(&msg))
});
}
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::sr25519::Pair::generate().0;
let sig = key.sign(&msg);
let public = key.public();
group.bench_function(BenchmarkId::new("verifying", format!("{}", msg_size)), |b| {
b.iter(|| pezsp_core::sr25519::Pair::verify(&sig, &msg, &public))
});
}
group.finish();
}
fn bench_ecdsa(c: &mut Criterion) {
let mut group = c.benchmark_group("ecdsa");
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::ecdsa::Pair::generate().0;
group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| {
b.iter(|| key.sign(&msg))
});
}
for &msg_size in &[32, 1024, 1024 * 1024] {
let msg = (0..msg_size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
let key = pezsp_core::ecdsa::Pair::generate().0;
let sig = key.sign(&msg);
let public = key.public();
group.bench_function(BenchmarkId::new("verifying", format!("{}", msg_size)), |b| {
b.iter(|| pezsp_core::ecdsa::Pair::verify(&sig, &msg, &public))
});
}
group.finish();
}
criterion_group!(benches, bench_ed25519, bench_sr25519, bench_ecdsa,);
criterion_main!(benches);
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env -S bash -eux
export RUSTFLAGS="-Cdebug-assertions=y -Dwarnings --cfg bizinikiwi_runtime"
T=wasm32-unknown-unknown
cargo check --target=$T --release --no-default-features --features="bls-experimental"
cargo check --target=$T --release --no-default-features --features="full_crypto,bls-experimental"
cargo check --target=$T --release --no-default-features --features="bandersnatch-experimental"
cargo check --target=$T --release --no-default-features --features="full_crypto,serde,bandersnatch-experimental"
cargo check --target=$T --release --no-default-features --features="full_crypto,serde"
cargo check --target=$T --release --no-default-features --features="full_crypto"
cargo check --target=$T --release --no-default-features --features="serde"
cargo check --target=$T --release --no-default-features
@@ -0,0 +1,23 @@
[package]
name = "pezsp-core-fuzz"
version = "0.0.0"
publish = false
edition.workspace = true
[lints]
workspace = true
[package.metadata]
cargo-fuzz = true
[[bin]]
name = "fuzz_address_uri"
path = "fuzz_targets/fuzz_address_uri.rs"
test = false
doc = false
[dependencies]
libfuzzer-sys = { workspace = true }
regex = { workspace = true }
pezsp-core = { workspace = true, default-features = true }
@@ -0,0 +1,54 @@
// 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.
#![no_main]
extern crate libfuzzer_sys;
extern crate regex;
extern crate pezsp_core;
use libfuzzer_sys::fuzz_target;
use regex::Regex;
use pezsp_core::crypto::AddressUri;
use std::sync::LazyLock;
static SECRET_PHRASE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
.expect("constructed from known-good static value; qed")
});
fuzz_target!(|input: &str| {
let regex_result = SECRET_PHRASE_REGEX.captures(input);
let manual_result = AddressUri::parse(input);
assert_eq!(regex_result.is_some(), manual_result.is_ok());
if manual_result.is_err() {
let _ = format!("{}", manual_result.as_ref().err().unwrap());
}
if let (Some(regex_result), Ok(manual_result)) = (regex_result, manual_result) {
assert_eq!(regex_result.name("phrase").map(|p| p.as_str()), manual_result.phrase);
let manual_paths = manual_result
.paths
.iter()
.map(|s| "/".to_string() + s)
.collect::<Vec<_>>()
.join("");
assert_eq!(regex_result.name("path").unwrap().as_str().to_string(), manual_paths);
assert_eq!(regex_result.name("password").map(|pass| pass.as_str()), manual_result.pass);
}
});
@@ -0,0 +1,433 @@
// 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.
//! Little util for parsing an address URI. Replaces regular expressions.
#[cfg(not(feature = "std"))]
use alloc::{
string::{String, ToString},
vec::Vec,
};
/// A container for results of parsing the address uri string.
///
/// Intended to be equivalent of:
/// `Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")`
/// which also handles soft and hard derivation paths:
/// `Regex::new(r"/(/?[^/]+)")`
///
/// Example:
/// ```
/// use pezsp_core::crypto::AddressUri;
/// let manual_result = AddressUri::parse("hello world/s//h///pass");
/// assert_eq!(
/// manual_result.unwrap(),
/// AddressUri { phrase: Some("hello world"), paths: vec!["s", "/h"], pass: Some("pass") }
/// );
/// ```
#[derive(Debug, PartialEq)]
pub struct AddressUri<'a> {
/// Phrase, hexadecimal string, or ss58-compatible string.
pub phrase: Option<&'a str>,
/// Key derivation paths, ordered as in input string,
pub paths: Vec<&'a str>,
/// Password.
pub pass: Option<&'a str>,
}
/// Errors that are possible during parsing the address URI.
#[allow(missing_docs)]
#[cfg_attr(feature = "std", derive(thiserror::Error))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error {
#[cfg_attr(feature = "std", error("Invalid character in phrase:\n{0}"))]
InvalidCharacterInPhrase(InvalidCharacterInfo),
#[cfg_attr(feature = "std", error("Invalid character in password:\n{0}"))]
InvalidCharacterInPass(InvalidCharacterInfo),
#[cfg_attr(feature = "std", error("Missing character in hard path:\n{0}"))]
MissingCharacterInHardPath(InvalidCharacterInfo),
#[cfg_attr(feature = "std", error("Missing character in soft path:\n{0}"))]
MissingCharacterInSoftPath(InvalidCharacterInfo),
}
impl Error {
/// Creates an instance of `Error::InvalidCharacterInPhrase` using given parameters.
pub fn in_phrase(input: &str, pos: usize) -> Self {
Self::InvalidCharacterInPhrase(InvalidCharacterInfo::new(input, pos))
}
/// Creates an instance of `Error::InvalidCharacterInPass` using given parameters.
pub fn in_pass(input: &str, pos: usize) -> Self {
Self::InvalidCharacterInPass(InvalidCharacterInfo::new(input, pos))
}
/// Creates an instance of `Error::MissingCharacterInHardPath` using given parameters.
pub fn in_hard_path(input: &str, pos: usize) -> Self {
Self::MissingCharacterInHardPath(InvalidCharacterInfo::new(input, pos))
}
/// Creates an instance of `Error::MissingCharacterInSoftPath` using given parameters.
pub fn in_soft_path(input: &str, pos: usize) -> Self {
Self::MissingCharacterInSoftPath(InvalidCharacterInfo::new(input, pos))
}
}
/// Complementary error information.
///
/// Structure contains complementary information about parsing address URI string.
/// String contains a copy of an original URI string, 0-based integer indicates position of invalid
/// character.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct InvalidCharacterInfo(String, usize);
impl InvalidCharacterInfo {
fn new(info: &str, pos: usize) -> Self {
Self(info.to_string(), pos)
}
}
impl core::fmt::Display for InvalidCharacterInfo {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let (s, pos) = escape_string(&self.0, self.1);
write!(f, "{s}\n{i}^", i = core::iter::repeat(" ").take(pos).collect::<String>())
}
}
/// Escapes the control characters in given string, and recomputes the position if some characters
/// were actually escaped.
fn escape_string(input: &str, pos: usize) -> (String, usize) {
let mut out = String::with_capacity(2 * input.len());
let mut out_pos = 0;
input
.chars()
.enumerate()
.map(|(i, c)| {
let esc = |c| (i, Some('\\'), c, 2);
match c {
'\t' => esc('t'),
'\n' => esc('n'),
'\r' => esc('r'),
'\x07' => esc('a'),
'\x08' => esc('b'),
'\x0b' => esc('v'),
'\x0c' => esc('f'),
_ => (i, None, c, 1),
}
})
.for_each(|(i, maybe_escape, c, increment)| {
maybe_escape.map(|e| out.push(e));
out.push(c);
if i < pos {
out_pos += increment;
}
});
(out, out_pos)
}
fn extract_prefix<'a>(input: &mut &'a str, is_allowed: &dyn Fn(char) -> bool) -> Option<&'a str> {
let output = input.trim_start_matches(is_allowed);
let prefix_len = input.len() - output.len();
let prefix = if prefix_len > 0 { Some(&input[..prefix_len]) } else { None };
*input = output;
prefix
}
fn strip_prefix(input: &mut &str, prefix: &str) -> bool {
if let Some(stripped_input) = input.strip_prefix(prefix) {
*input = stripped_input;
true
} else {
false
}
}
impl<'a> AddressUri<'a> {
/// Parses the given string.
pub fn parse(mut input: &'a str) -> Result<Self, Error> {
let initial_input = input;
let initial_input_len = input.len();
let phrase = extract_prefix(&mut input, &|ch: char| {
ch.is_ascii_digit() || ch.is_ascii_alphabetic() || ch == ' '
});
let mut pass = None;
let mut paths = Vec::new();
while !input.is_empty() {
let unstripped_input = input;
if strip_prefix(&mut input, "///") {
pass = Some(extract_prefix(&mut input, &|ch: char| ch != '\n').unwrap_or(""));
} else if strip_prefix(&mut input, "//") {
let path = extract_prefix(&mut input, &|ch: char| ch != '/')
.ok_or(Error::in_hard_path(initial_input, initial_input_len - input.len()))?;
assert!(path.len() > 0);
// hard path shall contain leading '/', so take it from unstripped input.
paths.push(&unstripped_input[1..path.len() + 2]);
} else if strip_prefix(&mut input, "/") {
paths.push(
extract_prefix(&mut input, &|ch: char| ch != '/').ok_or(
Error::in_soft_path(initial_input, initial_input_len - input.len()),
)?,
);
} else {
return Err(if pass.is_some() {
Error::in_pass(initial_input, initial_input_len - input.len())
} else {
Error::in_phrase(initial_input, initial_input_len - input.len())
});
}
}
Ok(Self { phrase, paths, pass })
}
}
#[cfg(test)]
mod tests {
use super::*;
use regex::Regex;
use std::sync::LazyLock;
static SECRET_PHRASE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?P<phrase>[a-zA-Z0-9 ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
.expect("constructed from known-good static value; qed")
});
fn check_with_regex(input: &str) {
let regex_result = SECRET_PHRASE_REGEX.captures(input);
let manual_result = AddressUri::parse(input);
assert_eq!(regex_result.is_some(), manual_result.is_ok());
if let (Some(regex_result), Ok(manual_result)) = (regex_result, manual_result) {
assert_eq!(
regex_result.name("phrase").map(|phrase| phrase.as_str()),
manual_result.phrase
);
let manual_paths = manual_result
.paths
.iter()
.map(|s| "/".to_string() + s)
.collect::<Vec<_>>()
.join("");
assert_eq!(regex_result.name("path").unwrap().as_str().to_string(), manual_paths);
assert_eq!(
regex_result.name("password").map(|phrase| phrase.as_str()),
manual_result.pass
);
}
}
fn check(input: &str, result: Result<AddressUri, Error>) {
let manual_result = AddressUri::parse(input);
assert_eq!(manual_result, result);
check_with_regex(input);
}
#[test]
fn test00() {
check("///", Ok(AddressUri { phrase: None, pass: Some(""), paths: vec![] }));
}
#[test]
fn test01() {
check("////////", Ok(AddressUri { phrase: None, pass: Some("/////"), paths: vec![] }))
}
#[test]
fn test02() {
check(
"sdasd///asda",
Ok(AddressUri { phrase: Some("sdasd"), pass: Some("asda"), paths: vec![] }),
);
}
#[test]
fn test03() {
check(
"sdasd//asda",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asda"] }),
);
}
#[test]
fn test04() {
check("sdasd//a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/a"] }));
}
#[test]
fn test05() {
let input = "sdasd//";
check(input, Err(Error::in_hard_path(input, 7)));
}
#[test]
fn test06() {
check(
"sdasd/xx//asda",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/asda"] }),
);
}
#[test]
fn test07() {
check(
"sdasd/xx//a/b//c///pass",
Ok(AddressUri {
phrase: Some("sdasd"),
pass: Some("pass"),
paths: vec!["xx", "/a", "b", "/c"],
}),
);
}
#[test]
fn test08() {
check(
"sdasd/xx//a",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["xx", "/a"] }),
);
}
#[test]
fn test09() {
let input = "sdasd/xx//";
check(input, Err(Error::in_hard_path(input, 10)));
}
#[test]
fn test10() {
check(
"sdasd/asda",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda"] }),
);
}
#[test]
fn test11() {
check(
"sdasd/asda//x",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asda", "/x"] }),
);
}
#[test]
fn test12() {
check("sdasd/a", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["a"] }));
}
#[test]
fn test13() {
let input = "sdasd/";
check(input, Err(Error::in_soft_path(input, 6)));
}
#[test]
fn test14() {
check("sdasd", Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec![] }));
}
#[test]
fn test15() {
let input = "sdasd.";
check(input, Err(Error::in_phrase(input, 5)));
}
#[test]
fn test16() {
let input = "sd.asd/asd.a";
check(input, Err(Error::in_phrase(input, 2)));
}
#[test]
fn test17() {
let input = "sd.asd//asd.a";
check(input, Err(Error::in_phrase(input, 2)));
}
#[test]
fn test18() {
check(
"sdasd/asd.a",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["asd.a"] }),
);
}
#[test]
fn test19() {
check(
"sdasd//asd.a",
Ok(AddressUri { phrase: Some("sdasd"), pass: None, paths: vec!["/asd.a"] }),
);
}
#[test]
fn test20() {
let input = "///\n";
check(input, Err(Error::in_pass(input, 3)));
}
#[test]
fn test21() {
let input = "///a\n";
check(input, Err(Error::in_pass(input, 4)));
}
#[test]
fn test22() {
let input = "sd asd///asd.a\n";
check(input, Err(Error::in_pass(input, 14)));
}
#[test]
fn test_invalid_char_info_1() {
let expected = "01234\n^";
let f = format!("{}", InvalidCharacterInfo::new("01234", 0));
assert_eq!(expected, f);
}
#[test]
fn test_invalid_char_info_2() {
let expected = "01\n ^";
let f = format!("{}", InvalidCharacterInfo::new("01", 1));
assert_eq!(expected, f);
}
#[test]
fn test_invalid_char_info_3() {
let expected = "01234\n ^";
let f = format!("{}", InvalidCharacterInfo::new("01234", 2));
assert_eq!(expected, f);
}
#[test]
fn test_invalid_char_info_4() {
let expected = "012\\n456\n ^";
let f = format!("{}", InvalidCharacterInfo::new("012\n456", 3));
assert_eq!(expected, f);
}
#[test]
fn test_invalid_char_info_5() {
let expected = "012\\n456\n ^";
let f = format!("{}", InvalidCharacterInfo::new("012\n456", 5));
assert_eq!(expected, f);
}
#[test]
fn test_invalid_char_info_6() {
let expected = "012\\f456\\t89\n ^";
let f = format!("{}", InvalidCharacterInfo::new("012\x0c456\t89", 9));
assert_eq!(expected, f);
}
}
@@ -0,0 +1,889 @@
// 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.
//! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch),
//! an elliptic curve built over BLS12-381 scalar field.
//!
//! The primitive can operate both as a regular VRF or as an anonymized Ring VRF.
#[cfg(feature = "full_crypto")]
use crate::crypto::VrfSecret;
use crate::{
crypto::{
ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair,
PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom, VrfPublic,
},
proof_of_possession::NonAggregatable,
};
use alloc::{vec, vec::Vec};
use ark_vrf::{
reexports::{
ark_ec::CurveGroup,
ark_serialize::{CanonicalDeserialize, CanonicalSerialize},
},
suites::bandersnatch::{self, BandersnatchSha512Ell2 as BandersnatchSuite, Secret},
Suite,
};
use codec::{Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen};
use scale_info::TypeInfo;
/// Identifier used to match public keys against bandersnatch-vrf keys.
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band");
/// The byte length of secret key seed.
pub const SEED_SERIALIZED_SIZE: usize = 32;
/// The byte length of serialized public key.
pub const PUBLIC_SERIALIZED_SIZE: usize = 32;
/// The byte length of serialized signature.
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
/// The byte length of serialized pre-output.
pub const PREOUT_SERIALIZED_SIZE: usize = 32;
#[doc(hidden)]
pub struct BandersnatchTag;
/// Bandersnatch public key.
pub type Public = PublicBytes<PUBLIC_SERIALIZED_SIZE, BandersnatchTag>;
impl CryptoType for Public {
type Pair = Pair;
}
/// Bandersnatch Schnorr signature.
pub type Signature = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, BandersnatchTag>;
/// Proof of Possession is the same as Signature for Bandersnatch
pub type ProofOfPossession = Signature;
impl CryptoType for Signature {
type Pair = Pair;
}
/// The raw secret seed, which can be used to reconstruct the secret [`Pair`].
type Seed = [u8; SEED_SERIALIZED_SIZE];
/// Bandersnatch secret key.
#[derive(Clone)]
pub struct Pair {
secret: Secret,
seed: Seed,
// This is only read back in the sign operaton
#[allow(dead_code)]
prefix: Seed,
}
impl Pair {
/// Get the key seed.
pub fn seed(&self) -> Seed {
self.seed
}
}
impl TraitPair for Pair {
type Seed = Seed;
type Public = Public;
type Signature = Signature;
type ProofOfPossession = Signature;
/// Make a new key pair from secret seed material.
///
/// The slice must be 32 bytes long or it will return an error.
fn from_seed_slice(seed_slice: &[u8]) -> Result<Pair, SecretStringError> {
if seed_slice.len() != SEED_SERIALIZED_SIZE {
return Err(SecretStringError::InvalidSeedLength);
}
let mut seed = [0; SEED_SERIALIZED_SIZE];
seed.copy_from_slice(seed_slice);
let h = ark_vrf::utils::hash::<<BandersnatchSuite as Suite>::Hasher>(&seed);
// Extract and cache the high half.
let mut prefix = [0; SEED_SERIALIZED_SIZE];
prefix.copy_from_slice(&h[32..64]);
let secret = Secret::from_seed(&seed);
Ok(Pair { secret, seed, prefix })
}
/// Derive a child key from a series of given (hard) junctions.
///
/// Soft junctions are not supported.
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
_seed: Option<Seed>,
) -> Result<(Pair, Option<Seed>), DeriveError> {
let derive_hard = |seed, cc| -> Seed {
("bandersnatch-vrf-HDKD", seed, cc).using_encoded(pezsp_crypto_hashing::blake2_256)
};
let mut seed = self.seed();
for p in path {
if let DeriveJunction::Hard(cc) = p {
seed = derive_hard(seed, cc);
} else {
return Err(DeriveError::SoftKeyInPath);
}
}
Ok((Self::from_seed(&seed), Some(seed)))
}
fn public(&self) -> Public {
let public = self.secret.public();
let mut raw = [0; PUBLIC_SERIALIZED_SIZE];
public
.serialize_compressed(raw.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
Public::unchecked_from(raw)
}
#[cfg(feature = "full_crypto")]
fn sign(&self, data: &[u8]) -> Signature {
// Deterministic nonce for plain Schnorr signature.
// Inspired by ed25519 <https://www.rfc-editor.org/rfc/rfc8032#section-5.1.6>
let h_in = [&self.prefix[..32], data].concat();
let h = &ark_vrf::utils::hash::<<BandersnatchSuite as Suite>::Hasher>(&h_in)[..32];
let k = ark_vrf::codec::scalar_decode::<BandersnatchSuite>(h);
let gk = BandersnatchSuite::generator() * k;
let c = BandersnatchSuite::challenge(&[&gk.into_affine(), &self.secret.public.0], data);
let s = k + c * self.secret.scalar;
let mut raw_signature = [0_u8; SIGNATURE_SERIALIZED_SIZE];
bandersnatch::IetfProof { c, s }
.serialize_compressed(&mut raw_signature.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
Signature::from_raw(raw_signature)
}
fn verify<M: AsRef<[u8]>>(signature: &Signature, data: M, public: &Public) -> bool {
let Ok(signature) = bandersnatch::IetfProof::deserialize_compressed(&signature.0[..])
else {
return false;
};
let Ok(public) = bandersnatch::Public::deserialize_compressed(&public.0[..]) else {
return false;
};
let gs = BandersnatchSuite::generator() * signature.s;
let yc = public.0 * signature.c;
let rv = gs - yc;
let cv = BandersnatchSuite::challenge(&[&rv.into_affine(), &public.0], data.as_ref());
signature.c == cv
}
/// Return a vector filled with the seed.
fn to_raw_vec(&self) -> Vec<u8> {
self.seed().to_vec()
}
}
impl CryptoType for Pair {
type Pair = Pair;
}
impl NonAggregatable for Pair {}
/// Bandersnatch VRF types and operations.
pub mod vrf {
use super::*;
use crate::crypto::VrfCrypto;
/// [`VrfSignature`] serialized size.
pub const VRF_SIGNATURE_SERIALIZED_SIZE: usize =
PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE;
/// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`].
#[derive(Clone, Debug)]
pub struct VrfInput(pub(super) bandersnatch::Input);
impl VrfInput {
/// Construct a new VRF input.
///
/// Hash to Curve (H2C) using Elligator2.
pub fn new(data: &[u8]) -> Self {
Self(bandersnatch::Input::new(data).expect("H2C for Bandersnatch can't fail; qed"))
}
}
/// VRF pre-output derived from [`VrfInput`] using a [`VrfSecret`].
///
/// This object is hashed to produce the actual VRF output.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VrfPreOutput(pub(super) bandersnatch::Output);
impl Encode for VrfPreOutput {
fn encode(&self) -> Vec<u8> {
let mut bytes = [0; PREOUT_SERIALIZED_SIZE];
self.0
.serialize_compressed(bytes.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
bytes.encode()
}
}
impl Decode for VrfPreOutput {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let buf = <[u8; PREOUT_SERIALIZED_SIZE]>::decode(i)?;
let preout = bandersnatch::Output::deserialize_compressed_unchecked(buf.as_slice())
.map_err(|_| "vrf-preout decode error: bad preout")?;
Ok(VrfPreOutput(preout))
}
}
// `VrfPreOutput` resolves to:
// ```
// pub struct Affine<P: TECurveConfig> {
// pub x: P::BaseField,
// pub y: P::BaseField,
// }
// ```
// where each `P::BaseField` contains a `pub struct BigInt<const N: usize>(pub [u64; N]);`
// Since none of these structures is allocated on the heap, we don't need any special
// memory tracking logic. We can simply implement `DecodeWithMemTracking`.
impl DecodeWithMemTracking for VrfPreOutput {}
impl EncodeLike for VrfPreOutput {}
impl MaxEncodedLen for VrfPreOutput {
fn max_encoded_len() -> usize {
<[u8; PREOUT_SERIALIZED_SIZE]>::max_encoded_len()
}
}
impl TypeInfo for VrfPreOutput {
type Identity = [u8; PREOUT_SERIALIZED_SIZE];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
/// Data to be signed via one of the two provided vrf flavors.
///
/// The object contains the VRF input and additional data to be signed together
/// with the VRF input. Additional data doesn't influence the VRF output.
///
/// The `input` is a [`VrfInput`]s which, during the signing procedure, is first mapped
/// to a [`VrfPreOutput`].
#[derive(Clone)]
pub struct VrfSignData {
/// VRF input.
pub vrf_input: VrfInput,
/// Additional data.
pub aux_data: Vec<u8>,
}
impl VrfSignData {
/// Construct a new data to be signed.
pub fn new(vrf_input_data: &[u8], aux_data: &[u8]) -> Self {
Self { vrf_input: VrfInput::new(vrf_input_data), aux_data: aux_data.to_vec() }
}
}
/// VRF signature.
///
/// Includes both the VRF proof and the pre-output generated from the
/// [`VrfSignData::vrf_input`].
///
/// Refer to [`VrfSignData`] for more details.
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct VrfSignature {
/// VRF pre-output.
pub pre_output: VrfPreOutput,
/// VRF proof.
pub proof: Signature,
}
#[cfg(feature = "full_crypto")]
impl VrfCrypto for Pair {
type VrfInput = VrfInput;
type VrfPreOutput = VrfPreOutput;
type VrfSignData = VrfSignData;
type VrfSignature = VrfSignature;
}
#[cfg(feature = "full_crypto")]
impl VrfSecret for Pair {
fn vrf_sign(&self, data: &VrfSignData) -> VrfSignature {
use ark_vrf::ietf::Prover;
let pre_output_impl = self.secret.output(data.vrf_input.0);
let pre_output = VrfPreOutput(pre_output_impl);
let proof_impl = self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data);
let mut proof = Signature::default();
proof_impl
.serialize_compressed(proof.0.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
VrfSignature { pre_output, proof }
}
fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput {
let pre_output_impl = self.secret.output(input.0);
VrfPreOutput(pre_output_impl)
}
}
impl VrfCrypto for Public {
type VrfInput = VrfInput;
type VrfPreOutput = VrfPreOutput;
type VrfSignData = VrfSignData;
type VrfSignature = VrfSignature;
}
impl VrfPublic for Public {
fn vrf_verify(&self, data: &VrfSignData, signature: &VrfSignature) -> bool {
use ark_vrf::ietf::Verifier;
let Ok(public) =
bandersnatch::Public::deserialize_compressed_unchecked(self.as_slice())
else {
return false;
};
let Ok(proof) =
ark_vrf::ietf::Proof::deserialize_compressed_unchecked(signature.proof.as_slice())
else {
return false;
};
public
.verify(data.vrf_input.0, signature.pre_output.0, &data.aux_data, &proof)
.is_ok()
}
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Generate VRF output bytes for the given `input`.
pub fn make_bytes(&self, input: &VrfInput) -> [u8; 32] {
self.vrf_pre_output(input).make_bytes()
}
}
impl VrfPreOutput {
/// Generate VRF output bytes.
pub fn make_bytes(&self) -> [u8; 32] {
let mut bytes = [0_u8; 32];
bytes.copy_from_slice(&self.0.hash()[..32]);
bytes
}
}
}
/// Bandersnatch Ring-VRF types and operations.
pub mod ring_vrf {
use super::{vrf::*, *};
use bandersnatch::{RingProofParams, RingVerifierKey as RingVerifierKeyImpl};
pub use bandersnatch::{RingProver, RingVerifier};
// Max size of serialized ring-vrf context given `domain_len`.
pub(crate) fn ring_context_serialized_size(ring_size: usize) -> usize {
const G1_POINT_UNCOMPRESSED_SIZE: usize = 96;
const G2_POINT_UNCOMPRESSED_SIZE: usize = 192;
const OVERHEAD_SIZE: usize = 16;
const G2_POINTS_NUM: usize = 2;
let g1_points_num = ark_vrf::ring::pcs_domain_size::<BandersnatchSuite>(ring_size);
OVERHEAD_SIZE +
g1_points_num * G1_POINT_UNCOMPRESSED_SIZE +
G2_POINTS_NUM * G2_POINT_UNCOMPRESSED_SIZE
}
/// [`RingVerifierKey`] serialized size.
pub const RING_VERIFIER_KEY_SERIALIZED_SIZE: usize = 384;
/// [`RingProof`] serialized size.
pub(crate) const RING_PROOF_SERIALIZED_SIZE: usize = 752;
/// [`RingVrfSignature`] serialized size.
pub const RING_SIGNATURE_SERIALIZED_SIZE: usize =
RING_PROOF_SERIALIZED_SIZE + PREOUT_SERIALIZED_SIZE;
/// Ring verifier key
pub struct RingVerifierKey(RingVerifierKeyImpl);
impl Encode for RingVerifierKey {
fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(RING_VERIFIER_KEY_SERIALIZED_SIZE);
self.0
.serialize_compressed(&mut buf)
.expect("serialization length is constant and checked by test; qed");
buf
}
}
impl Decode for RingVerifierKey {
fn decode<R: codec::Input>(input: &mut R) -> Result<Self, codec::Error> {
let mut buf = vec![0; RING_VERIFIER_KEY_SERIALIZED_SIZE];
input.read(&mut buf[..])?;
let vk = RingVerifierKeyImpl::deserialize_compressed_unchecked(buf.as_slice())
.map_err(|_| "RingVerifierKey decode error")?;
Ok(RingVerifierKey(vk))
}
}
impl EncodeLike for RingVerifierKey {}
impl MaxEncodedLen for RingVerifierKey {
fn max_encoded_len() -> usize {
RING_VERIFIER_KEY_SERIALIZED_SIZE
}
}
impl TypeInfo for RingVerifierKey {
type Identity = [u8; RING_VERIFIER_KEY_SERIALIZED_SIZE];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
/// Context used to construct ring prover and verifier.
///
/// Generic parameter `R` represents the ring size.
#[derive(Clone)]
pub struct RingContext<const R: usize>(RingProofParams);
impl<const R: usize> RingContext<R> {
/// Build an dummy instance for testing purposes.
pub fn new_testing() -> Self {
Self(RingProofParams::from_seed(R, [0; 32]))
}
/// Get the keyset max size.
pub fn max_keyset_size(&self) -> usize {
self.0.max_ring_size()
}
/// Get ring prover for the key at index `public_idx` in the `public_keys` set.
pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> RingProver {
let pks = Self::make_ring_vector(public_keys);
let prover_key = self.0.prover_key(&pks);
self.0.prover(prover_key, public_idx)
}
/// Get ring verifier for the `public_keys` set.
pub fn verifier(&self, public_keys: &[Public]) -> RingVerifier {
let vk = self.verifier_key(public_keys);
self.0.verifier(vk.0)
}
/// Build `RingVerifierKey` for lazy `RingVerifier` construction.
pub fn verifier_key(&self, public_keys: &[Public]) -> RingVerifierKey {
let pks = Self::make_ring_vector(public_keys);
RingVerifierKey(self.0.verifier_key(&pks))
}
/// Constructs a `RingVerifier` from a `VerifierKey` without a `RingContext` instance.
///
/// While this approach is computationally slightly less efficient than using a
/// pre-constructed `RingContext`, as some parameters need to be computed on-the-fly, it
/// is beneficial in memory or storage constrained environments. This avoids the need to
/// retain the full `RingContext` for ring signature verification. Instead, the
/// `VerifierKey` contains only the essential information needed to verify ring proofs.
pub fn verifier_no_context(verifier_key: RingVerifierKey) -> RingVerifier {
RingProofParams::verifier_no_context(verifier_key.0, R)
}
fn make_ring_vector(public_keys: &[Public]) -> Vec<bandersnatch::AffinePoint> {
use bandersnatch::AffinePoint;
public_keys
.iter()
.map(|pk| {
AffinePoint::deserialize_compressed_unchecked(pk.as_slice())
.unwrap_or(RingProofParams::padding_point())
})
.collect()
}
}
impl<const R: usize> Encode for RingContext<R> {
fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(ring_context_serialized_size(R));
self.0
.serialize_uncompressed(&mut buf)
.expect("serialization length is constant and checked by test; qed");
buf
}
}
impl<const R: usize> Decode for RingContext<R> {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let mut buf = vec![0; ring_context_serialized_size(R)];
input.read(&mut buf[..])?;
let ctx = RingProofParams::deserialize_uncompressed_unchecked(buf.as_slice())
.map_err(|_| "RingContext decode error")?;
Ok(RingContext(ctx))
}
}
impl<const R: usize> EncodeLike for RingContext<R> {}
impl<const R: usize> MaxEncodedLen for RingContext<R> {
fn max_encoded_len() -> usize {
ring_context_serialized_size(R)
}
}
impl<const R: usize> TypeInfo for RingContext<R> {
type Identity = Self;
fn type_info() -> scale_info::Type {
let path = scale_info::Path::new("RingContext", module_path!());
let array_type_def = scale_info::TypeDefArray {
len: ring_context_serialized_size(R) as u32,
type_param: scale_info::MetaType::new::<u8>(),
};
let type_def = scale_info::TypeDef::Array(array_type_def);
scale_info::Type { path, type_params: Vec::new(), type_def, docs: Vec::new() }
}
}
/// Ring VRF signature.
#[derive(
Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo,
)]
pub struct RingVrfSignature {
/// VRF pre-output.
pub pre_output: VrfPreOutput,
/// Ring signature.
pub proof: [u8; RING_PROOF_SERIALIZED_SIZE],
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Produce a ring-vrf signature.
///
/// The ring signature is verifiable if the public key corresponding to the
/// signing [`Pair`] is part of the ring from which the [`RingProver`] has
/// been constructed. If not, the produced signature is just useless.
pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature {
use ark_vrf::ring::Prover;
let pre_output_impl = self.secret.output(data.vrf_input.0);
let pre_output = VrfPreOutput(pre_output_impl);
let proof_impl =
self.secret.prove(data.vrf_input.0, pre_output.0, &data.aux_data, prover);
let mut proof = [0; RING_PROOF_SERIALIZED_SIZE];
proof_impl
.serialize_compressed(proof.as_mut_slice())
.expect("serialization length is constant and checked by test; qed");
RingVrfSignature { pre_output, proof }
}
}
impl RingVrfSignature {
/// Verify a ring-vrf signature.
///
/// The signature is verifiable if it has been produced by a member of the ring
/// from which the [`RingVerifier`] has been constructed.
pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool {
use ark_vrf::ring::Verifier;
let Ok(proof) =
bandersnatch::RingProof::deserialize_compressed_unchecked(self.proof.as_slice())
else {
return false;
};
bandersnatch::Public::verify(
data.vrf_input.0,
self.pre_output.0,
&data.aux_data,
&proof,
verifier,
)
.is_ok()
}
}
}
#[cfg(test)]
mod tests {
use super::{ring_vrf::*, vrf::*, *};
use crate::{
crypto::{VrfPublic, VrfSecret, DEV_PHRASE},
proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier},
};
const TEST_SEED: &[u8; SEED_SERIALIZED_SIZE] = &[0xcb; SEED_SERIALIZED_SIZE];
const TEST_RING_SIZE: usize = 16;
type TestRingContext = RingContext<TEST_RING_SIZE>;
#[allow(unused)]
fn b2h(bytes: &[u8]) -> String {
array_bytes::bytes2hex("", bytes)
}
fn h2b(hex: &str) -> Vec<u8> {
array_bytes::hex2bytes_unchecked(hex)
}
#[test]
fn backend_assumptions_sanity_check() {
use bandersnatch::{Input, RingProofParams};
let ctx = RingProofParams::from_seed(TEST_RING_SIZE, [0_u8; 32]);
let domain_size = ark_vrf::ring::pcs_domain_size::<BandersnatchSuite>(TEST_RING_SIZE);
assert_eq!(domain_size, ctx.pcs.powers_in_g1.len());
let domain_size2 = ark_vrf::ring::pcs_domain_size::<BandersnatchSuite>(ctx.max_ring_size());
assert_eq!(domain_size, domain_size2);
assert_eq!(
ark_vrf::ring::max_ring_size_from_pcs_domain_size::<BandersnatchSuite>(domain_size),
ctx.max_ring_size()
);
assert_eq!(ctx.uncompressed_size(), ring_context_serialized_size(TEST_RING_SIZE));
let prover_key_index = 3;
let secret = Secret::from_seed(&[prover_key_index as u8; 32]);
let public = secret.public();
assert_eq!(public.compressed_size(), PUBLIC_SERIALIZED_SIZE);
let input = Input::new(b"foo").unwrap();
let preout = secret.output(input);
assert_eq!(preout.compressed_size(), PREOUT_SERIALIZED_SIZE);
let ring_keys: Vec<_> = (0..TEST_RING_SIZE)
.map(|i| Secret::from_seed(&[i as u8; 32]).public().0.into())
.collect();
let verifier_key = ctx.verifier_key(&ring_keys[..]);
assert_eq!(verifier_key.compressed_size(), RING_VERIFIER_KEY_SERIALIZED_SIZE);
let prover_key = ctx.prover_key(&ring_keys);
let ring_prover = ctx.prover(prover_key, prover_key_index);
{
use ark_vrf::ietf::Prover;
let proof = secret.prove(input, preout, &[]);
assert_eq!(proof.compressed_size(), SIGNATURE_SERIALIZED_SIZE);
}
{
use ark_vrf::ring::Prover;
let proof = secret.prove(input, preout, &[], &ring_prover);
assert_eq!(proof.compressed_size(), RING_PROOF_SERIALIZED_SIZE);
}
}
#[test]
fn derive_works() {
let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap();
let known = h2b("f706ea7ee4eef553428a768dbf3a1ede0b389a9f75867ade317a61cbb4efeb01");
assert_eq!(pair.public().as_ref(), known);
// Soft derivation not supported
let res = Pair::from_string(&format!("{}//Alice/Soft", DEV_PHRASE), None);
assert!(res.is_err());
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string() {
let (pair, phrase, seed) = Pair::generate_with_phrase(None);
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
}
#[test]
fn sign_verify() {
let pair = Pair::from_seed(TEST_SEED);
let public = pair.public();
let msg = b"foo";
let signature = pair.sign(msg);
assert!(Pair::verify(&signature, msg, &public));
}
#[test]
fn vrf_sign_verify() {
let pair = Pair::from_seed(TEST_SEED);
let public = pair.public();
let data = VrfSignData::new(b"foo", b"aux");
let signature = pair.vrf_sign(&data);
assert!(public.vrf_verify(&data, &signature));
}
#[test]
fn vrf_sign_verify_with_bad_input() {
let pair = Pair::from_seed(TEST_SEED);
let public = pair.public();
let data = VrfSignData::new(b"foo", b"aux");
let signature = pair.vrf_sign(&data);
let data = VrfSignData::new(b"foo", b"bad");
assert!(!public.vrf_verify(&data, &signature));
let data = VrfSignData::new(b"bar", b"aux");
assert!(!public.vrf_verify(&data, &signature));
}
#[test]
fn vrf_output_bytes_match() {
let pair = Pair::from_seed(TEST_SEED);
let data = VrfSignData::new(b"foo", b"aux");
let signature = pair.vrf_sign(&data);
let o0 = pair.make_bytes(&data.vrf_input);
let o1 = signature.pre_output.make_bytes();
assert_eq!(o0, o1);
}
#[test]
fn vrf_signature_encode_decode() {
let pair = Pair::from_seed(TEST_SEED);
let data = VrfSignData::new(b"data", b"aux");
let expected = pair.vrf_sign(&data);
let bytes = expected.encode();
let expected_len = PREOUT_SERIALIZED_SIZE + SIGNATURE_SERIALIZED_SIZE;
assert_eq!(bytes.len(), expected_len);
let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap();
assert_eq!(expected, decoded);
}
#[test]
fn ring_vrf_sign_verify() {
let ring_ctx = TestRingContext::new_testing();
let mut pks: Vec<_> =
(0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
let pair = Pair::from_seed(TEST_SEED);
// Just pick one index to patch with the actual public key
let prover_idx = 3;
pks[prover_idx] = pair.public();
let prover = ring_ctx.prover(&pks, prover_idx);
let data = VrfSignData::new(b"data", b"aux");
let signature = pair.ring_vrf_sign(&data, &prover);
let verifier = ring_ctx.verifier(&pks);
assert!(signature.ring_vrf_verify(&data, &verifier));
}
#[test]
fn ring_vrf_sign_verify_with_out_of_ring_key() {
let ring_ctx = TestRingContext::new_testing();
let pks: Vec<_> =
(0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
let pair = Pair::from_seed(TEST_SEED);
let data = VrfSignData::new(b"foo", b"aux");
// pair.public != pks[0]
let prover = ring_ctx.prover(&pks, 0);
let signature = pair.ring_vrf_sign(&data, &prover);
let verifier = ring_ctx.verifier(&pks);
assert!(!signature.ring_vrf_verify(&data, &verifier));
}
#[test]
fn ring_vrf_make_bytes_matches() {
let ring_ctx = TestRingContext::new_testing();
let mut pks: Vec<_> =
(0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
let pair = Pair::from_seed(TEST_SEED);
// Just pick one index to patch with the actual public key
let prover_idx = 3;
pks[prover_idx] = pair.public();
let data = VrfSignData::new(b"data", b"aux");
let prover = ring_ctx.prover(&pks, prover_idx);
let signature = pair.ring_vrf_sign(&data, &prover);
let o0 = pair.make_bytes(&data.vrf_input);
let o1 = signature.pre_output.make_bytes();
assert_eq!(o0, o1);
}
#[test]
fn ring_vrf_signature_encode_decode() {
let ring_ctx = TestRingContext::new_testing();
let mut pks: Vec<_> =
(0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
let pair = Pair::from_seed(TEST_SEED);
// Just pick one index to patch with the actual public key
let prover_idx = 3;
pks[prover_idx] = pair.public();
let data = VrfSignData::new(b"foo", b"aux");
let prover = ring_ctx.prover(&pks, prover_idx);
let expected = pair.ring_vrf_sign(&data, &prover);
let bytes = expected.encode();
assert_eq!(bytes.len(), RING_SIGNATURE_SERIALIZED_SIZE);
let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap();
assert_eq!(expected, decoded);
}
#[test]
fn ring_vrf_context_encode_decode() {
let ctx1 = TestRingContext::new_testing();
let enc1 = ctx1.encode();
assert_eq!(enc1.len(), ring_context_serialized_size(TEST_RING_SIZE));
assert_eq!(enc1.len(), TestRingContext::max_encoded_len());
let ctx2 = TestRingContext::decode(&mut enc1.as_slice()).unwrap();
let enc2 = ctx2.encode();
assert_eq!(enc1, enc2);
}
#[test]
fn verifier_key_encode_decode() {
let ring_ctx = TestRingContext::new_testing();
let pks: Vec<_> =
(0..TEST_RING_SIZE).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect();
assert!(pks.len() <= ring_ctx.max_keyset_size());
let verifier_key = ring_ctx.verifier_key(&pks);
let enc1 = verifier_key.encode();
assert_eq!(enc1.len(), RING_VERIFIER_KEY_SERIALIZED_SIZE);
assert_eq!(RingVerifierKey::max_encoded_len(), RING_VERIFIER_KEY_SERIALIZED_SIZE);
let vd2 = RingVerifierKey::decode(&mut enc1.as_slice()).unwrap();
let enc2 = vd2.encode();
assert_eq!(enc1, enc2);
}
#[test]
fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert!(!Pair::verify_proof_of_possession(
owner,
&proof_of_possession,
&other_pair.public()
));
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
}
+711
View File
@@ -0,0 +1,711 @@
// 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.
//! BLS (BonehLynnShacham) Signature along with efficiently verifiable Chaum-Pedersen proof API.
//! Signatures are implemented according to
//! [Efficient Aggregatable BLS Signatures with Chaum-Pedersen Proofs](https://eprint.iacr.org/2022/1611)
//! Hash-to-BLS-curve is using Simplified SWU for AB == 0
//! [RFC 9380](https://datatracker.ietf.org/doc/rfc9380/) Sect 6.6.3.
//! Chaum-Pedersen proof uses the same hash-to-field specified in RFC 9380 for the field of the BLS
//! curve.
use crate::{
crypto::{
CryptoType, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes, SecretStringError,
SignatureBytes, UncheckedFrom,
},
proof_of_possession::{
statement_of_ownership, ProofOfPossessionGenerator, ProofOfPossessionVerifier,
},
};
use alloc::vec::Vec;
use w3f_bls::{
DoublePublicKey, DoublePublicKeyScheme, DoubleSignature, EngineBLS, Keypair, Message,
NuggetBLSnCPPoP, ProofOfPossession as BlsProofOfPossession, SecretKey, SerializableToBytes,
TinyBLS381,
};
#[cfg(feature = "full_crypto")]
use w3f_bls::ProofOfPossessionGenerator as BlsProofOfPossessionGenerator;
/// Required to generate Proof Of Possession
use sha2::Sha256;
/// BLS-377 specialized types
pub mod bls377 {
pub use super::{
PROOF_OF_POSSESSION_SERIALIZED_SIZE, PUBLIC_KEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
};
use crate::crypto::CryptoTypeId;
pub(crate) use w3f_bls::TinyBLS377 as BlsEngine;
/// An identifier used to match public keys against BLS12-377 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bls7");
#[doc(hidden)]
pub type Bls377Tag = BlsEngine;
/// BLS12-377 key pair.
pub type Pair = super::Pair<BlsEngine>;
/// BLS12-377 public key.
pub type Public = super::Public<BlsEngine>;
/// BLS12-377 signature.
pub type Signature = super::Signature<BlsEngine>;
/// BLS12-377 Proof Of Possesion.
pub type ProofOfPossession = super::ProofOfPossession<BlsEngine>;
impl super::HardJunctionId for BlsEngine {
const ID: &'static str = "BLS12377HDKD";
}
}
/// BLS-381 specialized types
pub mod bls381 {
pub use super::{
PROOF_OF_POSSESSION_SERIALIZED_SIZE, PUBLIC_KEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
};
use crate::crypto::CryptoTypeId;
pub use w3f_bls::TinyBLS381 as BlsEngine;
/// An identifier used to match public keys against BLS12-381 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bls8");
#[doc(hidden)]
pub type Bls381Tag = BlsEngine;
/// BLS12-381 key pair.
pub type Pair = super::Pair<BlsEngine>;
/// BLS12-381 public key.
pub type Public = super::Public<BlsEngine>;
/// BLS12-381 signature.
pub type Signature = super::Signature<BlsEngine>;
/// BLS12-381 Proof Of Possesion.
pub type ProofOfPossession = super::ProofOfPossession<BlsEngine>;
impl super::HardJunctionId for BlsEngine {
const ID: &'static str = "BLS12381HDKD";
}
}
trait BlsBound: EngineBLS + HardJunctionId + Send + Sync + 'static {}
impl<T: EngineBLS + HardJunctionId + Send + Sync + 'static> BlsBound for T {}
/// Secret key serialized size
const SECRET_KEY_SERIALIZED_SIZE: usize =
<SecretKey<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;
/// Public key serialized size
pub const PUBLIC_KEY_SERIALIZED_SIZE: usize =
<DoublePublicKey<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;
/// Signature serialized size
pub const SIGNATURE_SERIALIZED_SIZE: usize =
<DoubleSignature<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;
/// Signature serialized size (for back cert) + Nugget BLS PoP size
pub const PROOF_OF_POSSESSION_SERIALIZED_SIZE: usize = SIGNATURE_SERIALIZED_SIZE +
<NuggetBLSnCPPoP<TinyBLS381> as SerializableToBytes>::SERIALIZED_BYTES_SIZE;
/// A secret seed.
///
/// It's not called a "secret key" because ring doesn't expose the secret keys
/// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we
/// will need it later (such as for HDKD).
type Seed = [u8; SECRET_KEY_SERIALIZED_SIZE];
#[doc(hidden)]
pub struct BlsTag;
/// A public key.
pub type Public<SubTag> = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, (BlsTag, SubTag)>;
impl<T: BlsBound> CryptoType for Public<T> {
type Pair = Pair<T>;
}
/// A generic BLS signature.
pub type Signature<SubTag> = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, (BlsTag, SubTag)>;
impl<T: BlsBound> CryptoType for Signature<T> {
type Pair = Pair<T>;
}
/// A generic BLS ProofOfpossession
pub type ProofOfPossession<SubTag> =
SignatureBytes<PROOF_OF_POSSESSION_SERIALIZED_SIZE, (BlsTag, SubTag)>;
impl<T: BlsBound> CryptoType for ProofOfPossession<T> {
type Pair = Pair<T>;
}
/// A key pair.
pub struct Pair<T: EngineBLS>(Keypair<T>);
impl<T: EngineBLS> Clone for Pair<T> {
fn clone(&self) -> Self {
Pair(self.0.clone())
}
}
trait HardJunctionId {
const ID: &'static str;
}
/// Derive a single hard junction.
fn derive_hard_junction<T: HardJunctionId>(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
use codec::Encode;
(T::ID, secret_seed, cc).using_encoded(pezsp_crypto_hashing::blake2_256)
}
impl<T: EngineBLS> Pair<T> {}
impl<T: BlsBound> TraitPair for Pair<T> {
type Seed = Seed;
type Public = Public<T>;
type Signature = Signature<T>;
type ProofOfPossession = ProofOfPossession<T>;
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
if seed_slice.len() != SECRET_KEY_SERIALIZED_SIZE {
return Err(SecretStringError::InvalidSeedLength);
}
let secret = w3f_bls::SecretKey::from_seed(seed_slice);
let public = secret.into_public();
Ok(Pair(w3f_bls::Keypair { secret, public }))
}
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
seed: Option<Seed>,
) -> Result<(Self, Option<Seed>), DeriveError> {
let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] =
seed.unwrap_or(self.0.secret.to_bytes().try_into().expect(
"Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed",
));
for j in path {
match j {
DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath),
DeriveJunction::Hard(cc) => acc = derive_hard_junction::<T>(&acc, &cc),
}
}
Ok((Self::from_seed(&acc), Some(acc)))
}
fn public(&self) -> Self::Public {
let mut raw = [0u8; PUBLIC_KEY_SERIALIZED_SIZE];
let pk = DoublePublicKeyScheme::into_double_public_key(&self.0).to_bytes();
raw.copy_from_slice(pk.as_slice());
Self::Public::unchecked_from(raw)
}
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Self::Signature {
let mut mutable_self = self.clone();
let r: [u8; SIGNATURE_SERIALIZED_SIZE] =
DoublePublicKeyScheme::sign(&mut mutable_self.0, &Message::new(b"", message))
.to_bytes()
.try_into()
.expect("Signature serializer returns vectors of SIGNATURE_SERIALIZED_SIZE size");
Self::Signature::unchecked_from(r)
}
fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool {
let pubkey_array: [u8; PUBLIC_KEY_SERIALIZED_SIZE] =
match <[u8; PUBLIC_KEY_SERIALIZED_SIZE]>::try_from(pubkey.as_ref()) {
Ok(pk) => pk,
Err(_) => return false,
};
let public_key = match w3f_bls::double::DoublePublicKey::<T>::from_bytes(&pubkey_array) {
Ok(pk) => pk,
Err(_) => return false,
};
let sig_array = match sig.0[..].try_into() {
Ok(s) => s,
Err(_) => return false,
};
let sig = match w3f_bls::double::DoubleSignature::from_bytes(sig_array) {
Ok(s) => s,
Err(_) => return false,
};
sig.verify(&Message::new(b"", message.as_ref()), &public_key)
}
/// Get the seed for this key.
fn to_raw_vec(&self) -> Vec<u8> {
self.0
.secret
.to_bytes()
.try_into()
.expect("Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size")
}
}
impl<T: BlsBound> ProofOfPossessionGenerator for Pair<T> {
#[cfg(feature = "full_crypto")]
/// Generate proof of possession for BLS12 curves.
///
/// Signs on:
/// - owner as sort of back cert and proof of ownership to prevent front runner attack
/// - on its own public key with unique context to prevent rougue key attack on aggregation
fn generate_proof_of_possession(&mut self, owner: &[u8]) -> Self::ProofOfPossession {
let proof_of_ownership: [u8; SIGNATURE_SERIALIZED_SIZE] =
self.sign(statement_of_ownership(owner).as_slice()).to_raw();
let proof_of_possession: [u8; SIGNATURE_SERIALIZED_SIZE] =
<Keypair<T> as BlsProofOfPossessionGenerator<
T,
Sha256,
DoublePublicKey<T>,
NuggetBLSnCPPoP<T>,
>>::generate_pok(&mut self.0)
.to_bytes()
.try_into()
.expect("NuggetBLSnCPPoP serializer returns vectors of SIGNATURE_SERIALIZED_SIZE size");
let proof_of_ownership_and_possession: [u8; PROOF_OF_POSSESSION_SERIALIZED_SIZE] =
[proof_of_ownership, proof_of_possession]
.concat()
.try_into()
.expect("PROOF_OF_POSSESSION_SERIALIZED_SIZE = SIGNATURE_SERIALIZED_SIZE * 2");
Self::ProofOfPossession::unchecked_from(proof_of_ownership_and_possession)
}
}
impl<T: BlsBound> ProofOfPossessionVerifier for Pair<T> {
/// Verify both proof of ownership (back cert) and proof of possession of the private key
fn verify_proof_of_possession(
owner: &[u8],
proof_of_possession: &Self::ProofOfPossession,
allegedly_possessed_pubkey: &Self::Public,
) -> bool {
let Ok(allegedly_possessed_pubkey_as_bls_pubkey) =
DoublePublicKey::<T>::from_bytes(allegedly_possessed_pubkey.as_ref())
else {
return false;
};
let Ok(proof_of_ownership) = proof_of_possession.0[0..SIGNATURE_SERIALIZED_SIZE].try_into()
else {
return false;
};
if !Self::verify(
&proof_of_ownership,
statement_of_ownership(owner).as_slice(),
allegedly_possessed_pubkey,
) {
return false;
}
let Ok(proof_of_possession) =
NuggetBLSnCPPoP::<T>::from_bytes(&proof_of_possession.0[SIGNATURE_SERIALIZED_SIZE..])
else {
return false;
};
BlsProofOfPossession::<T, Sha256, _>::verify(
&proof_of_possession,
&allegedly_possessed_pubkey_as_bls_pubkey,
)
}
}
impl<T: BlsBound> CryptoType for Pair<T> {
type Pair = Pair<T>;
}
// Test set exercising the BLS12-377 implementation
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
use crate::crypto::DEV_PHRASE;
use bls377::Pair as Bls377Pair;
use bls381::Pair as Bls381Pair;
fn default_phrase_should_be_used<E: BlsBound>() {
assert_eq!(
Pair::<E>::from_string("//Alice///password", None).unwrap().public(),
Pair::<E>::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
.unwrap()
.public(),
);
}
#[test]
fn default_phrase_should_be_used_for_bls377() {
default_phrase_should_be_used::<bls377::BlsEngine>();
}
#[test]
fn default_phrase_should_be_used_for_bls381() {
default_phrase_should_be_used::<bls381::BlsEngine>();
}
fn seed_and_derive_should_work<E: BlsBound>() -> Vec<u8> {
let seed = array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
);
let pair = Pair::<E>::from_seed(&seed);
// we are using hash-to-field so this is not going to work
// assert_eq!(pair.seed(), seed);
let path = vec![DeriveJunction::Hard([0u8; 32])];
let derived = pair.derive(path.into_iter(), None).ok().unwrap().0;
println!("derived is: {:?}", array_bytes::bytes2hex("", derived.to_raw_vec()));
derived.to_raw_vec()
}
#[test]
fn seed_and_derive_should_work_for_bls377() {
let derived_as_raw_vector = seed_and_derive_should_work::<bls377::BlsEngine>();
assert_eq!(
derived_as_raw_vector,
array_bytes::hex2array_unchecked::<_, 32>(
"3a0626d095148813cd1642d38254f1cfff7eb8cc1a2fc83b2a135377c3554c12"
)
);
}
#[test]
fn seed_and_derive_should_work_for_bls381() {
let derived_as_raw_vector = seed_and_derive_should_work::<bls381::BlsEngine>();
assert_eq!(
derived_as_raw_vector,
array_bytes::hex2array_unchecked::<_, 32>(
"bb6ac58be00d3c7ae5608ca64180b5af628e79b58592b6067136bb46255cea27"
)
);
}
fn test_vector_should_work<E: BlsBound>(
pair: Pair<E>,
hex_expected_pub_key: &str,
hex_expected_signature: &str,
) {
let public = pair.public();
assert_eq!(
public,
Public::unchecked_from(array_bytes::hex2array_unchecked(hex_expected_pub_key))
);
let message = b"";
let expected_signature_bytes = array_bytes::hex2array_unchecked(hex_expected_signature);
let expected_signature = Signature::unchecked_from(expected_signature_bytes);
let signature = pair.sign(&message[..]);
assert!(signature == expected_signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn test_vector_should_work_for_bls377() {
let pair = Bls377Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
test_vector_should_work(pair,
"7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400",
"124571b4bf23083b5d07e720fde0a984d4d592868156ece77487e97a1ba4b29397dbdc454f13e3aed1ad4b6a99af2501c68ab88ec0495f962a4f55c7c460275a8d356cfa344c27778ca4c641bd9a3604ce5c28f9ed566e1d29bf3b5d3591e46ae28be3ece035e8e4db53a40fc5826002"
)
}
#[test]
fn test_vector_should_work_for_bls381() {
let pair = Bls381Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
test_vector_should_work(pair,
"88ff6c3a32542bc85f2adf1c490a929b7fcee50faeb95af9a036349390e9b3ea7326247c4fc4ebf88050688fd6265de0806284eec09ba0949f5df05dc93a787a14509749f36e4a0981bb748d953435483740907bb5c2fe8ffd97e8509e1a038b05fb08488db628ea0638b8d48c3ddf62ed437edd8b23d5989d6c65820fc70f80fb39b486a3766813e021124aec29a566",
"8f4fe16cbb1b7f26ddbfbcde864a3c2f68802fbca5bd59920a135ed7e0f74cd9ba160e61c85e9acee3b4fe277862f226e60ac1958b57ed4487daf4673af420e8bf036ee8169190a927ede2e8eb3d6600633c69b2a84eb017473988fdfde082e150cbef05b77018c1f8ccc06da9e80421"
)
}
#[test]
fn test_vector_by_string_should_work_for_bls377() {
let pair = Bls377Pair::from_string(
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
None,
)
.unwrap();
test_vector_should_work(pair,
"7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400",
"124571b4bf23083b5d07e720fde0a984d4d592868156ece77487e97a1ba4b29397dbdc454f13e3aed1ad4b6a99af2501c68ab88ec0495f962a4f55c7c460275a8d356cfa344c27778ca4c641bd9a3604ce5c28f9ed566e1d29bf3b5d3591e46ae28be3ece035e8e4db53a40fc5826002"
)
}
#[test]
fn test_vector_by_string_should_work_for_bls381() {
let pair = Bls381Pair::from_string(
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
None,
)
.unwrap();
test_vector_should_work(pair,
"88ff6c3a32542bc85f2adf1c490a929b7fcee50faeb95af9a036349390e9b3ea7326247c4fc4ebf88050688fd6265de0806284eec09ba0949f5df05dc93a787a14509749f36e4a0981bb748d953435483740907bb5c2fe8ffd97e8509e1a038b05fb08488db628ea0638b8d48c3ddf62ed437edd8b23d5989d6c65820fc70f80fb39b486a3766813e021124aec29a566",
"8f4fe16cbb1b7f26ddbfbcde864a3c2f68802fbca5bd59920a135ed7e0f74cd9ba160e61c85e9acee3b4fe277862f226e60ac1958b57ed4487daf4673af420e8bf036ee8169190a927ede2e8eb3d6600633c69b2a84eb017473988fdfde082e150cbef05b77018c1f8ccc06da9e80421"
)
}
fn test_pair<E: BlsBound>(pair: Pair<E>) -> (String, String) {
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, b"Something else", &public));
let public_bytes: &[u8] = public.as_ref();
let signature_bytes: &[u8] = signature.as_ref();
(array_bytes::bytes2hex("", public_bytes), array_bytes::bytes2hex("", signature_bytes))
}
#[test]
fn generated_pair_should_work_for_bls377() {
let (pair, _) = Bls377Pair::generate();
test_pair(pair);
}
#[test]
fn generated_pair_should_work_for_bls381() {
let (pair, _) = Bls381Pair::generate();
test_pair(pair);
}
#[test]
fn seeded_pair_should_work_for_bls377() {
let pair = Bls377Pair::from_seed(b"12345678901234567890123456789012");
let (public, _) = test_pair(pair);
assert_eq!(
public,
"754d2f2bbfa67df54d7e0e951979a18a1e0f45948857752cc2bac6bbb0b1d05e8e48bcc453920bf0c4bbd5993212480112a1fb433f04d74af0a8b700d93dc957ab3207f8d071e948f5aca1a7632c00bdf6d06be05b43e2e6216dccc8a5d55a0071cb2313cfd60b7e9114619cd17c06843b352f0b607a99122f6651df8f02e1ad3697bd208e62af047ddd7b942ba80080"
);
}
#[test]
fn seeded_pair_should_work_for_bls381() {
let pair = Bls381Pair::from_seed(b"12345678901234567890123456789012");
let (public, _) = test_pair(pair);
assert_eq!(
public,
"abe9554cc2cab7fdc391a4e07ed0f45544cf0fe235babedf553c098d37dd162d9402a0aed95c00ed01349a6017a3d864adcc9756e98b7931aa3526b1511730c9cbacf3cbe781ae5efefdb177b301bca0229a5cf87432251cd31341c9b88aea9501005fa16e814ad31a95fcc396633baf563f6306e982ddec978faa0399ba73c1c1a87fa4791b3f5bbb719c1401b2af37"
);
}
fn test_recover_with_phrase<E: BlsBound>(
pair: Pair<E>,
phrase: String,
password: Option<&str>,
) {
let (recovered_pair, _) = Pair::from_phrase(&phrase, password).unwrap();
assert_eq!(pair.public(), recovered_pair.public());
}
#[test]
fn generate_with_phrase_recovery_possible_for_bls377() {
let (pair, phrase, _) = Bls377Pair::generate_with_phrase(None);
test_recover_with_phrase(pair, phrase, None);
}
#[test]
fn generate_with_phrase_recovery_possible_for_bls381() {
let (pair, phrase, _) = Bls381Pair::generate_with_phrase(None);
test_recover_with_phrase(pair, phrase, None);
}
#[test]
fn generate_with_password_phrase_recovery_possible_for_bls377() {
let (pair, phrase, _) = Bls377Pair::generate_with_phrase(Some("password"));
test_recover_with_phrase(pair, phrase, Some("password"));
}
#[test]
fn generate_with_password_phrase_recovery_possible_for_bls381() {
let (pair, phrase, _) = Bls381Pair::generate_with_phrase(Some("password"));
test_recover_with_phrase(pair, phrase, Some("password"));
}
fn test_recover_from_seed_and_string<E: BlsBound>(pair: Pair<E>, phrase: String, seed: Seed) {
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string_for_bls377() {
let (pair, phrase, seed) = Bls377Pair::generate_with_phrase(None);
test_recover_from_seed_and_string(pair, phrase, seed);
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string_for_bls381() {
let (pair, phrase, seed) = Bls381Pair::generate_with_phrase(None);
test_recover_from_seed_and_string(pair, phrase, seed);
}
fn password_does_something<E: BlsBound>() {
let (pair1, phrase, _) = Pair::<E>::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::<E>::from_phrase(&phrase, None).unwrap();
assert_ne!(pair1.public(), pair2.public());
assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec());
}
#[test]
fn password_does_something_for_bls377() {
password_does_something::<bls377::BlsEngine>();
}
#[test]
fn password_does_something_for_bls381() {
password_does_something::<bls381::BlsEngine>();
}
fn ss58check_roundtrip_works<E: BlsBound>() {
let pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let s = public.to_ss58check();
println!("Correct: {}", s);
let cmp = Public::from_ss58check(&s).unwrap();
assert_eq!(cmp, public);
}
#[test]
fn ss58check_roundtrip_works_for_bls377() {
ss58check_roundtrip_works::<bls377::BlsEngine>();
}
#[test]
fn ss58check_roundtrip_works_for_bls381() {
ss58check_roundtrip_works::<bls381::BlsEngine>();
}
fn signature_serialization_works<E: BlsBound>() {
let pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let message = b"Something important";
let signature = pair.sign(&message[..]);
let serialized_signature = serde_json::to_string(&signature).unwrap();
// Signature is 112 bytes, hexify * 2, so 224 chars + 2 quote chars
assert_eq!(serialized_signature.len(), 226);
let signature = serde_json::from_str(&serialized_signature).unwrap();
assert!(Pair::<E>::verify(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_works_for_bls377() {
signature_serialization_works::<bls377::BlsEngine>();
}
#[test]
fn signature_serialization_works_for_bls381() {
signature_serialization_works::<bls381::BlsEngine>();
}
fn signature_serialization_doesnt_panic<E: BlsBound>() {
fn deserialize_signature<E: BlsBound>(
text: &str,
) -> Result<Signature<E>, serde_json::error::Error> {
serde_json::from_str(text)
}
assert!(deserialize_signature::<E>("Not valid json.").is_err());
assert!(deserialize_signature::<E>("\"Not an actual signature.\"").is_err());
// Poorly-sized
assert!(deserialize_signature::<E>("\"abc123\"").is_err());
}
#[test]
fn signature_serialization_doesnt_panic_for_bls377() {
signature_serialization_doesnt_panic::<bls377::BlsEngine>();
}
#[test]
fn signature_serialization_doesnt_panic_for_bls381() {
signature_serialization_doesnt_panic::<bls381::BlsEngine>();
}
fn must_generate_proof_of_possession<E: BlsBound>() {
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let owner = b"owner";
pair.generate_proof_of_possession(owner);
}
#[test]
fn must_generate_proof_of_possession_for_bls377() {
must_generate_proof_of_possession::<bls377::BlsEngine>();
}
#[test]
fn must_generate_proof_of_possession_for_bls381() {
must_generate_proof_of_possession::<bls381::BlsEngine>();
}
fn good_proof_of_possession_must_verify<E: BlsBound>() {
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let owner = b"owner";
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::<E>::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
}
#[test]
fn good_proof_of_possession_must_verify_for_bls377() {
good_proof_of_possession_must_verify::<bls377::BlsEngine>();
}
#[test]
fn good_proof_of_possession_must_verify_for_bls381() {
good_proof_of_possession_must_verify::<bls381::BlsEngine>();
}
fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key<E: BlsBound>() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::<E>::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::<E>::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert_eq!(
Pair::<E>::verify_proof_of_possession(
owner,
&proof_of_possession,
&other_pair.public()
),
false
);
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
#[test]
fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key_for_bls377() {
proof_of_possession_must_fail_if_prover_does_not_possess_secret_key::<bls377::BlsEngine>();
}
#[test]
fn proof_of_possession_must_fail_if_prover_does_not_possess_secret_key_for_bls381() {
proof_of_possession_must_fail_if_prover_does_not_possess_secret_key::<bls381::BlsEngine>();
}
}
@@ -0,0 +1,162 @@
// 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.
//! Provides a const function for converting a hex string to a `u8` array at compile time, when used
//! in the proper context.
/// Provides a const array from given string literal.
///
/// Valid characters are `[0-9a-fA-F]`, and the hex string should not start
/// with the `0x` prefix.
#[macro_export]
macro_rules! hex2array {
($input:expr) => {{
const BYTES: [u8; $input.len() / 2] = $crate::const_hex2array::private_hex2array($input);
BYTES
}};
}
/// Generates array from (static) string literal.
///
/// Valid characters are `[0-9a-fA-F]`, and the hex string should not start
/// with the `0x` prefix.
///
/// # Panics
///
/// The function will panic at compile time when used in a const context if:
/// - The given hex string has an invalid length.
/// - It contains invalid characters.
///
/// The function will panic at runtime when used in a non-const context if the above conditions are
/// met.
#[doc(hidden)]
pub const fn private_hex2array<const N: usize>(hex: &str) -> [u8; N] {
const fn c2b(c: u8) -> u8 {
match c as char {
'0'..='9' => c - b'0',
'a'..='f' => c - (b'a' - 10),
'A'..='F' => c - (b'A' - 10),
_ => panic!("hex string contains invalid character"),
}
}
let mut output = [0; N];
let mut i = 0;
if hex.len() != 2 * N {
panic!("hex string length is not valid");
}
while i < N {
output[i] = 16 * c2b(hex.as_bytes()[2 * i]) + c2b(hex.as_bytes()[2 * i + 1]);
i += 1;
}
output
}
#[cfg(test)]
mod testh2b {
use super::private_hex2array;
#[test]
fn t00() {
const T0: [u8; 0] = private_hex2array("");
const EMPTY: [u8; 0] = [];
assert_eq!(T0, EMPTY);
}
macro_rules! test_byte {
($a:expr, $b:expr) => {{
const X: [u8; 1] = private_hex2array($a);
assert_eq!(X, [$b]);
}};
}
#[test]
fn t01() {
test_byte!("00", 0);
test_byte!("01", 1);
test_byte!("02", 2);
test_byte!("03", 3);
test_byte!("04", 4);
test_byte!("05", 5);
test_byte!("06", 6);
test_byte!("07", 7);
test_byte!("08", 8);
test_byte!("09", 9);
test_byte!("0a", 10);
test_byte!("0A", 10);
test_byte!("0b", 11);
test_byte!("0B", 11);
test_byte!("0c", 12);
test_byte!("0C", 12);
test_byte!("0d", 13);
test_byte!("0D", 13);
test_byte!("0e", 14);
test_byte!("0E", 14);
test_byte!("0f", 15);
test_byte!("0F", 15);
}
#[test]
fn t02() {
const T0: [u8; 2] = private_hex2array("0a10");
assert_eq!(T0, [10, 16]);
const T1: [u8; 2] = private_hex2array("4545");
assert_eq!(T1, [69, 69]);
}
#[test]
fn t02m() {
assert_eq!(hex2array!("0a10"), [10, 16]);
assert_eq!(hex2array!("4545"), [69, 69]);
assert_eq!(
hex2array!("000102030405060708090a0b0c0d0e0f"),
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
);
}
#[test]
fn t16() {
const T16: [u8; 16] = private_hex2array("000102030405060708090a0b0c0d0e0f");
assert_eq!(T16, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
}
#[test]
fn t33() {
const T33: [u8; 33] =
private_hex2array("9c8af77d3a4e3f6f076853922985b9e6724fc9675329087f47aff1ceaaae772180");
assert_eq!(
T33,
[
156, 138, 247, 125, 58, 78, 63, 111, 7, 104, 83, 146, 41, 133, 185, 230, 114, 79,
201, 103, 83, 41, 8, 127, 71, 175, 241, 206, 170, 174, 119, 33, 128
]
);
}
#[test]
#[should_panic = "hex string length is not valid"]
fn t_panic_incorrect_length2() {
let _ = private_hex2array::<2>("454");
}
#[test]
#[should_panic = "hex string contains invalid character"]
fn t_panic_invalid_character() {
let _ = private_hex2array::<2>("45ag");
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,357 @@
// 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.
//! Generic byte array which can be specialized with a marker type.
use crate::{
crypto::{CryptoType, Derive, FromEntropy, Public, Signature, UncheckedFrom},
hash::{H256, H512},
};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use scale_info::TypeInfo;
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(all(not(feature = "std"), feature = "serde"))]
use alloc::{format, string::String};
pub use public_bytes::*;
pub use signature_bytes::*;
/// Generic byte array holding some crypto-related raw data.
///
/// The type is generic over a constant length `N` and a "tag" `T` which
/// can be used to specialize the byte array without requiring newtypes.
///
/// The tag `T` is held in a `PhantomData<fn() ->T>`, a trick allowing
/// `CryptoBytes` to be `Send` and `Sync` regardless of `T` properties
/// ([ref](https://doc.rust-lang.org/nomicon/phantom-data.html#table-of-phantomdata-patterns)).
#[derive(Encode, Decode, DecodeWithMemTracking, MaxEncodedLen)]
#[repr(transparent)]
pub struct CryptoBytes<const N: usize, T = ()>(pub [u8; N], PhantomData<fn() -> T>);
impl<const N: usize, T> Copy for CryptoBytes<N, T> {}
impl<const N: usize, T> Clone for CryptoBytes<N, T> {
fn clone(&self) -> Self {
Self(self.0, PhantomData)
}
}
impl<const N: usize, T> TypeInfo for CryptoBytes<N, T> {
type Identity = [u8; N];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
impl<const N: usize, T> PartialOrd for CryptoBytes<N, T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<const N: usize, T> Ord for CryptoBytes<N, T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<const N: usize, T> PartialEq for CryptoBytes<N, T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<const N: usize, T> core::hash::Hash for CryptoBytes<N, T> {
fn hash<H: scale_info::prelude::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<const N: usize, T> Eq for CryptoBytes<N, T> {}
impl<const N: usize, T> Default for CryptoBytes<N, T> {
fn default() -> Self {
Self([0_u8; N], PhantomData)
}
}
impl<const N: usize, T> AsRef<[u8]> for CryptoBytes<N, T> {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl<const N: usize, T> AsMut<[u8]> for CryptoBytes<N, T> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
impl<const N: usize, T> From<CryptoBytes<N, T>> for [u8; N] {
fn from(v: CryptoBytes<N, T>) -> [u8; N] {
v.0
}
}
impl<const N: usize, T> AsRef<[u8; N]> for CryptoBytes<N, T> {
fn as_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize, T> AsMut<[u8; N]> for CryptoBytes<N, T> {
fn as_mut(&mut self) -> &mut [u8; N] {
&mut self.0
}
}
impl<const N: usize, T> From<[u8; N]> for CryptoBytes<N, T> {
fn from(value: [u8; N]) -> Self {
Self::from_raw(value)
}
}
impl<const N: usize, T> TryFrom<&[u8]> for CryptoBytes<N, T> {
type Error = ();
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
if data.len() != N {
return Err(());
}
let mut r = [0u8; N];
r.copy_from_slice(data);
Ok(Self::from_raw(r))
}
}
impl<const N: usize, T> UncheckedFrom<[u8; N]> for CryptoBytes<N, T> {
fn unchecked_from(data: [u8; N]) -> Self {
Self::from_raw(data)
}
}
impl<const N: usize, T> core::ops::Deref for CryptoBytes<N, T> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize, T> CryptoBytes<N, T> {
/// Construct from raw array.
pub fn from_raw(inner: [u8; N]) -> Self {
Self(inner, PhantomData)
}
/// Construct from raw array.
pub fn to_raw(self) -> [u8; N] {
self.0
}
/// Return a slice filled with raw data.
pub fn as_array_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize, T> crate::ByteArray for CryptoBytes<N, T> {
const LEN: usize = N;
}
impl<const N: usize, T> FromEntropy for CryptoBytes<N, T> {
fn from_entropy(input: &mut impl codec::Input) -> Result<Self, codec::Error> {
let mut result = Self::default();
input.read(result.as_mut())?;
Ok(result)
}
}
impl<T> From<CryptoBytes<32, T>> for H256 {
fn from(x: CryptoBytes<32, T>) -> H256 {
H256::from(x.0)
}
}
impl<T> From<CryptoBytes<64, T>> for H512 {
fn from(x: CryptoBytes<64, T>) -> H512 {
H512::from(x.0)
}
}
impl<T> UncheckedFrom<H256> for CryptoBytes<32, T> {
fn unchecked_from(x: H256) -> Self {
Self::from_h256(x)
}
}
impl<T> CryptoBytes<32, T> {
/// A new instance from an H256.
pub fn from_h256(x: H256) -> Self {
Self::from_raw(x.into())
}
}
impl<T> CryptoBytes<64, T> {
/// A new instance from an H512.
pub fn from_h512(x: H512) -> Self {
Self::from_raw(x.into())
}
}
mod public_bytes {
use super::*;
/// Tag used for generic public key bytes.
pub struct PublicTag;
/// Generic encoded public key.
pub type PublicBytes<const N: usize, SubTag> = CryptoBytes<N, (PublicTag, SubTag)>;
impl<const N: usize, SubTag> Derive for PublicBytes<N, SubTag> where Self: CryptoType {}
impl<const N: usize, SubTag> Public for PublicBytes<N, SubTag> where Self: CryptoType {}
impl<const N: usize, SubTag> core::fmt::Debug for PublicBytes<N, SubTag>
where
Self: CryptoType,
{
#[cfg(feature = "std")]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = self.to_ss58check();
write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.as_ref()), &s[0..8])
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
Ok(())
}
}
#[cfg(feature = "std")]
impl<const N: usize, SubTag> std::fmt::Display for PublicBytes<N, SubTag>
where
Self: CryptoType,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_ss58check())
}
}
#[cfg(feature = "std")]
impl<const N: usize, SubTag> std::str::FromStr for PublicBytes<N, SubTag>
where
Self: CryptoType,
{
type Err = crate::crypto::PublicError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_ss58check(s)
}
}
#[cfg(feature = "serde")]
impl<const N: usize, SubTag> Serialize for PublicBytes<N, SubTag>
where
Self: CryptoType,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_ss58check())
}
}
#[cfg(feature = "serde")]
impl<'de, const N: usize, SubTag> Deserialize<'de> for PublicBytes<N, SubTag>
where
Self: CryptoType,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::from_ss58check(&String::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(format!("{:?}", e)))
}
}
}
mod signature_bytes {
use super::*;
/// Tag used for generic signature bytes.
pub struct SignatureTag;
/// Generic encoded signature.
pub type SignatureBytes<const N: usize, SubTag> = CryptoBytes<N, (SignatureTag, SubTag)>;
impl<const N: usize, SubTag> Signature for SignatureBytes<N, SubTag> where Self: CryptoType {}
#[cfg(feature = "serde")]
impl<const N: usize, SubTag> Serialize for SignatureBytes<N, SubTag>
where
Self: CryptoType,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&array_bytes::bytes2hex("", self))
}
}
#[cfg(feature = "serde")]
impl<'de, const N: usize, SubTag> Deserialize<'de> for SignatureBytes<N, SubTag>
where
Self: CryptoType,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(format!("{:?}", e)))?;
Self::try_from(signature_hex.as_ref())
.map_err(|e| de::Error::custom(format!("{:?}", e)))
}
}
impl<const N: usize, SubTag> core::fmt::Debug for SignatureBytes<N, SubTag>
where
Self: CryptoType,
{
#[cfg(feature = "std")]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", crate::hexdisplay::HexDisplay::from(&&self.0[..]))
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
Ok(())
}
}
}
+147
View File
@@ -0,0 +1,147 @@
// 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.
//! Contains the [`crate::defer!`] macro for *deferring* the execution
//! of code until the current scope is dropped.
//! This helps with *always* executing cleanup code.
/// Executes the wrapped closure on drop.
///
/// Should be used together with the [`crate::defer!`] macro.
#[must_use]
pub struct DeferGuard<F: FnOnce()>(pub Option<F>);
impl<F: FnOnce()> DeferGuard<F> {
/// Creates a new `DeferGuard` with the given closure.
pub fn new(f: F) -> Self {
Self(Some(f))
}
}
impl<F: FnOnce()> Drop for DeferGuard<F> {
fn drop(&mut self) {
self.0.take().map(|f| f());
}
}
/// Executes the given code when the current scope is dropped.
///
/// Multiple calls to [`crate::defer!`] will execute the passed codes in reverse order.
/// This also applies to panic stack unwinding.
///
/// # Example
///
/// ```rust
/// use pezsp_core::defer;
///
/// let message = std::cell::RefCell::new("".to_string());
/// {
/// defer!(
/// message.borrow_mut().push_str("world!");
/// );
/// defer!(
/// message.borrow_mut().push_str("Hello ");
/// );
/// }
/// assert_eq!(*message.borrow(), "Hello world!");
/// ```
#[macro_export]
macro_rules! defer(
( $( $code:tt )* ) => {
let _guard = $crate::defer::DeferGuard(Some(|| { $( $code )* }));
};
);
#[cfg(test)]
mod test {
#[test]
fn defer_guard_works() {
let mut called = false;
{
defer!(
called = true;
);
}
assert!(called, "DeferGuard should have executed the closure");
}
#[test]
/// `defer` executes the code in reverse order of being called.
fn defer_guard_order_works() {
let called = std::cell::RefCell::new(1);
defer!(
assert_eq!(*called.borrow(), 3);
);
defer!(
assert_eq!(*called.borrow(), 2);
*called.borrow_mut() = 3;
);
defer!({
assert_eq!(*called.borrow(), 1);
*called.borrow_mut() = 2;
});
}
#[test]
#[allow(unused_braces)]
#[allow(clippy::unnecessary_operation)]
fn defer_guard_syntax_works() {
let called = std::cell::RefCell::new(0);
{
defer!(*called.borrow_mut() += 1);
defer!(*called.borrow_mut() += 1;); // With ;
defer!({ *called.borrow_mut() += 1 });
defer!({ *called.borrow_mut() += 1 };); // With ;
}
assert_eq!(*called.borrow(), 4);
}
#[test]
/// `defer` executes the code even in case of a panic.
fn defer_guard_panic_unwind_works() {
use std::panic::{catch_unwind, AssertUnwindSafe};
let mut called = false;
let should_panic = catch_unwind(AssertUnwindSafe(|| {
defer!(called = true);
panic!();
}));
assert!(should_panic.is_err(), "DeferGuard should have panicked");
assert!(called, "DeferGuard should have executed the closure");
}
#[test]
/// `defer` executes the code even in case another `defer` panics.
fn defer_guard_defer_panics_unwind_works() {
use std::panic::{catch_unwind, AssertUnwindSafe};
let counter = std::cell::RefCell::new(0);
let should_panic = catch_unwind(AssertUnwindSafe(|| {
defer!(*counter.borrow_mut() += 1);
defer!(
*counter.borrow_mut() += 1;
panic!();
);
defer!(*counter.borrow_mut() += 1);
}));
assert!(should_panic.is_err(), "DeferGuard should have panicked");
assert_eq!(*counter.borrow(), 3, "DeferGuard should have executed the closure");
}
}
+873
View File
@@ -0,0 +1,873 @@
// 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.
//! Simple ECDSA secp256k1 API.
use crate::{
crypto::{
CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair, PublicBytes,
SecretStringError, SignatureBytes,
},
proof_of_possession::NonAggregatable,
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use k256::ecdsa::{SigningKey as SecretKey, VerifyingKey};
#[cfg(feature = "std")]
use secp256k1::{
ecdsa::{RecoverableSignature, RecoveryId},
Message, PublicKey, SecretKey, SECP256K1,
};
#[cfg(all(feature = "std", feature = "full_crypto"))]
type NativeSignature = secp256k1::ecdsa::RecoverableSignature;
#[cfg(all(not(feature = "std"), feature = "full_crypto"))]
type NativeSignature = (k256::ecdsa::Signature, k256::ecdsa::RecoveryId);
/// An identifier used to match public keys against ecdsa keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds");
/// The byte length of public key
pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 33;
/// The byte length of signature
pub const SIGNATURE_SERIALIZED_SIZE: usize = 65;
#[doc(hidden)]
#[derive(Clone)]
pub struct EcdsaTag;
#[doc(hidden)]
#[derive(Clone)]
pub struct EcdsaKeccakTag;
/// The secret seed.
///
/// The raw secret seed, which can be used to create the `Pair`.
type Seed = [u8; 32];
#[doc(hidden)]
pub type GenericPublic<TAG> = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, TAG>;
/// The ECDSA compressed public key.
///
/// Uses blake2 during key recovery.
pub type Public = GenericPublic<EcdsaTag>;
/// The ECDSA compressed public key.
///
/// Uses keccak during key recovery.
pub type KeccakPublic = GenericPublic<EcdsaKeccakTag>;
impl<TAG> GenericPublic<TAG> {
/// Create a new instance from the given full public key.
///
/// This will convert the full public key into the compressed format.
pub fn from_full(full: &[u8]) -> Result<Self, ()> {
let mut tagged_full = [0u8; 65];
let full = if full.len() == 64 {
// Tag it as uncompressed public key.
tagged_full[0] = 0x04;
tagged_full[1..].copy_from_slice(full);
&tagged_full
} else {
full
};
#[cfg(feature = "std")]
let pubkey = PublicKey::from_slice(&full);
#[cfg(not(feature = "std"))]
let pubkey = VerifyingKey::from_sec1_bytes(&full);
pubkey.map(|k| k.into()).map_err(|_| ())
}
}
impl<TAG> PartialEq<[u8; 33]> for GenericPublic<TAG> {
fn eq(&self, other: &[u8; 33]) -> bool {
&self.0 == other
}
}
#[cfg(feature = "std")]
impl<TAG> From<PublicKey> for GenericPublic<TAG> {
fn from(pubkey: PublicKey) -> Self {
Self::from(pubkey.serialize())
}
}
#[cfg(not(feature = "std"))]
impl<TAG> From<VerifyingKey> for GenericPublic<TAG> {
fn from(pubkey: VerifyingKey) -> Self {
Self::try_from(&pubkey.to_sec1_bytes()[..])
.expect("Valid key is serializable to [u8; 33]. qed.")
}
}
#[cfg(feature = "full_crypto")]
impl<TAG> From<GenericPair<GenericPublic<TAG>>> for GenericPublic<TAG> {
fn from(x: GenericPair<GenericPublic<TAG>>) -> Self {
x.public
}
}
#[doc(hidden)]
pub type GenericSignature<PUBLIC> = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, PUBLIC>;
/// A signature (a 512-bit value, plus 8 bits for recovery ID).
///
/// Uses blake2 during key recovery.
pub type Signature = GenericSignature<Public>;
/// A signature (a 512-bit value, plus 8 bits for recovery ID).
///
/// Uses keccak during key recovery.
pub type KeccakSignature = GenericSignature<KeccakPublic>;
/// A signature that allows recovering the public key from a message.
pub trait Recover: seal::Sealed {
/// The public key that will be recovered from the signature.
type Public;
/// Recover the public key from this signature and a pre-hashed message.
fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public>;
/// Recover the public key from this signature and a message.
fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public>;
}
#[cfg(feature = "std")]
impl<PUBLIC: From<PublicKey>> GenericSignature<PUBLIC> {
/// Recover the public key from this signature and a pre-hashed message.
pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option<PUBLIC> {
let rid = RecoveryId::from_i32(self.0[64] as i32).ok()?;
let sig = RecoverableSignature::from_compact(&self.0[..64], rid).ok()?;
let message = Message::from_digest_slice(message).expect("Message is a 32 bytes hash; qed");
SECP256K1.recover_ecdsa(&message, &sig).ok().map(From::from)
}
}
#[cfg(not(feature = "std"))]
impl<PUBLIC: From<VerifyingKey>> GenericSignature<PUBLIC> {
/// Recover the public key from this signature and a pre-hashed message.
pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option<PUBLIC> {
let rid = k256::ecdsa::RecoveryId::from_byte(self.0[64])?;
let sig = k256::ecdsa::Signature::from_bytes((&self.0[..64]).into()).ok()?;
VerifyingKey::recover_from_prehash(message, &sig, rid).map(From::from).ok()
}
}
/// Proof of Possession is the same as Signature.
///
/// Uses blake2 during key recovery.
pub type ProofOfPossession = Signature;
/// Proof of Possession is the same as Signature.
///
/// Uses keccak during key recovery.
pub type KeccakProofOfPossession = KeccakSignature;
impl Signature {
/// Recover the public key from this signature and a message.
pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Public> {
self.recover_prehashed(&pezsp_crypto_hashing::blake2_256(message.as_ref()))
}
}
impl KeccakSignature {
/// Recover the public key from this signature and a message.
pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<KeccakPublic> {
self.recover_prehashed(&pezsp_crypto_hashing::keccak_256(message.as_ref()))
}
}
impl Recover for Signature {
type Public = Public;
fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public> {
self.recover_prehashed(message)
}
fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public> {
self.recover(message)
}
}
impl Recover for KeccakSignature {
type Public = KeccakPublic;
fn recover_prehashed(&self, message: &[u8; 32]) -> Option<Self::Public> {
self.recover_prehashed(message)
}
fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Self::Public> {
self.recover(message)
}
}
#[cfg(not(feature = "std"))]
impl<PUBLIC> From<(k256::ecdsa::Signature, k256::ecdsa::RecoveryId)> for GenericSignature<PUBLIC> {
fn from(recsig: (k256::ecdsa::Signature, k256::ecdsa::RecoveryId)) -> Self {
let mut r = Self::default();
r.0[..64].copy_from_slice(&recsig.0.to_bytes());
r.0[64] = recsig.1.to_byte();
r
}
}
#[cfg(feature = "std")]
impl<PUBLIC> From<RecoverableSignature> for GenericSignature<PUBLIC> {
fn from(recsig: RecoverableSignature) -> Self {
let mut r = Self::default();
let (recid, sig) = recsig.serialize_compact();
r.0[..64].copy_from_slice(&sig);
// This is safe due to the limited range of possible valid ids.
r.0[64] = recid.to_i32() as u8;
r
}
}
/// Derive a single hard junction.
fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
use codec::Encode;
("Secp256k1HDKD", secret_seed, cc).using_encoded(pezsp_crypto_hashing::blake2_256)
}
#[derive(Clone)]
#[doc(hidden)]
pub struct GenericPair<PUBLIC> {
public: PUBLIC,
secret: SecretKey,
}
/// An ecdsa key pair using the blake2 algorithm for hashing the message.
pub type Pair = GenericPair<Public>;
/// An ecdsa key pair using the keccak algorithm for hashing the message.
pub type KeccakPair = GenericPair<KeccakPublic>;
impl TraitPair for Pair {
type Public = Public;
type Seed = Seed;
type Signature = Signature;
type ProofOfPossession = ProofOfPossession;
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
Self::from_seed_slice(seed_slice)
}
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
_seed: Option<Seed>,
) -> Result<(Self, Option<Seed>), DeriveError> {
self.derive(path)
}
fn public(&self) -> Self::Public {
self.public
}
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Self::Signature {
self.sign(message)
}
/// Verify a signature on a message. Returns true if the signature is good.
fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Public) -> bool {
Self::verify(sig, message, public)
}
/// Return a vec filled with raw data.
fn to_raw_vec(&self) -> Vec<u8> {
self.to_raw_vec()
}
}
impl TraitPair for KeccakPair {
type Public = KeccakPublic;
type Seed = Seed;
type Signature = KeccakSignature;
type ProofOfPossession = KeccakProofOfPossession;
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
Self::from_seed_slice(seed_slice)
}
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
_seed: Option<Seed>,
) -> Result<(Self, Option<Seed>), DeriveError> {
self.derive(path)
}
fn public(&self) -> Self::Public {
self.public
}
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Self::Signature {
self.sign(message)
}
/// Verify a signature on a message. Returns true if the signature is good.
fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool {
Self::verify(sig, message, public)
}
/// Return a vec filled with raw data.
fn to_raw_vec(&self) -> Vec<u8> {
self.to_raw_vec()
}
}
impl<PUBLIC> GenericPair<PUBLIC>
where
Self: TraitPair<Seed = Seed, Signature: Recover>,
<<Self as TraitPair>::Signature as Recover>::Public: PartialEq<PUBLIC>,
PUBLIC: PartialEq<[u8; 33]>,
{
/// Get the seed for this key.
pub fn seed(&self) -> Seed {
#[cfg(feature = "std")]
{
self.secret.secret_bytes()
}
#[cfg(not(feature = "std"))]
{
self.secret.to_bytes().into()
}
}
/// Exactly as `from_string` except that if no matches are found then, the the first 32
/// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey.
#[cfg(feature = "std")]
pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Self {
Self::from_string(s, password_override).unwrap_or_else(|_| {
let mut padded_seed: Seed = [b' '; 32];
let len = s.len().min(32);
padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]);
Self::from_seed(&padded_seed)
})
}
/// Verify a signature on a pre-hashed message. Return `true` if the signature is valid
/// and thus matches the given `public` key.
pub fn verify_prehashed(
sig: &<Self as TraitPair>::Signature,
message: &[u8; 32],
public: &PUBLIC,
) -> bool {
match sig.recover_prehashed(message) {
Some(actual) => actual == *public,
None => false,
}
}
/// Verify a signature on a message. Returns true if the signature is good.
/// Parses Signature using parse_overflowing_slice.
#[deprecated(note = "please use `verify` instead")]
pub fn verify_deprecated<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &Public) -> bool {
let message =
libsecp256k1::Message::parse(&pezsp_crypto_hashing::blake2_256(message.as_ref()));
let parse_signature_overflowing = |x: [u8; SIGNATURE_SERIALIZED_SIZE]| {
let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?;
let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?;
Some((sig, rid))
};
let (sig, rid) = match parse_signature_overflowing(sig.0) {
Some(sigri) => sigri,
_ => return false,
};
match libsecp256k1::recover(&message, &sig, &rid) {
Ok(actual) => pubkey == &actual.serialize_compressed(),
_ => false,
}
}
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
) -> Result<(Self, Option<Seed>), DeriveError> {
let mut acc = self.seed();
for j in path {
match j {
DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath),
DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc),
}
}
Ok((Self::from_seed(&acc), Some(acc)))
}
fn verify<M: AsRef<[u8]>>(
sig: &<Self as TraitPair>::Signature,
message: M,
public: &PUBLIC,
) -> bool {
sig.recover(message).map(|actual| actual == *public).unwrap_or_default()
}
fn to_raw_vec(&self) -> Vec<u8> {
self.seed().to_vec()
}
}
#[cfg(feature = "std")]
impl<PUBLIC: From<PublicKey>> GenericPair<PUBLIC> {
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
let secret =
SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?;
Ok(Self { public: PublicKey::from_secret_key(&SECP256K1, &secret).into(), secret })
}
}
#[cfg(not(feature = "std"))]
impl<PUBLIC: From<VerifyingKey>> GenericPair<PUBLIC> {
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
let secret =
SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?;
Ok(Self { public: VerifyingKey::from(&secret).into(), secret })
}
}
#[cfg(feature = "full_crypto")]
impl<PUBLIC> GenericPair<PUBLIC>
where
Self: TraitPair,
<Self as TraitPair>::Signature: From<NativeSignature>,
{
/// Sign a pre-hashed message
pub fn sign_prehashed(&self, message: &[u8; 32]) -> <Self as TraitPair>::Signature {
#[cfg(feature = "std")]
{
let message =
Message::from_digest_slice(message).expect("Message is a 32 bytes hash; qed");
SECP256K1.sign_ecdsa_recoverable(&message, &self.secret).into()
}
#[cfg(not(feature = "std"))]
{
// Signing fails only if the `message` number of bytes is less than the field length
// (unfallible as we're using a fixed message length of 32).
self.secret
.sign_prehash_recoverable(message)
.expect("Signing can't fail when using 32 bytes message hash. qed.")
.into()
}
}
}
#[cfg(feature = "full_crypto")]
impl Pair
where
<Self as TraitPair>::Signature: From<NativeSignature>,
{
fn sign(&self, message: &[u8]) -> Signature {
self.sign_prehashed(&pezsp_crypto_hashing::blake2_256(message))
}
}
#[cfg(feature = "full_crypto")]
impl KeccakPair
where
<Self as TraitPair>::Signature: From<NativeSignature>,
{
fn sign(&self, message: &[u8]) -> KeccakSignature {
self.sign_prehashed(&pezsp_crypto_hashing::keccak_256(message))
}
}
// The `secp256k1` backend doesn't implement cleanup for their private keys.
// Currently we should take care of wiping the secret from memory.
// NOTE: this solution is not effective when `Pair` is moved around memory.
// The very same problem affects other cryptographic backends that are just using
// `zeroize`for their secrets.
#[cfg(feature = "std")]
impl<PUBLIC> Drop for GenericPair<PUBLIC> {
fn drop(&mut self) {
self.secret.non_secure_erase()
}
}
impl CryptoType for Public {
type Pair = Pair;
}
impl CryptoType for KeccakPublic {
type Pair = KeccakPair;
}
impl CryptoType for Signature {
type Pair = Pair;
}
impl CryptoType for KeccakSignature {
type Pair = KeccakPair;
}
impl CryptoType for Pair {
type Pair = Self;
}
impl CryptoType for KeccakPair {
type Pair = Self;
}
impl NonAggregatable for Pair {}
mod seal {
pub trait Sealed {}
impl Sealed for super::Signature {}
impl Sealed for super::KeccakSignature {}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
crypto::{
set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry,
Ss58Codec, DEV_PHRASE,
},
proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier},
};
use serde_json;
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
.unwrap()
.public(),
);
}
#[test]
fn seed_and_derive_should_work() {
let seed = array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
);
let pair = Pair::from_seed(&seed);
assert_eq!(pair.seed(), seed);
let path = vec![DeriveJunction::Hard([0u8; 32])];
let derived = pair.derive(path.into_iter()).ok().unwrap();
assert_eq!(
derived.0.seed(),
array_bytes::hex2array_unchecked::<_, 32>(
"b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61"
)
);
}
#[test]
fn test_vector_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let public = pair.public();
assert_eq!(
public,
Public::from_full(
&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
).unwrap(),
);
let message = b"";
let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
let signature = Signature::from_raw(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn test_vector_by_string_should_work() {
let pair = Pair::from_string(
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
None,
)
.unwrap();
let public = pair.public();
assert_eq!(
public,
Public::from_full(
&array_bytes::hex2bytes_unchecked("8db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd913ebbe148dd17c56551a52952371071a6c604b3f3abe8f2c8fa742158ea6dd7d4"),
).unwrap(),
);
let message = b"";
let signature = array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00");
let signature = Signature::from_raw(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn generated_pair_should_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, b"Something else", &public));
}
#[test]
fn generated_pair_should_work_keccak() {
let (pair, _) = KeccakPair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(KeccakPair::verify(&signature, &message[..], &public));
assert!(!KeccakPair::verify(&signature, b"Something else", &public));
}
#[test]
fn seeded_pair_should_work() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
assert_eq!(
public,
Public::from_full(
&array_bytes::hex2bytes_unchecked("5676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba995840f3de562156558efbfdac3f16af0065e5f66795f4dd8262a228ef8c6d813"),
).unwrap(),
);
let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000");
let signature = pair.sign(&message[..]);
println!("Correct signature: {:?}", signature);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, "Other message", &public));
}
#[test]
fn generate_with_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(None);
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn generate_with_password_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string() {
let (pair, phrase, seed) = Pair::generate_with_phrase(None);
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
assert_eq!(pair.secret, repair_seed.secret);
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
assert_eq!(pair.secret, repair_phrase.secret);
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
assert_eq!(pair.secret, repair_string.secret);
}
#[test]
fn password_does_something() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_ne!(pair1.public(), pair2.public());
assert_ne!(pair1.secret, pair2.secret);
}
#[test]
fn ss58check_roundtrip_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let s = public.to_ss58check();
println!("Correct: {}", s);
let cmp = Public::from_ss58check(&s).unwrap();
assert_eq!(cmp, public);
}
#[test]
fn ss58check_format_check_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let format = Ss58AddressFormatRegistry::Reserved46Account.into();
let s = public.to_ss58check_with_version(format);
assert_eq!(Public::from_ss58check_with_version(&s), Err(PublicError::FormatNotAllowed));
}
#[test]
fn ss58check_full_roundtrip_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let format = Ss58AddressFormatRegistry::PezkuwiAccount.into();
let s = public.to_ss58check_with_version(format);
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
assert_eq!(k, public);
assert_eq!(f, format);
let format = Ss58AddressFormat::custom(64);
let s = public.to_ss58check_with_version(format);
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
assert_eq!(k, public);
assert_eq!(f, format);
}
#[test]
fn ss58check_custom_format_works() {
// We need to run this test in its own process to not interfere with other tests running in
// parallel and also relying on the ss58 version.
if std::env::var("RUN_CUSTOM_FORMAT_TEST") == Ok("1".into()) {
use crate::crypto::Ss58AddressFormat;
// temp save default format version
let default_format = crate::crypto::default_ss58_version();
// set current ss58 version is custom "200" `Ss58AddressFormat::Custom(200)`
set_default_ss58_version(Ss58AddressFormat::custom(200));
// custom addr encoded by version 200
let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV";
Public::from_ss58check(addr).unwrap();
set_default_ss58_version(default_format);
// set current ss58 version to default version
let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C";
Public::from_ss58check(addr).unwrap();
println!("CUSTOM_FORMAT_SUCCESSFUL");
} else {
let executable = std::env::current_exe().unwrap();
let output = std::process::Command::new(executable)
.env("RUN_CUSTOM_FORMAT_TEST", "1")
.args(&["--nocapture", "ss58check_custom_format_works"])
.output()
.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
assert!(output.contains("CUSTOM_FORMAT_SUCCESSFUL"));
}
}
#[test]
fn signature_serialization_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let message = b"Something important";
let signature = pair.sign(&message[..]);
let serialized_signature = serde_json::to_string(&signature).unwrap();
// Signature is 65 bytes, so 130 chars + 2 quote chars
assert_eq!(serialized_signature.len(), SIGNATURE_SERIALIZED_SIZE * 2 + 2);
let signature = serde_json::from_str(&serialized_signature).unwrap();
assert!(Pair::verify(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_doesnt_panic() {
fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
serde_json::from_str(text)
}
assert!(deserialize_signature("Not valid json.").is_err());
assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
// Poorly-sized
assert!(deserialize_signature("\"abc123\"").is_err());
}
#[test]
fn sign_prehashed_works() {
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
// `msg` shouldn't be mangled
let msg = [0u8; 32];
let sig1 = pair.sign_prehashed(&msg);
let sig2: Signature = {
#[cfg(feature = "std")]
{
let message = Message::from_digest_slice(&msg).unwrap();
SECP256K1.sign_ecdsa_recoverable(&message, &pair.secret).into()
}
#[cfg(not(feature = "std"))]
{
pair.secret
.sign_prehash_recoverable(&msg)
.expect("signing may not fail (???). qed.")
.into()
}
};
assert_eq!(sig1, sig2);
// signature is actually different
let sig2 = pair.sign(&msg);
assert_ne!(sig1, sig2);
// using pre-hashed `msg` works
let msg = b"this should be hashed";
let sig1 = pair.sign_prehashed(&pezsp_crypto_hashing::blake2_256(msg));
let sig2 = pair.sign(msg);
assert_eq!(sig1, sig2);
}
#[test]
fn verify_prehashed_works() {
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
// `msg` and `sig` match
let msg = pezsp_crypto_hashing::blake2_256(b"this should be hashed");
let sig = pair.sign_prehashed(&msg);
assert!(Pair::verify_prehashed(&sig, &msg, &pair.public()));
// `msg` and `sig` don't match
let msg = pezsp_crypto_hashing::blake2_256(b"this is a different message");
assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public()));
}
#[test]
fn recover_prehashed_works() {
let (pair, _, _) = Pair::generate_with_phrase(Some("password"));
// recovered key matches signing key
let msg = pezsp_crypto_hashing::blake2_256(b"this should be hashed");
let sig = pair.sign_prehashed(&msg);
let key = sig.recover_prehashed(&msg).unwrap();
assert_eq!(pair.public(), key);
// recovered key is useable
assert!(Pair::verify_prehashed(&sig, &msg, &key));
// recovered key and signing key don't match
let msg = pezsp_crypto_hashing::blake2_256(b"this is a different message");
let key = sig.recover_prehashed(&msg).unwrap();
assert_ne!(pair.public(), key);
}
#[test]
fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert_eq!(
Pair::verify_proof_of_possession(owner, &proof_of_possession, &other_pair.public()),
false
);
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
}
+359
View File
@@ -0,0 +1,359 @@
// 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.
//! Simple Ed25519 API.
use crate::{
crypto::{
ByteArray, CryptoType, CryptoTypeId, DeriveError, DeriveJunction, Pair as TraitPair,
PublicBytes, SecretStringError, SignatureBytes,
},
proof_of_possession::NonAggregatable,
};
use ed25519_zebra::{SigningKey, VerificationKey};
use alloc::vec::Vec;
/// An identifier used to match public keys against ed25519 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ed25");
/// The byte length of public key
pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 32;
/// The byte length of signature
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
/// A secret seed. It's not called a "secret key" because ring doesn't expose the secret keys
/// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we
/// will need it later (such as for HDKD).
type Seed = [u8; 32];
#[doc(hidden)]
pub struct Ed25519Tag;
/// A public key.
pub type Public = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, Ed25519Tag>;
/// A signature.
pub type Signature = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, Ed25519Tag>;
/// Proof of Possession is the same as Signature for ed25519
pub type ProofOfPossession = Signature;
/// A key pair.
#[derive(Copy, Clone)]
pub struct Pair {
public: VerificationKey,
secret: SigningKey,
}
/// Derive a single hard junction.
fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed {
use codec::Encode;
("Ed25519HDKD", secret_seed, cc).using_encoded(pezsp_crypto_hashing::blake2_256)
}
impl TraitPair for Pair {
type Public = Public;
type Seed = Seed;
type Signature = Signature;
type ProofOfPossession = ProofOfPossession;
/// Make a new key pair from secret seed material. The slice must be 32 bytes long or it
/// will return `None`.
///
/// You should never need to use this; generate(), generate_with_phrase
fn from_seed_slice(seed_slice: &[u8]) -> Result<Pair, SecretStringError> {
let secret =
SigningKey::try_from(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?;
let public = VerificationKey::from(&secret);
Ok(Pair { secret, public })
}
/// Derive a child key from a series of given junctions.
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
_seed: Option<Seed>,
) -> Result<(Pair, Option<Seed>), DeriveError> {
let mut acc = self.secret.into();
for j in path {
match j {
DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath),
DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc),
}
}
Ok((Self::from_seed(&acc), Some(acc)))
}
/// Get the public key.
fn public(&self) -> Public {
Public::from_raw(self.public.into())
}
/// Sign a message.
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Signature {
Signature::from_raw(self.secret.sign(message).into())
}
/// Verify a signature on a message.
///
/// Returns true if the signature is good.
fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, public: &Public) -> bool {
let Ok(public) = VerificationKey::try_from(public.as_slice()) else { return false };
let Ok(signature) = ed25519_zebra::Signature::try_from(sig.as_slice()) else {
return false;
};
public.verify(&signature, message.as_ref()).is_ok()
}
/// Return a vec filled with raw data.
fn to_raw_vec(&self) -> Vec<u8> {
self.seed().to_vec()
}
}
impl Pair {
/// Get the seed for this key.
pub fn seed(&self) -> Seed {
self.secret.into()
}
/// Exactly as `from_string` except that if no matches are found then, the the first 32
/// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey.
#[cfg(feature = "std")]
pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Pair {
Self::from_string(s, password_override).unwrap_or_else(|_| {
let mut padded_seed: Seed = [b' '; 32];
let len = s.len().min(32);
padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]);
Self::from_seed(&padded_seed)
})
}
}
impl CryptoType for Public {
type Pair = Pair;
}
impl CryptoType for Signature {
type Pair = Pair;
}
impl CryptoType for Pair {
type Pair = Pair;
}
impl NonAggregatable for Pair {}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
use crate::{
crypto::DEV_PHRASE,
proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier},
};
use serde_json;
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
.unwrap()
.public(),
);
}
#[test]
fn seed_and_derive_should_work() {
let seed = array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
);
let pair = Pair::from_seed(&seed);
assert_eq!(pair.seed(), seed);
let path = vec![DeriveJunction::Hard([0u8; 32])];
let derived = pair.derive(path.into_iter(), None).ok().unwrap().0;
assert_eq!(
derived.seed(),
array_bytes::hex2array_unchecked::<_, 32>(
"ede3354e133f9c8e337ddd6ee5415ed4b4ffe5fc7d21e933f4930a3730e5b21c"
)
);
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string() {
let (pair, phrase, seed) = Pair::generate_with_phrase(None);
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
}
#[test]
fn test_vector_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let public = pair.public();
assert_eq!(
public,
Public::from_raw(array_bytes::hex2array_unchecked(
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
))
);
let message = b"";
let signature = array_bytes::hex2array_unchecked("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
let signature = Signature::from_raw(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn test_vector_by_string_should_work() {
let pair = Pair::from_string(
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
None,
)
.unwrap();
let public = pair.public();
assert_eq!(
public,
Public::from_raw(array_bytes::hex2array_unchecked(
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
))
);
let message = b"";
let signature = array_bytes::hex2array_unchecked("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b");
let signature = Signature::from_raw(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn generated_pair_should_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, b"Something else", &public));
}
#[test]
fn seeded_pair_should_work() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
assert_eq!(
public,
Public::from_raw(array_bytes::hex2array_unchecked(
"2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee"
))
);
let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000");
let signature = pair.sign(&message[..]);
println!("Correct signature: {:?}", signature);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, "Other message", &public));
}
#[test]
fn generate_with_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(None);
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn generate_with_password_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn password_does_something() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_ne!(pair1.public(), pair2.public());
assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec());
}
#[test]
fn ss58check_roundtrip_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let s = public.to_ss58check();
println!("Correct: {}", s);
let cmp = Public::from_ss58check(&s).unwrap();
assert_eq!(cmp, public);
}
#[test]
fn signature_serialization_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let message = b"Something important";
let signature = pair.sign(&message[..]);
let serialized_signature = serde_json::to_string(&signature).unwrap();
// Signature is 64 bytes, so 128 chars + 2 quote chars
assert_eq!(serialized_signature.len(), 130);
let signature = serde_json::from_str(&serialized_signature).unwrap();
assert!(Pair::verify(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_doesnt_panic() {
fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
serde_json::from_str(text)
}
assert!(deserialize_signature("Not valid json.").is_err());
assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
// Poorly-sized
assert!(deserialize_signature("\"abc123\"").is_err());
}
#[test]
fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert_eq!(
Pair::verify_proof_of_possession(owner, &proof_of_possession, &other_pair.public()),
false
);
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
}
+120
View File
@@ -0,0 +1,120 @@
// 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.
//! A fixed hash type.
pub use primitive_types::{H160, H256, H512};
/// Hash conversion. Used to convert between unbound associated hash types in traits,
/// implemented by the same hash type.
/// Panics if used to convert between different hash types.
pub fn convert_hash<H1: Default + AsMut<[u8]>, H2: AsRef<[u8]>>(src: &H2) -> H1 {
let mut dest = H1::default();
assert_eq!(dest.as_mut().len(), src.as_ref().len());
dest.as_mut().copy_from_slice(src.as_ref());
dest
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_h160() {
let tests = vec![
(Default::default(), "0x0000000000000000000000000000000000000000"),
(H160::from_low_u64_be(2), "0x0000000000000000000000000000000000000002"),
(H160::from_low_u64_be(15), "0x000000000000000000000000000000000000000f"),
(H160::from_low_u64_be(16), "0x0000000000000000000000000000000000000010"),
(H160::from_low_u64_be(1_000), "0x00000000000000000000000000000000000003e8"),
(H160::from_low_u64_be(100_000), "0x00000000000000000000000000000000000186a0"),
(H160::from_low_u64_be(u64::MAX), "0x000000000000000000000000ffffffffffffffff"),
];
for (number, expected) in tests {
assert_eq!(
format!("{:?}", expected),
serde_json::to_string_pretty(&number).expect("Json pretty print failed")
);
assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap());
}
}
#[test]
fn test_h256() {
let tests = vec![
(
Default::default(),
"0x0000000000000000000000000000000000000000000000000000000000000000",
),
(
H256::from_low_u64_be(2),
"0x0000000000000000000000000000000000000000000000000000000000000002",
),
(
H256::from_low_u64_be(15),
"0x000000000000000000000000000000000000000000000000000000000000000f",
),
(
H256::from_low_u64_be(16),
"0x0000000000000000000000000000000000000000000000000000000000000010",
),
(
H256::from_low_u64_be(1_000),
"0x00000000000000000000000000000000000000000000000000000000000003e8",
),
(
H256::from_low_u64_be(100_000),
"0x00000000000000000000000000000000000000000000000000000000000186a0",
),
(
H256::from_low_u64_be(u64::MAX),
"0x000000000000000000000000000000000000000000000000ffffffffffffffff",
),
];
for (number, expected) in tests {
assert_eq!(
format!("{:?}", expected),
serde_json::to_string_pretty(&number).expect("Json pretty print failed")
);
assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap());
}
}
#[test]
fn test_invalid() {
assert!(serde_json::from_str::<H256>(
"\"0x000000000000000000000000000000000000000000000000000000000000000\""
)
.unwrap_err()
.is_data());
assert!(serde_json::from_str::<H256>(
"\"0x000000000000000000000000000000000000000000000000000000000000000g\""
)
.unwrap_err()
.is_data());
assert!(serde_json::from_str::<H256>(
"\"0x00000000000000000000000000000000000000000000000000000000000000000\""
)
.unwrap_err()
.is_data());
assert!(serde_json::from_str::<H256>("\"\"").unwrap_err().is_data());
assert!(serde_json::from_str::<H256>("\"0\"").unwrap_err().is_data());
assert!(serde_json::from_str::<H256>("\"10\"").unwrap_err().is_data());
}
}
+58
View File
@@ -0,0 +1,58 @@
// 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.
//! Bizinikiwi Blake2b Hasher implementation
pub mod blake2 {
use crate::hash::H256;
use hash256_std_hasher::Hash256StdHasher;
use hash_db::Hasher;
/// Concrete implementation of Hasher using Blake2b 256-bit hashes
#[derive(Debug)]
pub struct Blake2Hasher;
impl Hasher for Blake2Hasher {
type Out = H256;
type StdHasher = Hash256StdHasher;
const LENGTH: usize = 32;
fn hash(x: &[u8]) -> Self::Out {
pezsp_crypto_hashing::blake2_256(x).into()
}
}
}
pub mod keccak {
use crate::hash::H256;
use hash256_std_hasher::Hash256StdHasher;
use hash_db::Hasher;
/// Concrete implementation of Hasher using Keccak 256-bit hashes
#[derive(Debug)]
pub struct KeccakHasher;
impl Hasher for KeccakHasher {
type Out = H256;
type StdHasher = Hash256StdHasher;
const LENGTH: usize = 32;
fn hash(x: &[u8]) -> Self::Out {
pezsp_crypto_hashing::keccak_256(x).into()
}
}
}
@@ -0,0 +1,120 @@
// 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.
//! Wrapper type for byte collections that outputs hex.
/// Simple wrapper to display hex representation of bytes.
pub struct HexDisplay<'a>(&'a [u8]);
impl<'a> HexDisplay<'a> {
/// Create new instance that will display `d` as a hex string when displayed.
pub fn from<R: AsBytesRef>(d: &'a R) -> Self {
HexDisplay(d.as_bytes_ref())
}
}
impl<'a> core::fmt::Display for HexDisplay<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
if self.0.len() < 1027 {
for byte in self.0 {
f.write_fmt(format_args!("{:02x}", byte))?;
}
} else {
for byte in &self.0[0..512] {
f.write_fmt(format_args!("{:02x}", byte))?;
}
f.write_str("...")?;
for byte in &self.0[self.0.len() - 512..] {
f.write_fmt(format_args!("{:02x}", byte))?;
}
}
Ok(())
}
}
impl<'a> core::fmt::Debug for HexDisplay<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
for byte in self.0 {
f.write_fmt(format_args!("{:02x}", byte))?;
}
Ok(())
}
}
/// Simple trait to transform various types to `&[u8]`
pub trait AsBytesRef {
/// Transform `self` into `&[u8]`.
fn as_bytes_ref(&self) -> &[u8];
}
impl AsBytesRef for &[u8] {
fn as_bytes_ref(&self) -> &[u8] {
self
}
}
impl AsBytesRef for [u8] {
fn as_bytes_ref(&self) -> &[u8] {
self
}
}
impl AsBytesRef for alloc::vec::Vec<u8> {
fn as_bytes_ref(&self) -> &[u8] {
self
}
}
impl AsBytesRef for pezsp_storage::StorageKey {
fn as_bytes_ref(&self) -> &[u8] {
self.as_ref()
}
}
macro_rules! impl_non_endians {
( $( $t:ty ),* ) => { $(
impl AsBytesRef for $t {
fn as_bytes_ref(&self) -> &[u8] { &self[..] }
}
)* }
}
impl_non_endians!(
[u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], [u8; 10], [u8; 12],
[u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], [u8; 48], [u8; 56],
[u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128], [u8; 144], [u8; 177]
);
/// Format into ASCII + # + hex, suitable for storage key preimages.
#[cfg(feature = "std")]
pub fn ascii_format(asciish: &[u8]) -> String {
let mut r = String::new();
let mut latch = false;
for c in asciish {
match (latch, *c) {
(false, 32..=127) => r.push(*c as char),
_ => {
if !latch {
r.push('#');
latch = true;
}
r.push_str(&format!("{:02x}", *c));
},
}
}
r
}
+537
View File
@@ -0,0 +1,537 @@
// 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.
//! Shareable Bizinikiwi types.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
/// Initialize a key-value collection from array.
///
/// Creates a vector of given pairs and calls `collect` on the iterator from it.
/// Can be used to create a `HashMap`.
#[macro_export]
macro_rules! map {
($( $name:expr => $value:expr ),* $(,)? ) => (
vec![ $( ( $name, $value ) ),* ].into_iter().collect()
);
}
use alloc::vec::Vec;
#[doc(hidden)]
pub use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::ops::Deref;
use scale_info::TypeInfo;
#[cfg(feature = "serde")]
pub use serde;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use pezsp_debug_derive::RuntimeDebug;
#[cfg(feature = "serde")]
pub use impl_serde::serialize as bytes;
#[deprecated(
since = "27.0.0",
note = "`sp-crypto-hashing` re-exports will be removed after June 2024. Use `sp-crypto-hashing` instead."
)]
pub use pezsp_crypto_hashing::{self as hashing, *};
pub mod const_hex2array;
pub mod crypto;
pub mod hexdisplay;
pub use paste;
mod address_uri;
pub mod defer;
pub mod hash;
#[cfg(not(bizinikiwi_runtime))]
mod hasher;
pub mod offchain;
pub mod proof_of_possession;
pub mod testing;
#[cfg(not(bizinikiwi_runtime))]
pub mod traits;
pub mod uint;
#[cfg(feature = "bandersnatch-experimental")]
pub mod bandersnatch;
#[cfg(feature = "bls-experimental")]
pub mod bls;
pub mod crypto_bytes;
pub mod ecdsa;
pub mod ed25519;
pub mod paired_crypto;
pub mod sr25519;
#[cfg(feature = "bls-experimental")]
pub use bls::{bls377, bls381};
#[cfg(feature = "bls-experimental")]
pub use paired_crypto::{ecdsa_bls377, ecdsa_bls381};
pub use self::{
hash::{convert_hash, H160, H256, H512},
uint::{U256, U512},
};
pub use crypto::{ByteArray, DeriveJunction, Pair, Public};
#[cfg(not(bizinikiwi_runtime))]
pub use self::hasher::blake2::Blake2Hasher;
#[cfg(not(bizinikiwi_runtime))]
pub use self::hasher::keccak::KeccakHasher;
pub use hash_db::Hasher;
pub use bounded_collections as bounded;
#[cfg(feature = "std")]
pub use bounded_collections::{bounded_btree_map, bounded_vec};
pub use bounded_collections::{
parameter_types, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstInt,
ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, ConstUint, Get, GetDefault, TryCollect,
TypedGet,
};
pub use pezsp_storage as storage;
#[doc(hidden)]
pub use pezsp_std;
/// Hex-serialized shim for `Vec<u8>`.
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, Hash, PartialOrd, Ord))]
pub struct Bytes(#[cfg_attr(feature = "serde", serde(with = "bytes"))] pub Vec<u8>);
impl From<Vec<u8>> for Bytes {
fn from(s: Vec<u8>) -> Self {
Bytes(s)
}
}
impl From<OpaqueMetadata> for Bytes {
fn from(s: OpaqueMetadata) -> Self {
Bytes(s.0)
}
}
impl Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0[..]
}
}
impl codec::WrapperTypeEncode for Bytes {}
impl codec::WrapperTypeDecode for Bytes {
type Wrapped = Vec<u8>;
}
#[cfg(feature = "std")]
impl alloc::str::FromStr for Bytes {
type Err = bytes::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
bytes::from_hex(s).map(Bytes)
}
}
/// Stores the encoded `RuntimeMetadata` for the native side as opaque type.
#[derive(Encode, Decode, PartialEq, TypeInfo)]
pub struct OpaqueMetadata(Vec<u8>);
impl OpaqueMetadata {
/// Creates a new instance with the given metadata blob.
pub fn new(metadata: Vec<u8>) -> Self {
OpaqueMetadata(metadata)
}
}
impl Deref for OpaqueMetadata {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Simple blob to hold a `PeerId` without committing to its format.
#[derive(
Default,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
DecodeWithMemTracking,
RuntimeDebug,
TypeInfo,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OpaquePeerId(pub Vec<u8>);
impl OpaquePeerId {
/// Create new `OpaquePeerId`
pub fn new(vec: Vec<u8>) -> Self {
OpaquePeerId(vec)
}
}
/// Provide a simple 4 byte identifier for a type.
pub trait TypeId {
/// Simple 4 byte identifier.
const TYPE_ID: [u8; 4];
}
/// A log level matching the one from `log` crate.
///
/// Used internally by `pezsp_io::logging::log` method.
#[derive(Copy, Clone)]
pub enum RuntimeInterfaceLogLevel {
/// `Error` log level.
Error = 0_isize,
/// `Warn` log level.
Warn = 1_isize,
/// `Info` log level.
Info = 2_isize,
/// `Debug` log level.
Debug = 3_isize,
/// `Trace` log level.
Trace = 4_isize,
}
impl TryFrom<u8> for RuntimeInterfaceLogLevel {
type Error = ();
fn try_from(value: u8) -> Result<Self, ()> {
match value {
0 => Ok(Self::Error),
1 => Ok(Self::Warn),
2 => Ok(Self::Info),
3 => Ok(Self::Debug),
4 => Ok(Self::Trace),
_ => Err(()),
}
}
}
impl From<RuntimeInterfaceLogLevel> for u8 {
fn from(value: RuntimeInterfaceLogLevel) -> Self {
value as Self
}
}
impl From<u32> for RuntimeInterfaceLogLevel {
fn from(val: u32) -> Self {
match val {
x if x == RuntimeInterfaceLogLevel::Warn as u32 => RuntimeInterfaceLogLevel::Warn,
x if x == RuntimeInterfaceLogLevel::Info as u32 => RuntimeInterfaceLogLevel::Info,
x if x == RuntimeInterfaceLogLevel::Debug as u32 => RuntimeInterfaceLogLevel::Debug,
x if x == RuntimeInterfaceLogLevel::Trace as u32 => RuntimeInterfaceLogLevel::Trace,
_ => RuntimeInterfaceLogLevel::Error,
}
}
}
impl From<log::Level> for RuntimeInterfaceLogLevel {
fn from(l: log::Level) -> Self {
use log::Level::*;
match l {
Error => Self::Error,
Warn => Self::Warn,
Info => Self::Info,
Debug => Self::Debug,
Trace => Self::Trace,
}
}
}
impl From<RuntimeInterfaceLogLevel> for log::Level {
fn from(l: RuntimeInterfaceLogLevel) -> Self {
use self::RuntimeInterfaceLogLevel::*;
match l {
Error => Self::Error,
Warn => Self::Warn,
Info => Self::Info,
Debug => Self::Debug,
Trace => Self::Trace,
}
}
}
/// Log level filter that expresses which log levels should be filtered.
///
/// This enum matches the [`log::LevelFilter`] enum.
#[derive(Encode, Decode, Copy, Clone)]
pub enum LogLevelFilter {
/// `Off` log level filter.
Off = 0_isize,
/// `Error` log level filter.
Error = 1_isize,
/// `Warn` log level filter.
Warn = 2_isize,
/// `Info` log level filter.
Info = 3_isize,
/// `Debug` log level filter.
Debug = 4_isize,
/// `Trace` log level filter.
Trace = 5_isize,
}
impl TryFrom<u8> for LogLevelFilter {
type Error = ();
fn try_from(value: u8) -> Result<Self, ()> {
match value {
0 => Ok(Self::Off),
1 => Ok(Self::Error),
2 => Ok(Self::Warn),
3 => Ok(Self::Info),
4 => Ok(Self::Debug),
5 => Ok(Self::Trace),
_ => Err(()),
}
}
}
impl From<LogLevelFilter> for u8 {
fn from(value: LogLevelFilter) -> Self {
value as Self
}
}
impl From<LogLevelFilter> for log::LevelFilter {
fn from(l: LogLevelFilter) -> Self {
use self::LogLevelFilter::*;
match l {
Off => Self::Off,
Error => Self::Error,
Warn => Self::Warn,
Info => Self::Info,
Debug => Self::Debug,
Trace => Self::Trace,
}
}
}
impl From<log::LevelFilter> for LogLevelFilter {
fn from(l: log::LevelFilter) -> Self {
use log::LevelFilter::*;
match l {
Off => Self::Off,
Error => Self::Error,
Warn => Self::Warn,
Info => Self::Info,
Debug => Self::Debug,
Trace => Self::Trace,
}
}
}
/// Encodes the given value into a buffer and returns the pointer and the length as a single `u64`.
///
/// When Bizinikiwi calls into Wasm it expects a fixed signature for functions exported
/// from the Wasm blob. The return value of this signature is always a `u64`.
/// This `u64` stores the pointer to the encoded return value and the length of this encoded value.
/// The low `32bits` are reserved for the pointer, followed by `32bit` for the length.
#[cfg(not(feature = "std"))]
pub fn to_bizinikiwi_wasm_fn_return_value(value: &impl Encode) -> u64 {
let encoded = value.encode();
let ptr = encoded.as_ptr() as u64;
let length = encoded.len() as u64;
let res = ptr | (length << 32);
// Leak the output vector to avoid it being freed.
// This is fine in a WASM context since the heap
// will be discarded after the call.
core::mem::forget(encoded);
res
}
/// The void type - it cannot exist.
// Oh rust, you crack me up...
#[derive(
Clone,
Decode,
DecodeWithMemTracking,
Encode,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub enum Void {}
/// Macro for creating `Maybe*` marker traits.
///
/// Such a maybe-marker trait requires the given bound when `feature = std` and doesn't require
/// the bound on `no_std`. This is useful for situations where you require that a type implements
/// a certain trait with `feature = std`, but not on `no_std`.
///
/// # Example
///
/// ```
/// pezsp_core::impl_maybe_marker! {
/// /// A marker for a type that implements `Debug` when `feature = std`.
/// trait MaybeDebug: std::fmt::Debug;
/// /// A marker for a type that implements `Debug + Display` when `feature = std`.
/// trait MaybeDebugDisplay: std::fmt::Debug, std::fmt::Display;
/// }
/// ```
#[macro_export]
macro_rules! impl_maybe_marker {
(
$(
$(#[$doc:meta] )+
trait $trait_name:ident: $( $trait_bound:path ),+;
)+
) => {
$(
$(#[$doc])+
#[cfg(feature = "std")]
pub trait $trait_name: $( $trait_bound + )+ {}
#[cfg(feature = "std")]
impl<T: $( $trait_bound + )+> $trait_name for T {}
$(#[$doc])+
#[cfg(not(feature = "std"))]
pub trait $trait_name {}
#[cfg(not(feature = "std"))]
impl<T> $trait_name for T {}
)+
}
}
/// Macro for creating `Maybe*` marker traits.
///
/// Such a maybe-marker trait requires the given bound when either `feature = std` or `feature =
/// serde` is activated.
///
/// # Example
///
/// ```
/// pezsp_core::impl_maybe_marker_std_or_serde! {
/// /// A marker for a type that implements `Debug` when `feature = serde` or `feature = std`.
/// trait MaybeDebug: std::fmt::Debug;
/// /// A marker for a type that implements `Debug + Display` when `feature = serde` or `feature = std`.
/// trait MaybeDebugDisplay: std::fmt::Debug, std::fmt::Display;
/// }
/// ```
#[macro_export]
macro_rules! impl_maybe_marker_std_or_serde {
(
$(
$(#[$doc:meta] )+
trait $trait_name:ident: $( $trait_bound:path ),+;
)+
) => {
$(
$(#[$doc])+
#[cfg(any(feature = "serde", feature = "std"))]
pub trait $trait_name: $( $trait_bound + )+ {}
#[cfg(any(feature = "serde", feature = "std"))]
impl<T: $( $trait_bound + )+> $trait_name for T {}
$(#[$doc])+
#[cfg(not(any(feature = "serde", feature = "std")))]
pub trait $trait_name {}
#[cfg(not(any(feature = "serde", feature = "std")))]
impl<T> $trait_name for T {}
)+
}
}
/// The maximum number of bytes that can be allocated at one time.
// The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for
// everybody.
pub const MAX_POSSIBLE_ALLOCATION: u32 = 33554432; // 2^25 bytes, 32 MiB
/// Generates a macro for checking if a certain feature is enabled.
///
/// These feature checking macros can be used to conditionally enable/disable code in a dependent
/// crate based on a feature in the crate where the macro is called.
///
/// # Example
///```
/// pezsp_core::generate_feature_enabled_macro!(check_std_is_enabled, feature = "std", $);
/// pezsp_core::generate_feature_enabled_macro!(check_std_or_serde_is_enabled, any(feature = "std", feature = "serde"), $);
///
/// // All the code passed to the macro will then conditionally compiled based on the features
/// // activated for the crate where the macro was generated.
/// check_std_is_enabled! {
/// struct StdEnabled;
/// }
///```
#[macro_export]
// We need to skip formatting this macro because of this bug:
// https://github.com/rust-lang/rustfmt/issues/5283
#[rustfmt::skip]
macro_rules! generate_feature_enabled_macro {
( $macro_name:ident, $feature_name:meta, $d:tt ) => {
$crate::paste::paste!{
///
#[cfg($feature_name)]
#[macro_export]
macro_rules! [<_ $macro_name>] {
( $d ( $d input:tt )* ) => {
$d ( $d input )*
}
}
///
#[cfg(not($feature_name))]
#[macro_export]
macro_rules! [<_ $macro_name>] {
( $d ( $d input:tt )* ) => {};
}
/// Enable/disable the given code depending on
#[doc = concat!("`", stringify!($feature_name), "`")]
/// being enabled for the crate or not.
///
/// # Example
///
/// ```nocompile
/// // Will add the code depending on the feature being enabled or not.
#[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")]
/// ```
// https://github.com/rust-lang/rust/pull/52234
pub use [<_ $macro_name>] as $macro_name;
}
};
}
#[cfg(test)]
mod tests {
use super::*;
generate_feature_enabled_macro!(if_test, test, $);
generate_feature_enabled_macro!(if_not_test, not(test), $);
#[test]
#[should_panic]
fn generate_feature_enabled_macro_panics() {
if_test!(panic!("This should panic"));
}
#[test]
fn generate_feature_enabled_macro_works() {
if_not_test!(panic!("This should not panic"));
}
}
@@ -0,0 +1,808 @@
// 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.
//! Offchain workers types
use crate::{OpaquePeerId, RuntimeDebug};
use alloc::{boxed::Box, vec::Vec};
use codec::{Decode, Encode};
use scale_info::TypeInfo;
pub use crate::crypto::KeyTypeId;
#[cfg(feature = "std")]
pub mod storage;
#[cfg(feature = "std")]
pub mod testing;
/// Persistent storage prefix used by the Offchain Worker API when creating a DB key.
pub const STORAGE_PREFIX: &[u8] = b"storage";
/// Offchain DB persistent (non-fork-aware) storage.
pub trait OffchainStorage: Clone + Send + Sync {
/// Persist a value in storage under given key and prefix.
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]);
/// Clear a storage entry under given key and prefix.
fn remove(&mut self, prefix: &[u8], key: &[u8]);
/// Retrieve a value from storage under given key and prefix.
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>>;
/// Replace the value in storage if given old_value matches the current one.
///
/// Returns `true` if the value has been set and false otherwise.
fn compare_and_set(
&mut self,
prefix: &[u8],
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool;
}
/// A type of supported crypto.
#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub enum StorageKind {
/// Persistent storage is non-revertible and not fork-aware. It means that any value
/// set by the offchain worker triggered at block `N(hash1)` is persisted even
/// if that block is reverted as non-canonical and is available for the worker
/// that is re-run at block `N(hash2)`.
/// This storage can be used by offchain workers to handle forks
/// and coordinate offchain workers running on different forks.
PERSISTENT = 0_isize,
/// Local storage is revertible and fork-aware. It means that any value
/// set by the offchain worker triggered at block `N(hash1)` is reverted
/// if that block is reverted as non-canonical and is NOT available for the worker
/// that is re-run at block `N(hash2)`.
LOCAL = 1_isize,
}
impl TryFrom<u32> for StorageKind {
type Error = ();
fn try_from(kind: u32) -> Result<Self, Self::Error> {
match kind {
e if e == u32::from(StorageKind::PERSISTENT as u8) => Ok(StorageKind::PERSISTENT),
e if e == u32::from(StorageKind::LOCAL as u8) => Ok(StorageKind::LOCAL),
_ => Err(()),
}
}
}
impl From<StorageKind> for u32 {
fn from(c: StorageKind) -> Self {
c as u32
}
}
/// Opaque type for offchain http requests.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Hash))]
pub struct HttpRequestId(pub u16);
impl From<u16> for HttpRequestId {
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<HttpRequestId> for u16 {
fn from(c: HttpRequestId) -> Self {
c.0
}
}
impl From<HttpRequestId> for u32 {
fn from(c: HttpRequestId) -> Self {
c.0 as u32
}
}
/// An error enum returned by some http methods.
#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode)]
#[repr(C)]
pub enum HttpError {
/// The requested action couldn't been completed within a deadline.
#[codec(index = 1)]
DeadlineReached = 0_isize,
/// There was an IO Error while processing the request.
#[codec(index = 2)]
IoError = 1_isize,
/// The ID of the request is invalid in this context.
#[codec(index = 3)]
Invalid = 2_isize,
}
impl TryFrom<u32> for HttpError {
type Error = ();
fn try_from(error: u32) -> Result<Self, Self::Error> {
match error {
e if e == HttpError::DeadlineReached as u8 as u32 => Ok(HttpError::DeadlineReached),
e if e == HttpError::IoError as u8 as u32 => Ok(HttpError::IoError),
e if e == HttpError::Invalid as u8 as u32 => Ok(HttpError::Invalid),
_ => Err(()),
}
}
}
impl From<HttpError> for u32 {
fn from(c: HttpError) -> Self {
c as u8 as u32
}
}
/// Status of the HTTP request
#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode)]
pub enum HttpRequestStatus {
/// Deadline was reached while we waited for this request to finish.
///
/// Note the deadline is controlled by the calling part, it not necessarily
/// means that the request has timed out.
DeadlineReached,
/// An error has occurred during the request, for example a timeout or the
/// remote has closed our socket.
///
/// The request is now considered destroyed. To retry the request you need
/// to construct it again.
IoError,
/// The passed ID is invalid in this context.
Invalid,
/// The request has finished with given status code.
Finished(u16),
}
impl From<HttpRequestStatus> for u32 {
fn from(status: HttpRequestStatus) -> Self {
match status {
HttpRequestStatus::Invalid => 0,
HttpRequestStatus::DeadlineReached => 10,
HttpRequestStatus::IoError => 20,
HttpRequestStatus::Finished(code) => u32::from(code),
}
}
}
impl TryFrom<u32> for HttpRequestStatus {
type Error = ();
fn try_from(status: u32) -> Result<Self, Self::Error> {
match status {
0 => Ok(HttpRequestStatus::Invalid),
10 => Ok(HttpRequestStatus::DeadlineReached),
20 => Ok(HttpRequestStatus::IoError),
100..=999 => u16::try_from(status).map(HttpRequestStatus::Finished).map_err(|_| ()),
_ => Err(()),
}
}
}
/// A blob to hold information about the local node's network state
/// without committing to its format.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(Default))]
pub struct OpaqueNetworkState {
/// PeerId of the local node in SCALE encoded.
pub peer_id: OpaquePeerId,
/// List of addresses the node knows it can be reached as.
pub external_addresses: Vec<OpaqueMultiaddr>,
}
/// Simple blob to hold a `Multiaddr` without committing to its format.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct OpaqueMultiaddr(pub Vec<u8>);
impl OpaqueMultiaddr {
/// Create new `OpaqueMultiaddr`
pub fn new(vec: Vec<u8>) -> Self {
OpaqueMultiaddr(vec)
}
}
/// Opaque timestamp type
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, Encode, Decode)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Timestamp(u64);
impl From<u64> for Timestamp {
fn from(value: u64) -> Self {
Self(value)
}
}
impl From<Timestamp> for u64 {
fn from(value: Timestamp) -> u64 {
value.0
}
}
/// Duration type
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, Encode, Decode)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Duration(u64);
impl Duration {
/// Create new duration representing given number of milliseconds.
pub const fn from_millis(millis: u64) -> Self {
Duration(millis)
}
/// Returns number of milliseconds this Duration represents.
pub fn millis(&self) -> u64 {
self.0
}
}
impl Timestamp {
/// Creates new `Timestamp` given unix timestamp in milliseconds.
pub fn from_unix_millis(millis: u64) -> Self {
Timestamp(millis)
}
/// Increase the timestamp by given `Duration`.
pub fn add(&self, duration: Duration) -> Timestamp {
Timestamp(self.0.saturating_add(duration.0))
}
/// Decrease the timestamp by given `Duration`
pub fn sub(&self, duration: Duration) -> Timestamp {
Timestamp(self.0.saturating_sub(duration.0))
}
/// Returns a saturated difference (Duration) between two Timestamps.
pub fn diff(&self, other: &Self) -> Duration {
Duration(self.0.saturating_sub(other.0))
}
/// Return number of milliseconds since UNIX epoch.
pub fn unix_millis(&self) -> u64 {
self.0
}
}
bitflags::bitflags! {
/// Execution context extra capabilities.
pub struct Capabilities: u32 {
/// External http calls.
const HTTP = 1 << 0;
/// Keystore access.
const KEYSTORE = 1 << 2;
/// Randomness source.
const RANDOMNESS = 1 << 3;
/// Access to opaque network state.
const NETWORK_STATE = 1 << 4;
/// Access to offchain worker DB (read only).
const OFFCHAIN_DB_READ = 1 << 5;
/// Access to offchain worker DB (writes).
const OFFCHAIN_DB_WRITE = 1 << 6;
/// Manage the authorized nodes
const NODE_AUTHORIZATION = 1 << 7;
/// Access time related functionality
const TIME = 1 << 8;
}
}
/// An extended externalities for offchain workers.
pub trait Externalities: Send {
/// Returns if the local node is a potential validator.
///
/// Even if this function returns `true`, it does not mean that any keys are configured
/// and that the validator is registered in the chain.
fn is_validator(&self) -> bool;
/// Returns information about the local node's network state.
fn network_state(&self) -> Result<OpaqueNetworkState, ()>;
/// Returns current UNIX timestamp (in millis)
fn timestamp(&mut self) -> Timestamp;
/// Pause the execution until `deadline` is reached.
fn sleep_until(&mut self, deadline: Timestamp);
/// Returns a random seed.
///
/// This is a truly random non deterministic seed generated by host environment.
/// Obviously fine in the off-chain worker context.
fn random_seed(&mut self) -> [u8; 32];
/// Initiates a http request given HTTP verb and the URL.
///
/// Meta is a future-reserved field containing additional, parity-scale-codec encoded
/// parameters. Returns the id of newly started request.
///
/// Returns an error if:
/// - No new request identifier could be allocated.
/// - The method or URI contain invalid characters.
fn http_request_start(
&mut self,
method: &str,
uri: &str,
meta: &[u8],
) -> Result<HttpRequestId, ()>;
/// Append header to the request.
///
/// Calling this function multiple times with the same header name continues appending new
/// headers. In other words, headers are never replaced.
///
/// Returns an error if:
/// - The request identifier is invalid.
/// - You have called `http_request_write_body` on that request.
/// - The name or value contain invalid characters.
///
/// An error doesn't poison the request, and you can continue as if the call had never been
/// made.
fn http_request_add_header(
&mut self,
request_id: HttpRequestId,
name: &str,
value: &str,
) -> Result<(), ()>;
/// Write a chunk of request body.
///
/// Calling this function with a non-empty slice may or may not start the
/// HTTP request. Calling this function with an empty chunks finalizes the
/// request and always starts it. It is no longer valid to write more data
/// afterwards.
/// Passing `None` as deadline blocks forever.
///
/// Returns an error if:
/// - The request identifier is invalid.
/// - `http_response_wait` has already been called on this request.
/// - The deadline is reached.
/// - An I/O error has happened, for example the remote has closed our request. The request is
/// then considered invalid.
fn http_request_write_body(
&mut self,
request_id: HttpRequestId,
chunk: &[u8],
deadline: Option<Timestamp>,
) -> Result<(), HttpError>;
/// Block and wait for the responses for given requests.
///
/// Returns a vector of request statuses (the len is the same as ids).
/// Note that if deadline is not provided the method will block indefinitely,
/// otherwise unready responses will produce `DeadlineReached` status.
///
/// If a response returns an `IoError`, it is then considered destroyed.
/// Its id is then invalid.
///
/// Passing `None` as deadline blocks forever.
fn http_response_wait(
&mut self,
ids: &[HttpRequestId],
deadline: Option<Timestamp>,
) -> Vec<HttpRequestStatus>;
/// Read all response headers.
///
/// Returns a vector of pairs `(HeaderKey, HeaderValue)`.
///
/// Dispatches the request if it hasn't been done yet. It is no longer
/// valid to modify the headers or write data to the request.
///
/// Returns an empty list if the identifier is unknown/invalid, hasn't
/// received a response, or has finished.
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)>;
/// Read a chunk of body response to given buffer.
///
/// Dispatches the request if it hasn't been done yet. It is no longer
/// valid to modify the headers or write data to the request.
///
/// Returns the number of bytes written or an error in case a deadline
/// is reached or server closed the connection.
/// Passing `None` as a deadline blocks forever.
///
/// If `Ok(0)` or `Err(IoError)` is returned, the request is considered
/// destroyed. Doing another read or getting the response's headers, for
/// example, is then invalid.
///
/// Returns an error if:
/// - The request identifier is invalid.
/// - The deadline is reached.
/// - An I/O error has happened, for example the remote has closed our request. The request is
/// then considered invalid.
fn http_response_read_body(
&mut self,
request_id: HttpRequestId,
buffer: &mut [u8],
deadline: Option<Timestamp>,
) -> Result<usize, HttpError>;
/// Set the authorized nodes from runtime.
///
/// In a permissioned network, the connections between nodes need to reach a
/// consensus between participants.
///
/// - `nodes`: a set of nodes which are allowed to connect for the local node.
/// each one is identified with an `OpaquePeerId`, here it just use plain bytes
/// without any encoding. Invalid `OpaquePeerId`s are silently ignored.
/// - `authorized_only`: if true, only the authorized nodes are allowed to connect,
/// otherwise unauthorized nodes can also be connected through other mechanism.
fn set_authorized_nodes(&mut self, nodes: Vec<OpaquePeerId>, authorized_only: bool);
}
impl<T: Externalities + ?Sized> Externalities for Box<T> {
fn is_validator(&self) -> bool {
(&**self).is_validator()
}
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
(&**self).network_state()
}
fn timestamp(&mut self) -> Timestamp {
(&mut **self).timestamp()
}
fn sleep_until(&mut self, deadline: Timestamp) {
(&mut **self).sleep_until(deadline)
}
fn random_seed(&mut self) -> [u8; 32] {
(&mut **self).random_seed()
}
fn http_request_start(
&mut self,
method: &str,
uri: &str,
meta: &[u8],
) -> Result<HttpRequestId, ()> {
(&mut **self).http_request_start(method, uri, meta)
}
fn http_request_add_header(
&mut self,
request_id: HttpRequestId,
name: &str,
value: &str,
) -> Result<(), ()> {
(&mut **self).http_request_add_header(request_id, name, value)
}
fn http_request_write_body(
&mut self,
request_id: HttpRequestId,
chunk: &[u8],
deadline: Option<Timestamp>,
) -> Result<(), HttpError> {
(&mut **self).http_request_write_body(request_id, chunk, deadline)
}
fn http_response_wait(
&mut self,
ids: &[HttpRequestId],
deadline: Option<Timestamp>,
) -> Vec<HttpRequestStatus> {
(&mut **self).http_response_wait(ids, deadline)
}
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
(&mut **self).http_response_headers(request_id)
}
fn http_response_read_body(
&mut self,
request_id: HttpRequestId,
buffer: &mut [u8],
deadline: Option<Timestamp>,
) -> Result<usize, HttpError> {
(&mut **self).http_response_read_body(request_id, buffer, deadline)
}
fn set_authorized_nodes(&mut self, nodes: Vec<OpaquePeerId>, authorized_only: bool) {
(&mut **self).set_authorized_nodes(nodes, authorized_only)
}
}
/// An `*Externalities` implementation with limited capabilities.
pub struct LimitedExternalities<T> {
capabilities: Capabilities,
externalities: T,
}
impl<T> LimitedExternalities<T> {
/// Create new externalities limited to given `capabilities`.
pub fn new(capabilities: Capabilities, externalities: T) -> Self {
Self { capabilities, externalities }
}
/// Check if given capability is allowed.
///
/// Panics in case it is not.
fn check(&self, capability: Capabilities, name: &'static str) {
if !self.capabilities.contains(capability) {
panic!("Accessing a forbidden API: {}. No: {:?} capability.", name, capability);
}
}
}
impl<T: Externalities> Externalities for LimitedExternalities<T> {
fn is_validator(&self) -> bool {
self.check(Capabilities::KEYSTORE, "is_validator");
self.externalities.is_validator()
}
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
self.check(Capabilities::NETWORK_STATE, "network_state");
self.externalities.network_state()
}
fn timestamp(&mut self) -> Timestamp {
self.check(Capabilities::TIME, "timestamp");
self.externalities.timestamp()
}
fn sleep_until(&mut self, deadline: Timestamp) {
self.check(Capabilities::TIME, "sleep_until");
self.externalities.sleep_until(deadline)
}
fn random_seed(&mut self) -> [u8; 32] {
self.check(Capabilities::RANDOMNESS, "random_seed");
self.externalities.random_seed()
}
fn http_request_start(
&mut self,
method: &str,
uri: &str,
meta: &[u8],
) -> Result<HttpRequestId, ()> {
self.check(Capabilities::HTTP, "http_request_start");
self.externalities.http_request_start(method, uri, meta)
}
fn http_request_add_header(
&mut self,
request_id: HttpRequestId,
name: &str,
value: &str,
) -> Result<(), ()> {
self.check(Capabilities::HTTP, "http_request_add_header");
self.externalities.http_request_add_header(request_id, name, value)
}
fn http_request_write_body(
&mut self,
request_id: HttpRequestId,
chunk: &[u8],
deadline: Option<Timestamp>,
) -> Result<(), HttpError> {
self.check(Capabilities::HTTP, "http_request_write_body");
self.externalities.http_request_write_body(request_id, chunk, deadline)
}
fn http_response_wait(
&mut self,
ids: &[HttpRequestId],
deadline: Option<Timestamp>,
) -> Vec<HttpRequestStatus> {
self.check(Capabilities::HTTP, "http_response_wait");
self.externalities.http_response_wait(ids, deadline)
}
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
self.check(Capabilities::HTTP, "http_response_headers");
self.externalities.http_response_headers(request_id)
}
fn http_response_read_body(
&mut self,
request_id: HttpRequestId,
buffer: &mut [u8],
deadline: Option<Timestamp>,
) -> Result<usize, HttpError> {
self.check(Capabilities::HTTP, "http_response_read_body");
self.externalities.http_response_read_body(request_id, buffer, deadline)
}
fn set_authorized_nodes(&mut self, nodes: Vec<OpaquePeerId>, authorized_only: bool) {
self.check(Capabilities::NODE_AUTHORIZATION, "set_authorized_nodes");
self.externalities.set_authorized_nodes(nodes, authorized_only)
}
}
#[cfg(not(bizinikiwi_runtime))]
pezsp_externalities::decl_extension! {
/// The offchain worker extension that will be registered at the Bizinikiwi externalities.
pub struct OffchainWorkerExt(Box<dyn Externalities>);
}
#[cfg(not(bizinikiwi_runtime))]
impl OffchainWorkerExt {
/// Create a new instance of `Self`.
pub fn new<O: Externalities + 'static>(offchain: O) -> Self {
Self(Box::new(offchain))
}
}
/// A externalities extension for accessing the Offchain DB.
pub trait DbExternalities: Send {
/// Sets a value in the local storage.
///
/// Note this storage is not part of the consensus, it's only accessible by
/// offchain worker tasks running on the same machine. It _is_ persisted between runs.
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]);
/// Removes a value in the local storage.
///
/// Note this storage is not part of the consensus, it's only accessible by
/// offchain worker tasks running on the same machine. It _is_ persisted between runs.
fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]);
/// Sets a value in the local storage if it matches current value.
///
/// Since multiple offchain workers may be running concurrently, to prevent
/// data races use CAS to coordinate between them.
///
/// Returns `true` if the value has been set, `false` otherwise.
///
/// Note this storage is not part of the consensus, it's only accessible by
/// offchain worker tasks running on the same machine. It _is_ persisted between runs.
fn local_storage_compare_and_set(
&mut self,
kind: StorageKind,
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool;
/// Gets a value from the local storage.
///
/// If the value does not exist in the storage `None` will be returned.
/// Note this storage is not part of the consensus, it's only accessible by
/// offchain worker tasks running on the same machine. It _is_ persisted between runs.
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>>;
}
impl<T: DbExternalities + ?Sized> DbExternalities for Box<T> {
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
(&mut **self).local_storage_set(kind, key, value)
}
fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) {
(&mut **self).local_storage_clear(kind, key)
}
fn local_storage_compare_and_set(
&mut self,
kind: StorageKind,
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
(&mut **self).local_storage_compare_and_set(kind, key, old_value, new_value)
}
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
(&mut **self).local_storage_get(kind, key)
}
}
impl<T: DbExternalities> DbExternalities for LimitedExternalities<T> {
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_set");
self.externalities.local_storage_set(kind, key, value)
}
fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) {
self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_clear");
self.externalities.local_storage_clear(kind, key)
}
fn local_storage_compare_and_set(
&mut self,
kind: StorageKind,
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
self.check(Capabilities::OFFCHAIN_DB_WRITE, "local_storage_compare_and_set");
self.externalities
.local_storage_compare_and_set(kind, key, old_value, new_value)
}
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
self.check(Capabilities::OFFCHAIN_DB_READ, "local_storage_get");
self.externalities.local_storage_get(kind, key)
}
}
#[cfg(not(bizinikiwi_runtime))]
pezsp_externalities::decl_extension! {
/// The offchain database extension that will be registered at the Bizinikiwi externalities.
pub struct OffchainDbExt(Box<dyn DbExternalities>);
}
#[cfg(not(bizinikiwi_runtime))]
impl OffchainDbExt {
/// Create a new instance of `OffchainDbExt`.
pub fn new<O: DbExternalities + 'static>(offchain: O) -> Self {
Self(Box::new(offchain))
}
}
/// Abstraction over transaction pool.
///
/// This trait is currently used within the `ExternalitiesExtension`
/// to provide offchain calls with access to the transaction pool without
/// tight coupling with any pool implementation.
#[cfg(not(bizinikiwi_runtime))]
pub trait TransactionPool {
/// Submit transaction.
///
/// The transaction will end up in the pool and be propagated to others.
fn submit_transaction(&mut self, extrinsic: Vec<u8>) -> Result<(), ()>;
}
#[cfg(not(bizinikiwi_runtime))]
pezsp_externalities::decl_extension! {
/// An externalities extension to submit transactions to the pool.
pub struct TransactionPoolExt(Box<dyn TransactionPool + Send>);
}
#[cfg(not(bizinikiwi_runtime))]
impl TransactionPoolExt {
/// Create a new instance of `TransactionPoolExt`.
pub fn new<O: TransactionPool + Send + 'static>(pool: O) -> Self {
Self(Box::new(pool))
}
}
/// Change to be applied to the offchain worker db in regards to a key.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum OffchainOverlayedChange {
/// Remove the data associated with the key
Remove,
/// Overwrite the value of an associated key
SetValue(Vec<u8>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn timestamp_ops() {
let t = Timestamp(5);
assert_eq!(t.add(Duration::from_millis(10)), Timestamp(15));
assert_eq!(t.sub(Duration::from_millis(10)), Timestamp(0));
assert_eq!(t.diff(&Timestamp(3)), Duration(2));
}
#[test]
fn capabilities() {
let none = Capabilities::empty();
let all = Capabilities::all();
let some = Capabilities::KEYSTORE | Capabilities::RANDOMNESS;
assert!(!none.contains(Capabilities::KEYSTORE));
assert!(all.contains(Capabilities::KEYSTORE));
assert!(some.contains(Capabilities::KEYSTORE));
assert!(!none.contains(Capabilities::RANDOMNESS));
assert!(all.contains(Capabilities::RANDOMNESS));
assert!(!some.contains(Capabilities::TIME));
}
}
@@ -0,0 +1,184 @@
// 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.
//! In-memory implementation of offchain workers database.
use crate::offchain::{DbExternalities, OffchainStorage, StorageKind, STORAGE_PREFIX};
use std::{
collections::hash_map::{Entry, HashMap},
iter::Iterator,
};
const LOG_TARGET: &str = "offchain-worker::storage";
/// In-memory storage for offchain workers.
#[derive(Debug, Clone, Default)]
pub struct InMemOffchainStorage {
storage: HashMap<Vec<u8>, Vec<u8>>,
}
impl InMemOffchainStorage {
/// Consume the offchain storage and iterate over all key value pairs.
pub fn into_iter(self) -> impl Iterator<Item = (Vec<u8>, Vec<u8>)> {
self.storage.into_iter()
}
/// Iterate over all key value pairs by reference.
pub fn iter(&self) -> impl Iterator<Item = (&Vec<u8>, &Vec<u8>)> {
self.storage.iter()
}
/// Remove a key and its associated value from the offchain database.
pub fn remove(&mut self, prefix: &[u8], key: &[u8]) {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.storage.remove(&key);
}
}
impl OffchainStorage for InMemOffchainStorage {
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
let key = prefix.iter().chain(key).cloned().collect();
self.storage.insert(key, value.to_vec());
}
fn remove(&mut self, prefix: &[u8], key: &[u8]) {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.storage.remove(&key);
}
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
let key: Vec<u8> = prefix.iter().chain(key).cloned().collect();
self.storage.get(&key).cloned()
}
fn compare_and_set(
&mut self,
prefix: &[u8],
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
let key = prefix.iter().chain(key).cloned().collect();
match self.storage.entry(key) {
Entry::Vacant(entry) =>
if old_value.is_none() {
entry.insert(new_value.to_vec());
true
} else {
false
},
Entry::Occupied(ref mut entry) if Some(entry.get().as_slice()) == old_value => {
entry.insert(new_value.to_vec());
true
},
_ => false,
}
}
}
fn unavailable_yet<R: Default>(name: &str) -> R {
tracing::error!(
target: LOG_TARGET,
"The {:?} API is not available for offchain workers yet. Follow \
https://github.com/pezkuwichain/kurdistan-sdk/issues/19 for details",
name
);
Default::default()
}
const LOCAL_DB: &str = "LOCAL (fork-aware) DB";
/// Offchain DB that implements [`DbExternalities`] for [`OffchainStorage`].
#[derive(Debug, Clone)]
pub struct OffchainDb<Storage> {
/// Persistent storage database.
persistent: Storage,
}
impl<Storage> OffchainDb<Storage> {
/// Create new instance of Offchain DB.
pub fn new(persistent: Storage) -> Self {
Self { persistent }
}
}
impl<Storage: OffchainStorage> DbExternalities for OffchainDb<Storage> {
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
tracing::debug!(
target: LOG_TARGET,
?kind,
key = ?array_bytes::bytes2hex("", key),
value = ?array_bytes::bytes2hex("", value),
"Write",
);
match kind {
StorageKind::PERSISTENT => self.persistent.set(STORAGE_PREFIX, key, value),
StorageKind::LOCAL => unavailable_yet(LOCAL_DB),
}
}
fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) {
tracing::debug!(
target: LOG_TARGET,
?kind,
key = ?array_bytes::bytes2hex("", key),
"Clear",
);
match kind {
StorageKind::PERSISTENT => self.persistent.remove(STORAGE_PREFIX, key),
StorageKind::LOCAL => unavailable_yet(LOCAL_DB),
}
}
fn local_storage_compare_and_set(
&mut self,
kind: StorageKind,
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
tracing::debug!(
target: LOG_TARGET,
?kind,
key = ?array_bytes::bytes2hex("", key),
new_value = ?array_bytes::bytes2hex("", new_value),
old_value = ?old_value.as_ref().map(|s| array_bytes::bytes2hex("", s)),
"CAS",
);
match kind {
StorageKind::PERSISTENT =>
self.persistent.compare_and_set(STORAGE_PREFIX, key, old_value, new_value),
StorageKind::LOCAL => unavailable_yet(LOCAL_DB),
}
}
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
let result = match kind {
StorageKind::PERSISTENT => self.persistent.get(STORAGE_PREFIX, key),
StorageKind::LOCAL => unavailable_yet(LOCAL_DB),
};
tracing::debug!(
target: LOG_TARGET,
?kind,
key = ?array_bytes::bytes2hex("", key),
result = ?result.as_ref().map(|s| array_bytes::bytes2hex("", s)),
"Read",
);
result
}
}
@@ -0,0 +1,433 @@
// 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.
//! Utilities for offchain calls testing.
//!
//! Namely all ExecutionExtensions that allow mocking
//! the extra APIs.
use crate::{
offchain::{
self, storage::InMemOffchainStorage, HttpError, HttpRequestId as RequestId,
HttpRequestStatus as RequestStatus, OffchainOverlayedChange, OffchainStorage,
OpaqueNetworkState, StorageKind, Timestamp, TransactionPool,
},
OpaquePeerId,
};
use std::{
collections::{BTreeMap, VecDeque},
sync::Arc,
};
use parking_lot::RwLock;
/// Pending request.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct PendingRequest {
/// HTTP method
pub method: String,
/// URI
pub uri: String,
/// Encoded Metadata
pub meta: Vec<u8>,
/// Request headers
pub headers: Vec<(String, String)>,
/// Request body
pub body: Vec<u8>,
/// Has the request been sent already.
pub sent: bool,
/// Response body
pub response: Option<Vec<u8>>,
/// Number of bytes already read from the response body.
pub read: usize,
/// Response headers
pub response_headers: Vec<(String, String)>,
}
/// Sharable "persistent" offchain storage for test.
#[derive(Debug, Clone, Default)]
pub struct TestPersistentOffchainDB {
persistent: Arc<RwLock<InMemOffchainStorage>>,
}
impl TestPersistentOffchainDB {
const PREFIX: &'static [u8] = b"";
/// Create a new and empty offchain storage db for persistent items
pub fn new() -> Self {
Self { persistent: Arc::new(RwLock::new(InMemOffchainStorage::default())) }
}
/// Apply a set of off-chain changes directly to the test backend
pub fn apply_offchain_changes(
&mut self,
changes: impl Iterator<Item = ((Vec<u8>, Vec<u8>), OffchainOverlayedChange)>,
) {
let mut me = self.persistent.write();
for ((_prefix, key), value_operation) in changes {
match value_operation {
OffchainOverlayedChange::SetValue(val) =>
me.set(Self::PREFIX, key.as_slice(), val.as_slice()),
OffchainOverlayedChange::Remove => me.remove(Self::PREFIX, key.as_slice()),
}
}
}
/// Retrieve a key from the test backend.
pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
OffchainStorage::get(self, Self::PREFIX, key)
}
}
impl OffchainStorage for TestPersistentOffchainDB {
fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
self.persistent.write().set(prefix, key, value);
}
fn remove(&mut self, prefix: &[u8], key: &[u8]) {
self.persistent.write().remove(prefix, key);
}
fn get(&self, prefix: &[u8], key: &[u8]) -> Option<Vec<u8>> {
self.persistent.read().get(prefix, key)
}
fn compare_and_set(
&mut self,
prefix: &[u8],
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
self.persistent.write().compare_and_set(prefix, key, old_value, new_value)
}
}
/// Internal state of the externalities.
///
/// This can be used in tests to respond or assert stuff about interactions.
#[derive(Debug, Default)]
pub struct OffchainState {
/// A list of pending requests.
pub requests: BTreeMap<RequestId, PendingRequest>,
// Queue of requests that the test is expected to perform (in order).
expected_requests: VecDeque<PendingRequest>,
/// Persistent local storage
pub persistent_storage: TestPersistentOffchainDB,
/// Local storage
pub local_storage: InMemOffchainStorage,
/// A supposedly random seed.
pub seed: [u8; 32],
/// A timestamp simulating the current time.
pub timestamp: Timestamp,
}
impl OffchainState {
/// Asserts that pending request has been submitted and fills it's response.
pub fn fulfill_pending_request(
&mut self,
id: u16,
expected: PendingRequest,
response: impl Into<Vec<u8>>,
response_headers: impl IntoIterator<Item = (String, String)>,
) {
match self.requests.get_mut(&RequestId(id)) {
None => {
panic!("Missing pending request: {:?}.\n\nAll: {:?}", id, self.requests);
},
Some(req) => {
assert_eq!(*req, expected);
req.response = Some(response.into());
req.response_headers = response_headers.into_iter().collect();
},
}
}
fn fulfill_expected(&mut self, id: u16) {
if let Some(mut req) = self.expected_requests.pop_back() {
let response = req.response.take().expect("Response checked when added.");
let headers = std::mem::take(&mut req.response_headers);
self.fulfill_pending_request(id, req, response, headers);
}
}
/// Add expected HTTP request.
///
/// This method can be used to initialize expected HTTP requests and their responses
/// before running the actual code that utilizes them (for instance before calling into
/// runtime). Expected request has to be fulfilled before this struct is dropped,
/// the `response` and `response_headers` fields will be used to return results to the callers.
/// Requests are expected to be performed in the insertion order.
pub fn expect_request(&mut self, expected: PendingRequest) {
if expected.response.is_none() {
panic!("Expected request needs to have a response.");
}
self.expected_requests.push_front(expected);
}
}
impl Drop for OffchainState {
fn drop(&mut self) {
// If we panic! while we are already in a panic, the test dies with an illegal instruction.
if !self.expected_requests.is_empty() && !std::thread::panicking() {
panic!("Unfulfilled expected requests: {:?}", self.expected_requests);
}
}
}
/// Implementation of offchain externalities used for tests.
#[derive(Clone, Default, Debug)]
pub struct TestOffchainExt(pub Arc<RwLock<OffchainState>>);
impl TestOffchainExt {
/// Create new `TestOffchainExt` and a reference to the internal state.
pub fn new() -> (Self, Arc<RwLock<OffchainState>>) {
let ext = Self::default();
let state = ext.0.clone();
(ext, state)
}
/// Create new `TestOffchainExt` and a reference to the internal state.
pub fn with_offchain_db(
offchain_db: TestPersistentOffchainDB,
) -> (Self, Arc<RwLock<OffchainState>>) {
let (ext, state) = Self::new();
ext.0.write().persistent_storage = offchain_db;
(ext, state)
}
}
impl offchain::Externalities for TestOffchainExt {
fn is_validator(&self) -> bool {
true
}
fn network_state(&self) -> Result<OpaqueNetworkState, ()> {
Ok(OpaqueNetworkState { peer_id: Default::default(), external_addresses: vec![] })
}
fn timestamp(&mut self) -> Timestamp {
self.0.read().timestamp
}
fn sleep_until(&mut self, deadline: Timestamp) {
self.0.write().timestamp = deadline;
}
fn random_seed(&mut self) -> [u8; 32] {
self.0.read().seed
}
fn http_request_start(
&mut self,
method: &str,
uri: &str,
meta: &[u8],
) -> Result<RequestId, ()> {
let mut state = self.0.write();
let id = RequestId(state.requests.len() as u16);
state.requests.insert(
id,
PendingRequest {
method: method.into(),
uri: uri.into(),
meta: meta.into(),
..Default::default()
},
);
Ok(id)
}
fn http_request_add_header(
&mut self,
request_id: RequestId,
name: &str,
value: &str,
) -> Result<(), ()> {
let mut state = self.0.write();
if let Some(req) = state.requests.get_mut(&request_id) {
req.headers.push((name.into(), value.into()));
Ok(())
} else {
Err(())
}
}
fn http_request_write_body(
&mut self,
request_id: RequestId,
chunk: &[u8],
_deadline: Option<Timestamp>,
) -> Result<(), HttpError> {
let mut state = self.0.write();
let sent = {
let req = state.requests.get_mut(&request_id).ok_or(HttpError::IoError)?;
req.body.extend(chunk);
if chunk.is_empty() {
req.sent = true;
}
req.sent
};
if sent {
state.fulfill_expected(request_id.0);
}
Ok(())
}
fn http_response_wait(
&mut self,
ids: &[RequestId],
_deadline: Option<Timestamp>,
) -> Vec<RequestStatus> {
let state = self.0.read();
ids.iter()
.map(|id| match state.requests.get(id) {
Some(req) if req.response.is_none() => {
panic!("No `response` provided for request with id: {:?}", id)
},
None => RequestStatus::Invalid,
_ => RequestStatus::Finished(200),
})
.collect()
}
fn http_response_headers(&mut self, request_id: RequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
let state = self.0.read();
if let Some(req) = state.requests.get(&request_id) {
req.response_headers
.clone()
.into_iter()
.map(|(k, v)| (k.into_bytes(), v.into_bytes()))
.collect()
} else {
Default::default()
}
}
fn http_response_read_body(
&mut self,
request_id: RequestId,
buffer: &mut [u8],
_deadline: Option<Timestamp>,
) -> Result<usize, HttpError> {
let mut state = self.0.write();
if let Some(req) = state.requests.get_mut(&request_id) {
let response = req
.response
.as_mut()
.unwrap_or_else(|| panic!("No response provided for request: {:?}", request_id));
if req.read >= response.len() {
// Remove the pending request as per spec.
state.requests.remove(&request_id);
Ok(0)
} else {
let read = std::cmp::min(buffer.len(), response[req.read..].len());
buffer[0..read].copy_from_slice(&response[req.read..req.read + read]);
req.read += read;
Ok(read)
}
} else {
Err(HttpError::IoError)
}
}
fn set_authorized_nodes(&mut self, _nodes: Vec<OpaquePeerId>, _authorized_only: bool) {
unimplemented!()
}
}
impl offchain::DbExternalities for TestOffchainExt {
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
let mut state = self.0.write();
match kind {
StorageKind::LOCAL => state.local_storage.set(b"", key, value),
StorageKind::PERSISTENT => state.persistent_storage.set(b"", key, value),
};
}
fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) {
let mut state = self.0.write();
match kind {
StorageKind::LOCAL => state.local_storage.remove(b"", key),
StorageKind::PERSISTENT => state.persistent_storage.remove(b"", key),
};
}
fn local_storage_compare_and_set(
&mut self,
kind: StorageKind,
key: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
let mut state = self.0.write();
match kind {
StorageKind::LOCAL =>
state.local_storage.compare_and_set(b"", key, old_value, new_value),
StorageKind::PERSISTENT =>
state.persistent_storage.compare_and_set(b"", key, old_value, new_value),
}
}
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
let state = self.0.read();
match kind {
StorageKind::LOCAL => state.local_storage.get(TestPersistentOffchainDB::PREFIX, key),
StorageKind::PERSISTENT => state.persistent_storage.get(key),
}
}
}
/// The internal state of the fake transaction pool.
#[derive(Default)]
pub struct PoolState {
/// A vector of transactions submitted from the runtime.
pub transactions: Vec<Vec<u8>>,
}
/// Implementation of transaction pool used for test.
///
/// Note that this implementation does not verify correctness
/// of sent extrinsics. It's meant to be used in contexts
/// where an actual runtime is not known.
///
/// It's advised to write integration tests that include the
/// actual transaction pool to make sure the produced
/// transactions are valid.
#[derive(Default)]
pub struct TestTransactionPoolExt(Arc<RwLock<PoolState>>);
impl TestTransactionPoolExt {
/// Create new `TestTransactionPoolExt` and a reference to the internal state.
pub fn new() -> (Self, Arc<RwLock<PoolState>>) {
let ext = Self::default();
let state = ext.0.clone();
(ext, state)
}
}
impl TransactionPool for TestTransactionPoolExt {
fn submit_transaction(&mut self, extrinsic: Vec<u8>) -> Result<(), ()> {
self.0.write().transactions.push(extrinsic);
Ok(())
}
}
@@ -0,0 +1,781 @@
// 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.
//! API for using a pair of crypto schemes together.
use core::marker::PhantomData;
use crate::crypto::{
ByteArray, CryptoType, DeriveError, DeriveJunction, Pair as PairT, Public as PublicT,
PublicBytes, SecretStringError, Signature as SignatureT, SignatureBytes, UncheckedFrom,
};
use crate::proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier};
use alloc::vec::Vec;
/// ECDSA and BLS12-377 paired crypto scheme
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls377 {
use crate::{bls377, crypto::CryptoTypeId, ecdsa};
#[cfg(feature = "full_crypto")]
use crate::{
crypto::{Pair as PairT, UncheckedFrom},
Hasher,
};
/// An identifier used to match public keys against BLS12-377 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecb7");
const PUBLIC_KEY_LEN: usize =
ecdsa::PUBLIC_KEY_SERIALIZED_SIZE + bls377::PUBLIC_KEY_SERIALIZED_SIZE;
const SIGNATURE_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls377::SIGNATURE_SERIALIZED_SIZE;
const POP_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls377::PROOF_OF_POSSESSION_SERIALIZED_SIZE;
#[doc(hidden)]
pub struct EcdsaBls377Tag(ecdsa::EcdsaTag, bls377::Bls377Tag);
impl super::PairedCryptoSubTagBound for EcdsaBls377Tag {}
/// (ECDSA,BLS12-377) key-pair pair.
pub type Pair = super::Pair<
ecdsa::Pair,
bls377::Pair,
PUBLIC_KEY_LEN,
SIGNATURE_LEN,
POP_LEN,
EcdsaBls377Tag,
>;
/// (ECDSA,BLS12-377) public key pair.
pub type Public = super::Public<PUBLIC_KEY_LEN, EcdsaBls377Tag>;
/// (ECDSA,BLS12-377) signature pair.
pub type Signature = super::Signature<SIGNATURE_LEN, EcdsaBls377Tag>;
/// (ECDSA, (BLS12-377, BLS12-377))
pub type ProofOfPossession = super::Signature<POP_LEN, EcdsaBls377Tag>;
impl super::CryptoType for Public {
type Pair = Pair;
}
impl super::CryptoType for Signature {
type Pair = Pair;
}
impl super::CryptoType for ProofOfPossession {
type Pair = Pair;
}
impl super::CryptoType for Pair {
type Pair = Pair;
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Hashes the `message` with the specified [`Hasher`] before signing with the ECDSA secret
/// component.
///
/// The hasher does not affect the BLS12-377 component. This generates BLS12-377 Signature
/// according to IETF standard.
pub fn sign_with_hasher<H>(&self, message: &[u8]) -> Signature
where
H: Hasher,
H::Out: Into<[u8; 32]>,
{
let msg_hash = H::hash(message).into();
let mut raw: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
raw[..ecdsa::SIGNATURE_SERIALIZED_SIZE]
.copy_from_slice(self.left.sign_prehashed(&msg_hash).as_ref());
raw[ecdsa::SIGNATURE_SERIALIZED_SIZE..]
.copy_from_slice(self.right.sign(message).as_ref());
<Self as PairT>::Signature::unchecked_from(raw)
}
/// Hashes the `message` with the specified [`Hasher`] before verifying with the ECDSA
/// public component.
///
/// The hasher does not affect the the BLS12-377 component. This verifies whether the
/// BLS12-377 signature was hashed and signed according to IETF standard
pub fn verify_with_hasher<H>(sig: &Signature, message: &[u8], public: &Public) -> bool
where
H: Hasher,
H::Out: Into<[u8; 32]>,
{
let msg_hash = H::hash(message).into();
let Ok(left_pub) = public.0[..ecdsa::PUBLIC_KEY_SERIALIZED_SIZE].try_into() else {
return false;
};
let Ok(left_sig) = sig.0[..ecdsa::SIGNATURE_SERIALIZED_SIZE].try_into() else {
return false;
};
if !ecdsa::Pair::verify_prehashed(&left_sig, &msg_hash, &left_pub) {
return false;
}
let Ok(right_pub) = public.0[ecdsa::PUBLIC_KEY_SERIALIZED_SIZE..].try_into() else {
return false;
};
let Ok(right_sig) = sig.0[ecdsa::SIGNATURE_SERIALIZED_SIZE..].try_into() else {
return false;
};
bls377::Pair::verify(&right_sig, message, &right_pub)
}
}
}
/// ECDSA and BLS12-381 paired crypto scheme
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls381 {
use crate::{bls381, crypto::CryptoTypeId, ecdsa};
#[cfg(feature = "full_crypto")]
use crate::{
crypto::{Pair as PairT, UncheckedFrom},
Hasher,
};
/// An identifier used to match public keys against BLS12-381 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecb8");
/// Aggregate public key size.
pub const PUBLIC_KEY_LEN: usize =
ecdsa::PUBLIC_KEY_SERIALIZED_SIZE + bls381::PUBLIC_KEY_SERIALIZED_SIZE;
/// Aggregate signature serialized size.
pub const SIGNATURE_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls381::SIGNATURE_SERIALIZED_SIZE;
/// Size of Proof Of Possession for (ECDSA, BLS12-381) type (this is one ECDSA and two BLS12-381
/// signature)
pub const POP_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls381::PROOF_OF_POSSESSION_SERIALIZED_SIZE;
#[doc(hidden)]
pub struct EcdsaBls381Tag(ecdsa::EcdsaTag, bls381::Bls381Tag);
impl super::PairedCryptoSubTagBound for EcdsaBls381Tag {}
/// (ECDSA,BLS12-381) key-pair pair.
pub type Pair = super::Pair<
ecdsa::Pair,
bls381::Pair,
PUBLIC_KEY_LEN,
SIGNATURE_LEN,
POP_LEN,
EcdsaBls381Tag,
>;
/// (ECDSA,BLS12-381) public key pair.
pub type Public = super::Public<PUBLIC_KEY_LEN, EcdsaBls381Tag>;
/// (ECDSA,BLS12-381) signature pair.
pub type Signature = super::Signature<SIGNATURE_LEN, EcdsaBls381Tag>;
/// (ECDSA, (BLS12-381, BLS12-381))
pub type ProofOfPossession = super::Signature<POP_LEN, EcdsaBls381Tag>;
impl super::CryptoType for Public {
type Pair = Pair;
}
impl super::CryptoType for Signature {
type Pair = Pair;
}
impl super::CryptoType for ProofOfPossession {
type Pair = Pair;
}
impl super::CryptoType for Pair {
type Pair = Pair;
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Hashes the `message` with the specified [`Hasher`] before signing with the ECDSA secret
/// component.
///
/// The hasher does not affect the BLS12-381 component. This generates BLS12-381 Signature
/// according to IETF standard.
pub fn sign_with_hasher<H>(&self, message: &[u8]) -> Signature
where
H: Hasher,
H::Out: Into<[u8; 32]>,
{
let msg_hash = H::hash(message).into();
let mut raw: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
raw[..ecdsa::SIGNATURE_SERIALIZED_SIZE]
.copy_from_slice(self.left.sign_prehashed(&msg_hash).as_ref());
raw[ecdsa::SIGNATURE_SERIALIZED_SIZE..]
.copy_from_slice(self.right.sign(message).as_ref());
<Self as PairT>::Signature::unchecked_from(raw)
}
/// Hashes the `message` with the specified [`Hasher`] before verifying with the ECDSA
/// public component.
///
/// The hasher does not affect the the BLS12-381 component. This verifies whether the
/// BLS12-381 signature was hashed and signed according to IETF standard
pub fn verify_with_hasher<H>(sig: &Signature, message: &[u8], public: &Public) -> bool
where
H: Hasher,
H::Out: Into<[u8; 32]>,
{
let msg_hash = H::hash(message).into();
let Ok(left_pub) = public.0[..ecdsa::PUBLIC_KEY_SERIALIZED_SIZE].try_into() else {
return false;
};
let Ok(left_sig) = sig.0[..ecdsa::SIGNATURE_SERIALIZED_SIZE].try_into() else {
return false;
};
if !ecdsa::Pair::verify_prehashed(&left_sig, &msg_hash, &left_pub) {
return false;
}
let Ok(right_pub) = public.0[ecdsa::PUBLIC_KEY_SERIALIZED_SIZE..].try_into() else {
return false;
};
let Ok(right_sig) = sig.0[ecdsa::SIGNATURE_SERIALIZED_SIZE..].try_into() else {
return false;
};
bls381::Pair::verify(&right_sig, message, &right_pub)
}
}
}
/// Secure seed length.
///
/// Currently only supporting sub-schemes whose seed is a 32-bytes array.
const SECURE_SEED_LEN: usize = 32;
/// A secret seed.
///
/// It's not called a "secret key" because ring doesn't expose the secret keys
/// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we
/// will need it later (such as for HDKD).
type Seed = [u8; SECURE_SEED_LEN];
#[doc(hidden)]
pub trait PairedCryptoSubTagBound {}
#[doc(hidden)]
pub struct PairedCryptoTag;
/// A public key.
pub type Public<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> =
PublicBytes<LEFT_PLUS_RIGHT_LEN, (PairedCryptoTag, SubTag)>;
impl<
LeftPair: PairT,
RightPair: PairT,
const LEFT_PLUS_RIGHT_PUBLIC_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag: PairedCryptoSubTagBound,
> From<Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, POP_LEN, SubTag>>
for Public<LEFT_PLUS_RIGHT_PUBLIC_LEN, SubTag>
where
Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, POP_LEN, SubTag>:
PairT<Public = Public<LEFT_PLUS_RIGHT_PUBLIC_LEN, SubTag>>,
{
fn from(
x: Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, POP_LEN, SubTag>,
) -> Self {
x.public()
}
}
/// A pair of signatures of different types
pub type Signature<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> =
SignatureBytes<LEFT_PLUS_RIGHT_LEN, (PairedCryptoTag, SubTag)>;
/// A pair of proof of possession of different types
pub type ProofOfPossession<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> =
SignatureBytes<LEFT_PLUS_RIGHT_LEN, (PairedCryptoTag, SubTag)>;
/// A key pair.
pub struct Pair<
LeftPair: PairT,
RightPair: PairT,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag,
> {
left: LeftPair,
right: RightPair,
_phantom: PhantomData<fn() -> SubTag>,
}
///Implementation of Clone for PairedCrypto
impl<
LeftPair: PairT + Clone,
RightPair: PairT + Clone,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag,
> Clone for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>
{
fn clone(&self) -> Self {
Self { left: self.left.clone(), right: self.right.clone(), _phantom: PhantomData }
}
}
///Implementation of keypair for paired cryptographic keys
impl<
LeftPair: PairT,
RightPair: PairT,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag: PairedCryptoSubTagBound,
> PairT for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>
where
Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>: CryptoType,
Public<PUBLIC_KEY_LEN, SubTag>: PublicT,
Signature<SIGNATURE_LEN, SubTag>: SignatureT,
ProofOfPossession<POP_LEN, SubTag>: SignatureT,
LeftPair::Seed: From<Seed> + Into<Seed>,
RightPair::Seed: From<Seed> + Into<Seed>,
{
type Seed = Seed;
type Public = Public<PUBLIC_KEY_LEN, SubTag>;
type Signature = Signature<SIGNATURE_LEN, SubTag>;
type ProofOfPossession = Signature<POP_LEN, SubTag>;
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
if seed_slice.len() != SECURE_SEED_LEN {
return Err(SecretStringError::InvalidSeedLength);
}
let left = LeftPair::from_seed_slice(&seed_slice)?;
let right = RightPair::from_seed_slice(&seed_slice)?;
Ok(Pair { left, right, _phantom: PhantomData })
}
/// Derive a child key from a series of given junctions.
///
/// Note: if the `LeftPair` and `RightPair` crypto schemes differ in
/// seed derivation, `derive` will drop the seed in the return.
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
seed: Option<Self::Seed>,
) -> Result<(Self, Option<Self::Seed>), DeriveError> {
let left_path: Vec<_> = path.collect();
let right_path: Vec<_> = left_path.clone();
let left = self.left.derive(left_path.into_iter(), seed.map(|s| s.into()))?;
let right = self.right.derive(right_path.into_iter(), seed.map(|s| s.into()))?;
let seed = match (left.1, right.1) {
(Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()),
_ => None,
};
Ok((Self { left: left.0, right: right.0, _phantom: PhantomData }, seed))
}
fn public(&self) -> Self::Public {
let mut raw = [0u8; PUBLIC_KEY_LEN];
let left_pub = self.left.public();
let right_pub = self.right.public();
raw[..LeftPair::Public::LEN].copy_from_slice(left_pub.as_ref());
raw[LeftPair::Public::LEN..].copy_from_slice(right_pub.as_ref());
Self::Public::unchecked_from(raw)
}
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Self::Signature {
let mut raw: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
raw[..LeftPair::Signature::LEN].copy_from_slice(self.left.sign(message).as_ref());
raw[LeftPair::Signature::LEN..].copy_from_slice(self.right.sign(message).as_ref());
Self::Signature::unchecked_from(raw)
}
fn verify<Msg: AsRef<[u8]>>(
sig: &Self::Signature,
message: Msg,
public: &Self::Public,
) -> bool {
let Ok(left_pub) = public.0[..LeftPair::Public::LEN].try_into() else { return false };
let Ok(left_sig) = sig.0[0..LeftPair::Signature::LEN].try_into() else { return false };
if !LeftPair::verify(&left_sig, message.as_ref(), &left_pub) {
return false;
}
let Ok(right_pub) = public.0[LeftPair::Public::LEN..].try_into() else { return false };
let Ok(right_sig) = sig.0[LeftPair::Signature::LEN..].try_into() else { return false };
RightPair::verify(&right_sig, message.as_ref(), &right_pub)
}
/// Get the seed/secret key for each key and then concatenate them.
fn to_raw_vec(&self) -> Vec<u8> {
let mut raw = self.left.to_raw_vec();
raw.extend(self.right.to_raw_vec());
raw
}
}
impl<
LeftPair: PairT + ProofOfPossessionGenerator,
RightPair: PairT + ProofOfPossessionGenerator,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag: PairedCryptoSubTagBound,
> ProofOfPossessionGenerator
for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>
where
Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>: CryptoType,
Public<PUBLIC_KEY_LEN, SubTag>: PublicT,
Signature<SIGNATURE_LEN, SubTag>: SignatureT,
ProofOfPossession<POP_LEN, SubTag>: SignatureT,
LeftPair::Seed: From<Seed> + Into<Seed>,
RightPair::Seed: From<Seed> + Into<Seed>,
{
#[cfg(feature = "full_crypto")]
fn generate_proof_of_possession(&mut self, owner: &[u8]) -> Self::ProofOfPossession {
let mut raw: [u8; POP_LEN] = [0u8; POP_LEN];
raw.copy_from_slice(
[
self.left.generate_proof_of_possession(owner).to_raw_vec(),
self.right.generate_proof_of_possession(owner).to_raw_vec(),
]
.concat()
.as_slice(),
);
Self::ProofOfPossession::unchecked_from(raw)
}
}
/// This requires that the proof_of_possession of LEFT is of LeftPair::Signature.
/// This is the case for current implemented cases but does not
/// holds in general.
impl<
LeftPair: PairT + ProofOfPossessionVerifier,
RightPair: PairT + ProofOfPossessionVerifier,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
const POP_LEN: usize,
SubTag: PairedCryptoSubTagBound,
> ProofOfPossessionVerifier
for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>
where
Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, POP_LEN, SubTag>: CryptoType,
Public<PUBLIC_KEY_LEN, SubTag>: PublicT,
Signature<SIGNATURE_LEN, SubTag>: SignatureT,
ProofOfPossession<POP_LEN, SubTag>: SignatureT,
LeftPair::Seed: From<Seed> + Into<Seed>,
RightPair::Seed: From<Seed> + Into<Seed>,
{
fn verify_proof_of_possession(
owner: &[u8],
proof_of_possession: &Self::ProofOfPossession,
allegedly_possessed_pubkey: &Self::Public,
) -> bool {
let Ok(left_pub) = allegedly_possessed_pubkey.0[..LeftPair::Public::LEN].try_into() else {
return false;
};
let Ok(left_proof_of_possession) =
proof_of_possession.0[0..LeftPair::ProofOfPossession::LEN].try_into()
else {
return false;
};
if !LeftPair::verify_proof_of_possession(owner, &left_proof_of_possession, &left_pub) {
return false;
}
let Ok(right_pub) = allegedly_possessed_pubkey.0[LeftPair::Public::LEN..].try_into() else {
return false;
};
let Ok(right_proof_of_possession) =
proof_of_possession.0[LeftPair::ProofOfPossession::LEN..].try_into()
else {
return false;
};
RightPair::verify_proof_of_possession(owner, &right_proof_of_possession, &right_pub)
}
}
// Test set exercising the (ECDSA,BLS12-377) implementation
#[cfg(all(test, feature = "bls-experimental"))]
mod tests {
use super::*;
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
use crate::{bls377, crypto::DEV_PHRASE, ecdsa, KeccakHasher};
use codec::{Decode, Encode};
use ecdsa_bls377::{Pair, Signature};
#[test]
fn test_length_of_paired_ecdsa_and_bls377_public_key_and_signature_is_correct() {
assert_eq!(
<Pair as PairT>::Public::LEN,
<ecdsa::Pair as PairT>::Public::LEN + <bls377::Pair as PairT>::Public::LEN
);
assert_eq!(
<Pair as PairT>::Signature::LEN,
<ecdsa::Pair as PairT>::Signature::LEN + <bls377::Pair as PairT>::Signature::LEN
);
}
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
.unwrap()
.public(),
);
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string() {
let (pair, phrase, seed) = Pair::generate_with_phrase(None);
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
}
#[test]
fn seed_and_derive_should_work() {
let seed_for_right_and_left: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
);
let pair = Pair::from_seed(&seed_for_right_and_left);
// we are using hash-to-field so this is not going to work
// assert_eq!(pair.seed(), seed);
let path = vec![DeriveJunction::Hard([0u8; 32])];
let derived = pair.derive(path.into_iter(), None).ok().unwrap().0;
assert_eq!(
derived.to_raw_vec(),
[
array_bytes::hex2array_unchecked::<&str, SECURE_SEED_LEN>(
"b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61"
),
array_bytes::hex2array_unchecked::<&str, SECURE_SEED_LEN>(
"3a0626d095148813cd1642d38254f1cfff7eb8cc1a2fc83b2a135377c3554c12"
)
]
.concat()
);
}
#[test]
fn test_vector_should_work() {
let seed_left_and_right: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
);
let pair = Pair::from_seed(&([seed_left_and_right].concat()[..].try_into().unwrap()));
let public = pair.public();
assert_eq!(
public,
Public::unchecked_from(
array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400"),
),
);
let message = b"";
let signature =
array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00124571b4bf23083b5d07e720fde0a984d4d592868156ece77487e97a1ba4b29397dbdc454f13e3aed1ad4b6a99af2501c68ab88ec0495f962a4f55c7c460275a8d356cfa344c27778ca4c641bd9a3604ce5c28f9ed566e1d29bf3b5d3591e46ae28be3ece035e8e4db53a40fc5826002"
);
let signature = Signature::unchecked_from(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn test_vector_by_string_should_work() {
let pair = Pair::from_string(
"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
None,
)
.unwrap();
let public = pair.public();
assert_eq!(
public,
Public::unchecked_from(
array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400"
),
),
);
let message = b"";
let signature =
array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00124571b4bf23083b5d07e720fde0a984d4d592868156ece77487e97a1ba4b29397dbdc454f13e3aed1ad4b6a99af2501c68ab88ec0495f962a4f55c7c460275a8d356cfa344c27778ca4c641bd9a3604ce5c28f9ed566e1d29bf3b5d3591e46ae28be3ece035e8e4db53a40fc5826002"
);
let signature = Signature::unchecked_from(signature);
assert!(pair.sign(&message[..]) == signature);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn generated_pair_should_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, b"Something else", &public));
}
#[test]
fn seeded_pair_should_work() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let public = pair.public();
assert_eq!(
public,
Public::unchecked_from(
array_bytes::hex2array_unchecked("035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9754d2f2bbfa67df54d7e0e951979a18a1e0f45948857752cc2bac6bbb0b1d05e8e48bcc453920bf0c4bbd5993212480112a1fb433f04d74af0a8b700d93dc957ab3207f8d071e948f5aca1a7632c00bdf6d06be05b43e2e6216dccc8a5d55a0071cb2313cfd60b7e9114619cd17c06843b352f0b607a99122f6651df8f02e1ad3697bd208e62af047ddd7b942ba80080")
),
);
let message =
array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"
);
let signature = pair.sign(&message[..]);
println!("Correct signature: {:?}", signature);
assert!(Pair::verify(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, "Other message", &public));
}
#[test]
fn generate_with_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(None);
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn generate_with_password_phrase_recovery_possible() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap();
assert_eq!(pair1.public(), pair2.public());
}
#[test]
fn password_does_something() {
let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password"));
let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap();
assert_ne!(pair1.public(), pair2.public());
assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec());
}
#[test]
fn ss58check_roundtrip_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let public = pair.public();
let s = public.to_ss58check();
println!("Correct: {}", s);
let cmp = Public::from_ss58check(&s).unwrap();
assert_eq!(cmp, public);
}
#[test]
fn sign_and_verify_with_hasher_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let message = b"Something important";
let signature = pair.sign_with_hasher::<KeccakHasher>(&message[..]);
assert!(Pair::verify_with_hasher::<KeccakHasher>(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let message = b"Something important";
let signature = pair.sign(&message[..]);
let serialized_signature = serde_json::to_string(&signature).unwrap();
println!("{:?} -- {:}", signature.0, serialized_signature);
// Signature is 177 bytes, hexify * 2 + 2 quote charsy
assert_eq!(serialized_signature.len(), 356);
let signature = serde_json::from_str(&serialized_signature).unwrap();
assert!(Pair::verify(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_doesnt_panic() {
fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
serde_json::from_str(text)
}
assert!(deserialize_signature("Not valid json.").is_err());
assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
// Poorly-sized
assert!(deserialize_signature("\"abc123\"").is_err());
}
#[test]
fn encode_and_decode_public_key_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let public = pair.public();
let encoded_public = public.encode();
let decoded_public = Public::decode(&mut encoded_public.as_slice()).unwrap();
assert_eq!(public, decoded_public)
}
#[test]
fn encode_and_decode_signature_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let message = b"Something important";
let signature = pair.sign(&message[..]);
let encoded_signature = signature.encode();
let decoded_signature = Signature::decode(&mut encoded_signature.as_slice()).unwrap();
assert_eq!(signature, decoded_signature)
}
#[test]
fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert_eq!(
Pair::verify_proof_of_possession(owner, &proof_of_possession, &other_pair.public()),
false
);
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
}
@@ -0,0 +1,139 @@
// 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.
//! Utilities for proving possession of a particular public key
use crate::crypto::{CryptoType, Pair, Signature};
use pezsp_std::vec::Vec;
/// Pair which is able to generate proof of possession.
///
/// This is implemented in different trait to provide default behavior.
pub trait ProofOfPossessionGenerator: Pair
where
Self::Public: CryptoType,
Self::ProofOfPossession: Signature,
{
/// Generate proof of possession.
///
/// This is usually done by signing the owner's identifier, this is prevent front runner to
/// claim ownership of public keys of other entities.
///
/// However, for aggregatable signature the proof of possession generator is supposed to
/// produce a "signature" with unique hash context that should never be used in other
/// signatures. While this proves that the secret key is known to the prover, it prevents
/// malicious actors to trick an honest party to sign an unpossessed public key resulting in
/// a rogue key attack (See: Section 4.3 of - Ristenpart, T., & Yilek, S. (2007). The power
/// of proofs-of-possession: Securing multiparty signatures against rogue-key attacks. In ,
/// Annual {{International Conference}} on the {{Theory}} and {{Applications}} of
/// {{Cryptographic Techniques} (pp. 228245). : Springer).
///
/// As such, for aggregatable signatures, proof of possession consists of two signatures one
/// regular signature signing the owner identity and the second one with unique context
/// which signs the correspoding public key (and nothing else).
#[cfg(feature = "full_crypto")]
fn generate_proof_of_possession(&mut self, owner: &[u8]) -> Self::ProofOfPossession;
}
/// Pair which is able to verify proof of possession.
///
/// While you don't need a keypair to verify a proof of possession (you only need a public key) we
/// constrain on Pair to use the Public and Signature types associated to Pair. This is implemented
/// in different trait (than Public Key) to provide default behavior.
pub trait ProofOfPossessionVerifier: Pair
where
Self::Public: CryptoType,
Self::ProofOfPossession: Signature,
{
/// Verify proof of possession.
///
/// The proof of possession verifier is supposed to to verify a signature with unique hash
/// context that is produced solely for this reason. This proves that that the secret key is
/// known to the prover.
fn verify_proof_of_possession(
owner: &[u8],
proof_of_possession: &Self::ProofOfPossession,
allegedly_possessesd_pubkey: &Self::Public,
) -> bool;
}
/// Simply returns the owner prefixed with proof of possession context.
pub fn statement_of_ownership(owner: &[u8]) -> Vec<u8> {
/// The context which attached to pop message to attest its purpose.
const PROOF_OF_POSSESSION_CONTEXT_TAG: &[u8; 4] = b"POP_";
[PROOF_OF_POSSESSION_CONTEXT_TAG, owner].concat()
}
/// Marker trait to identify whether the scheme is not aggregatable.
///
/// Aggregatable schemes may change/optimize implementation parts such as Proof Of Possession or
/// other specifics.
///
/// This is specifically because implementation of proof of possession for aggregatable schemes is
/// security critical.
///
/// We would like to prevent aggregatable scheme from unknowingly generating signatures which
/// aggregate to false albeit valid proof of possession aka rogue key attack. We ensure that by
/// separating signing and generating proof_of_possession at the API level.
///
/// Rogue key attack however is not immediately applicable to non-aggregatable scheme when even if
/// an honest signing oracle is tricked to sign a rogue proof_of_possession, it is not possible to
/// aggregate it to generate a valid proof for a key the attack does not possess. Therefore we do
/// not require non-aggregatable schemes to prevent proof_of_possession confirming signatures at API
/// level
pub trait NonAggregatable: Pair {
/// Default proof_of_possession statement.
fn proof_of_possession_statement(owner: &[u8]) -> Vec<u8> {
statement_of_ownership(owner)
}
}
impl<T> ProofOfPossessionVerifier for T
where
T: NonAggregatable<ProofOfPossession = Self::Signature>,
{
/// Default implementation for non-aggregatable signatures.
///
/// While we enforce hash context separation at the library level in aggregatable schemes,
/// it remains as an advisory for the default implementation using signature API used for
/// non-aggregatable schemes
fn verify_proof_of_possession(
owner: &[u8],
proof_of_possession: &Self::ProofOfPossession,
allegedly_possessesd_pubkey: &Self::Public,
) -> bool {
let proof_of_possession_statement = statement_of_ownership(owner);
Self::verify(
&proof_of_possession,
proof_of_possession_statement,
allegedly_possessesd_pubkey,
)
}
}
impl<T> ProofOfPossessionGenerator for T
where
T: NonAggregatable<ProofOfPossession = Self::Signature>,
{
/// Default implementation for non-aggregatable signatures.
///
/// While we enforce hash context separation at the library level in aggregatable schemes,
/// it remains as an advisory for the default implementation using signature API used for
/// non-aggregatable schemes
#[cfg(feature = "full_crypto")]
fn generate_proof_of_possession(&mut self, owner: &[u8]) -> Self::ProofOfPossession {
let proof_of_possession_statement = statement_of_ownership(owner);
self.sign(proof_of_possession_statement.as_slice())
}
}
+943
View File
@@ -0,0 +1,943 @@
// 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.
//! Simple sr25519 (Schnorr-Ristretto) API.
//!
//! Note: `CHAIN_CODE_LENGTH` must be equal to `crate::crypto::JUNCTION_ID_LEN`
//! for this to work.
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
use crate::{
crypto::{CryptoBytes, DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError},
proof_of_possession::NonAggregatable,
};
use alloc::vec::Vec;
#[cfg(feature = "full_crypto")]
use schnorrkel::signing_context;
use schnorrkel::{
derive::{ChainCode, Derivation, CHAIN_CODE_LENGTH},
ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey,
};
use crate::crypto::{CryptoType, CryptoTypeId, Derive, Public as TraitPublic, SignatureBytes};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
#[cfg(all(not(feature = "std"), feature = "serde"))]
use alloc::{format, string::String};
use schnorrkel::keys::{MINI_SECRET_KEY_LENGTH, SECRET_KEY_LENGTH};
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
// signing context
const SIGNING_CTX: &[u8] = b"bizinikiwi";
/// An identifier used to match public keys against sr25519 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"sr25");
/// The byte length of public key
pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 32;
/// The byte length of signature
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
#[doc(hidden)]
pub struct Sr25519Tag;
#[doc(hidden)]
pub struct Sr25519PublicTag;
/// An Schnorrkel/Ristretto x25519 ("sr25519") public key.
pub type Public = CryptoBytes<PUBLIC_KEY_SERIALIZED_SIZE, Sr25519PublicTag>;
impl TraitPublic for Public {}
impl Derive for Public {
/// Derive a child key from a series of given junctions.
///
/// `None` if there are any hard junctions in there.
#[cfg(feature = "serde")]
fn derive<Iter: Iterator<Item = DeriveJunction>>(&self, path: Iter) -> Option<Public> {
let mut acc = PublicKey::from_bytes(self.as_ref()).ok()?;
for j in path {
match j {
DeriveJunction::Soft(cc) => acc = acc.derived_key_simple(ChainCode(cc), &[]).0,
DeriveJunction::Hard(_cc) => return None,
}
}
Some(Self::from(acc.to_bytes()))
}
}
#[cfg(feature = "std")]
impl std::str::FromStr for Public {
type Err = crate::crypto::PublicError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_ss58check(s)
}
}
#[cfg(feature = "std")]
impl std::fmt::Display for Public {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_ss58check())
}
}
impl core::fmt::Debug for Public {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let s = self.to_ss58check();
write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.0), &s[0..8])
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
Ok(())
}
}
#[cfg(feature = "serde")]
impl Serialize for Public {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_ss58check())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Public {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Public::from_ss58check(&String::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(format!("{:?}", e)))
}
}
/// An Schnorrkel/Ristretto x25519 ("sr25519") signature.
pub type Signature = SignatureBytes<SIGNATURE_SERIALIZED_SIZE, Sr25519Tag>;
/// Proof of Possession is the same as Signature for sr25519
pub type ProofOfPossession = Signature;
#[cfg(feature = "full_crypto")]
impl From<schnorrkel::Signature> for Signature {
fn from(s: schnorrkel::Signature) -> Signature {
Signature::from(s.to_bytes())
}
}
/// An Schnorrkel/Ristretto x25519 ("sr25519") key pair.
pub struct Pair(Keypair);
impl Clone for Pair {
fn clone(&self) -> Self {
Pair(schnorrkel::Keypair {
public: self.0.public,
secret: schnorrkel::SecretKey::from_bytes(&self.0.secret.to_bytes()[..])
.expect("key is always the correct size; qed"),
})
}
}
#[cfg(feature = "std")]
impl From<MiniSecretKey> for Pair {
fn from(sec: MiniSecretKey) -> Pair {
Pair(sec.expand_to_keypair(ExpansionMode::Ed25519))
}
}
#[cfg(feature = "std")]
impl From<SecretKey> for Pair {
fn from(sec: SecretKey) -> Pair {
Pair(Keypair::from(sec))
}
}
#[cfg(feature = "full_crypto")]
impl From<schnorrkel::Keypair> for Pair {
fn from(p: schnorrkel::Keypair) -> Pair {
Pair(p)
}
}
#[cfg(feature = "full_crypto")]
impl From<Pair> for schnorrkel::Keypair {
fn from(p: Pair) -> schnorrkel::Keypair {
p.0
}
}
#[cfg(feature = "full_crypto")]
impl AsRef<schnorrkel::Keypair> for Pair {
fn as_ref(&self) -> &schnorrkel::Keypair {
&self.0
}
}
/// Derive a single hard junction.
fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> MiniSecretKey {
secret.hard_derive_mini_secret_key(Some(ChainCode(*cc)), b"").0
}
/// The raw secret seed, which can be used to recreate the `Pair`.
type Seed = [u8; MINI_SECRET_KEY_LENGTH];
impl TraitPair for Pair {
type Public = Public;
type Seed = Seed;
type Signature = Signature;
type ProofOfPossession = ProofOfPossession;
/// Get the public key.
fn public(&self) -> Public {
Public::from(self.0.public.to_bytes())
}
/// Make a new key pair from raw secret seed material.
///
/// This is generated using schnorrkel's Mini-Secret-Keys.
///
/// A `MiniSecretKey` is literally what Ed25519 calls a `SecretKey`, which is just 32 random
/// bytes.
fn from_seed_slice(seed: &[u8]) -> Result<Pair, SecretStringError> {
match seed.len() {
MINI_SECRET_KEY_LENGTH => Ok(Pair(
MiniSecretKey::from_bytes(seed)
.map_err(|_| SecretStringError::InvalidSeed)?
.expand_to_keypair(ExpansionMode::Ed25519),
)),
SECRET_KEY_LENGTH => Ok(Pair(
SecretKey::from_bytes(seed)
.map_err(|_| SecretStringError::InvalidSeed)?
.to_keypair(),
)),
_ => Err(SecretStringError::InvalidSeedLength),
}
}
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
seed: Option<Seed>,
) -> Result<(Pair, Option<Seed>), DeriveError> {
let seed = seed
.and_then(|s| MiniSecretKey::from_bytes(&s).ok())
.filter(|msk| msk.expand(ExpansionMode::Ed25519) == self.0.secret);
let init = self.0.secret.clone();
let (result, seed) = path.fold((init, seed), |(acc, acc_seed), j| match (j, acc_seed) {
(DeriveJunction::Soft(cc), _) => (acc.derived_key_simple(ChainCode(cc), &[]).0, None),
(DeriveJunction::Hard(cc), maybe_seed) => {
let seed = derive_hard_junction(&acc, &cc);
(seed.expand(ExpansionMode::Ed25519), maybe_seed.map(|_| seed))
},
});
Ok((Self(result.into()), seed.map(|s| MiniSecretKey::to_bytes(&s))))
}
#[cfg(feature = "full_crypto")]
fn sign(&self, message: &[u8]) -> Signature {
let context = signing_context(SIGNING_CTX);
self.0.sign(context.bytes(message)).into()
}
fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &Public) -> bool {
let Ok(signature) = schnorrkel::Signature::from_bytes(sig.as_ref()) else { return false };
let Ok(public) = PublicKey::from_bytes(pubkey.as_ref()) else { return false };
public.verify_simple(SIGNING_CTX, message.as_ref(), &signature).is_ok()
}
fn to_raw_vec(&self) -> Vec<u8> {
self.0.secret.to_bytes().to_vec()
}
}
#[cfg(not(bizinikiwi_runtime))]
impl Pair {
/// Verify a signature on a message. Returns `true` if the signature is good.
/// Supports old 0.1.1 deprecated signatures and should be used only for backward
/// compatibility.
pub fn verify_deprecated<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &Public) -> bool {
// Match both schnorrkel 0.1.1 and 0.8.0+ signatures, supporting both wallets
// that have not been upgraded and those that have.
match PublicKey::from_bytes(pubkey.as_ref()) {
Ok(pk) => pk
.verify_simple_preaudit_deprecated(SIGNING_CTX, message.as_ref(), &sig.0[..])
.is_ok(),
Err(_) => false,
}
}
}
impl CryptoType for Public {
type Pair = Pair;
}
impl CryptoType for Signature {
type Pair = Pair;
}
impl CryptoType for Pair {
type Pair = Pair;
}
impl NonAggregatable for Pair {}
/// Schnorrkel VRF related types and operations.
pub mod vrf {
use super::*;
#[cfg(feature = "full_crypto")]
use crate::crypto::VrfSecret;
use crate::crypto::{VrfCrypto, VrfPublic};
use schnorrkel::{
errors::MultiSignatureStage,
vrf::{VRF_PREOUT_LENGTH, VRF_PROOF_LENGTH},
SignatureError,
};
const DEFAULT_EXTRA_DATA_LABEL: &[u8] = b"VRF";
/// Transcript ready to be used for VRF related operations.
#[derive(Clone)]
pub struct VrfTranscript(pub merlin::Transcript);
impl VrfTranscript {
/// Build a new transcript instance.
///
/// Each `data` element is a tuple `(domain, message)` used to build the transcript.
pub fn new(label: &'static [u8], data: &[(&'static [u8], &[u8])]) -> Self {
let mut transcript = merlin::Transcript::new(label);
data.iter().for_each(|(l, b)| transcript.append_message(l, b));
VrfTranscript(transcript)
}
/// Map transcript to `VrfSignData`.
pub fn into_sign_data(self) -> VrfSignData {
self.into()
}
}
/// VRF input.
///
/// Technically a transcript used by the Fiat-Shamir transform.
pub type VrfInput = VrfTranscript;
/// VRF input ready to be used for VRF sign and verify operations.
#[derive(Clone)]
pub struct VrfSignData {
/// Transcript data contributing to VRF output.
pub(super) transcript: VrfTranscript,
/// Extra transcript data to be signed by the VRF.
pub(super) extra: Option<VrfTranscript>,
}
impl From<VrfInput> for VrfSignData {
fn from(transcript: VrfInput) -> Self {
VrfSignData { transcript, extra: None }
}
}
// Get a reference to the inner VRF input.
impl AsRef<VrfInput> for VrfSignData {
fn as_ref(&self) -> &VrfInput {
&self.transcript
}
}
impl VrfSignData {
/// Build a new instance ready to be used for VRF signer and verifier.
///
/// `input` will contribute to the VRF output bytes.
pub fn new(input: VrfTranscript) -> Self {
input.into()
}
/// Add some extra data to be signed.
///
/// `extra` will not contribute to the VRF output bytes.
pub fn with_extra(mut self, extra: VrfTranscript) -> Self {
self.extra = Some(extra);
self
}
}
/// VRF signature data
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct VrfSignature {
/// VRF pre-output.
pub pre_output: VrfPreOutput,
/// VRF proof.
pub proof: VrfProof,
}
/// VRF pre-output type suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VrfPreOutput(pub schnorrkel::vrf::VRFPreOut);
impl Encode for VrfPreOutput {
fn encode(&self) -> Vec<u8> {
self.0.as_bytes().encode()
}
}
impl Decode for VrfPreOutput {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_PREOUT_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFPreOut::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VrfPreOutput {
fn max_encoded_len() -> usize {
<[u8; VRF_PREOUT_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VrfPreOutput {
type Identity = [u8; VRF_PREOUT_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
/// VRF proof type suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VrfProof(pub schnorrkel::vrf::VRFProof);
impl Encode for VrfProof {
fn encode(&self) -> Vec<u8> {
self.0.to_bytes().encode()
}
}
impl Decode for VrfProof {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VrfProof {
fn max_encoded_len() -> usize {
<[u8; VRF_PROOF_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VrfProof {
type Identity = [u8; VRF_PROOF_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
#[cfg(feature = "full_crypto")]
impl VrfCrypto for Pair {
type VrfInput = VrfTranscript;
type VrfPreOutput = VrfPreOutput;
type VrfSignData = VrfSignData;
type VrfSignature = VrfSignature;
}
#[cfg(feature = "full_crypto")]
impl VrfSecret for Pair {
fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature {
let inout = self.0.vrf_create_hash(data.transcript.0.clone());
let extra = data
.extra
.as_ref()
.map(|e| e.0.clone())
.unwrap_or_else(|| merlin::Transcript::new(DEFAULT_EXTRA_DATA_LABEL));
let proof = self.0.dleq_proove(extra, &inout, true).0;
VrfSignature { pre_output: VrfPreOutput(inout.to_preout()), proof: VrfProof(proof) }
}
fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput {
let pre_output = self.0.vrf_create_hash(input.0.clone()).to_preout();
VrfPreOutput(pre_output)
}
}
impl VrfCrypto for Public {
type VrfInput = VrfTranscript;
type VrfPreOutput = VrfPreOutput;
type VrfSignData = VrfSignData;
type VrfSignature = VrfSignature;
}
impl VrfPublic for Public {
fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool {
let do_verify = || {
let public = schnorrkel::PublicKey::from_bytes(&self.0)?;
let inout =
signature.pre_output.0.attach_input_hash(&public, data.transcript.0.clone())?;
let extra = data
.extra
.as_ref()
.map(|e| e.0.clone())
.unwrap_or_else(|| merlin::Transcript::new(DEFAULT_EXTRA_DATA_LABEL));
public.dleq_verify(extra, &inout, &signature.proof.0, true)
};
do_verify().is_ok()
}
}
fn convert_error(e: SignatureError) -> codec::Error {
use MultiSignatureStage::*;
use SignatureError::*;
match e {
EquationFalse => "Signature error: `EquationFalse`".into(),
PointDecompressionError => "Signature error: `PointDecompressionError`".into(),
ScalarFormatError => "Signature error: `ScalarFormatError`".into(),
NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(),
BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(),
InvalidKey => "Signature error: `InvalidKey`".into(),
MuSigAbsent { musig_stage: Commitment } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigAbsent { musig_stage: Reveal } =>
"Signature error: `MuSigAbsent` at stage `Reveal`".into(),
MuSigAbsent { musig_stage: Cosignature } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate"
.into(),
}
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Generate output bytes from the given VRF configuration.
pub fn make_bytes<const N: usize>(&self, context: &[u8], input: &VrfInput) -> [u8; N]
where
[u8; N]: Default,
{
let inout = self.0.vrf_create_hash(input.0.clone());
inout.make_bytes::<[u8; N]>(context)
}
}
impl Public {
/// Generate output bytes from the given VRF configuration.
pub fn make_bytes<const N: usize>(
&self,
context: &[u8],
input: &VrfInput,
pre_output: &VrfPreOutput,
) -> Result<[u8; N], codec::Error>
where
[u8; N]: Default,
{
let pubkey = schnorrkel::PublicKey::from_bytes(&self.0).map_err(convert_error)?;
let inout = pre_output
.0
.attach_input_hash(&pubkey, input.0.clone())
.map_err(convert_error)?;
Ok(inout.make_bytes::<[u8; N]>(context))
}
}
impl VrfPreOutput {
/// Generate output bytes from the given VRF configuration.
pub fn make_bytes<const N: usize>(
&self,
context: &[u8],
input: &VrfInput,
public: &Public,
) -> Result<[u8; N], codec::Error>
where
[u8; N]: Default,
{
public.make_bytes(context, input, self)
}
}
}
#[cfg(test)]
mod tests {
use super::{vrf::*, *};
use crate::{
crypto::{Ss58Codec, VrfPublic, VrfSecret, DEV_ADDRESS, DEV_PHRASE},
proof_of_possession::{ProofOfPossessionGenerator, ProofOfPossessionVerifier},
ByteArray as _,
};
use serde_json;
#[test]
fn derive_soft_known_pair_should_work() {
let pair = Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None).unwrap();
// known address of DEV_PHRASE with 1.1
let known = array_bytes::hex2bytes_unchecked(
"d6c71059dbbe9ad2b0ed3f289738b800836eb425544ce694825285b958ca755e",
);
assert_eq!(pair.public().to_raw_vec(), known);
}
#[test]
fn derive_hard_known_pair_should_work() {
let pair = Pair::from_string(&format!("{}//Alice", DEV_PHRASE), None).unwrap();
// known address of DEV_PHRASE with 1.1
let known = array_bytes::hex2bytes_unchecked(
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
);
assert_eq!(pair.public().to_raw_vec(), known);
}
#[test]
fn verify_known_old_message_should_work() {
let public = Public::from_raw(array_bytes::hex2array_unchecked(
"b4bfa1f7a5166695eb75299fd1c4c03ea212871c342f2c5dfea0902b2c246918",
));
// signature generated by the 1.1 version with the same ^^ public key.
let signature = Signature::from_raw(array_bytes::hex2array_unchecked(
"5a9755f069939f45d96aaf125cf5ce7ba1db998686f87f2fb3cbdea922078741a73891ba265f70c31436e18a9acd14d189d73c12317ab6c313285cd938453202"
));
let message = b"Verifying that I am the owner of 5G9hQLdsKQswNPgB499DeA5PkFBbgkLPJWkkS6FAM6xGQ8xD. Hash: 221455a3\n";
assert!(Pair::verify_deprecated(&signature, &message[..], &public));
assert!(!Pair::verify(&signature, &message[..], &public));
}
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password"))
.unwrap()
.public(),
);
assert_eq!(
Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None)
.as_ref()
.map(Pair::public),
Pair::from_string("/Alice", None).as_ref().map(Pair::public)
);
}
#[test]
fn default_address_should_be_used() {
assert_eq!(
Public::from_string(&format!("{}/Alice", DEV_ADDRESS)),
Public::from_string("/Alice")
);
}
#[test]
fn default_phrase_should_correspond_to_default_address() {
assert_eq!(
Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None).unwrap().public(),
Public::from_string(&format!("{}/Alice", DEV_ADDRESS)).unwrap(),
);
assert_eq!(
Pair::from_string("/Alice", None).unwrap().public(),
Public::from_string("/Alice").unwrap()
);
}
#[test]
fn derive_soft_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let derive_1 = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0;
let derive_1b = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0;
let derive_2 = pair.derive(Some(DeriveJunction::soft(2)).into_iter(), None).unwrap().0;
assert_eq!(derive_1.public(), derive_1b.public());
assert_ne!(derive_1.public(), derive_2.public());
}
#[test]
fn derive_hard_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let derive_1 = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0;
let derive_1b = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0;
let derive_2 = pair.derive(Some(DeriveJunction::hard(2)).into_iter(), None).unwrap().0;
assert_eq!(derive_1.public(), derive_1b.public());
assert_ne!(derive_1.public(), derive_2.public());
}
#[test]
fn derive_soft_public_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let path = Some(DeriveJunction::soft(1));
let pair_1 = pair.derive(path.into_iter(), None).unwrap().0;
let public_1 = pair.public().derive(path.into_iter()).unwrap();
assert_eq!(pair_1.public(), public_1);
}
#[test]
fn derive_hard_public_should_fail() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let path = Some(DeriveJunction::hard(1));
assert!(pair.public().derive(path.into_iter()).is_none());
}
#[test]
fn sr_test_vector_should_work() {
let pair = Pair::from_seed(&array_bytes::hex2array_unchecked(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
));
let public = pair.public();
assert_eq!(
public,
Public::from_raw(array_bytes::hex2array_unchecked(
"44a996beb1eef7bdcab976ab6d2ca26104834164ecf28fb375600576fcc6eb0f"
))
);
let message = b"";
let signature = pair.sign(message);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn generate_with_phrase_should_be_recoverable_with_from_string() {
let (pair, phrase, seed) = Pair::generate_with_phrase(None);
let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid");
assert_eq!(pair.public(), repair_seed.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let (repair_phrase, reseed) =
Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid");
assert_eq!(seed, reseed);
assert_eq!(pair.public(), repair_phrase.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid");
assert_eq!(pair.public(), repair_string.public());
assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec());
}
#[test]
fn generated_pair_should_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn messed_signature_should_not_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Signed payload";
let mut signature = pair.sign(&message[..]);
let bytes = &mut signature.0;
bytes[0] = !bytes[0];
bytes[2] = !bytes[2];
assert!(!Pair::verify(&signature, &message[..], &public));
}
#[test]
fn messed_message_should_not_work() {
let (pair, _) = Pair::generate();
let public = pair.public();
let message = b"Something important";
let signature = pair.sign(&message[..]);
assert!(!Pair::verify(&signature, &b"Something unimportant", &public));
}
#[test]
fn seeded_pair_should_work() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
assert_eq!(
public,
Public::from_raw(array_bytes::hex2array_unchecked(
"741c08a06f41c596608f6774259bd9043304adfa5d3eea62760bd9be97634d63"
))
);
let message = array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000");
let signature = pair.sign(&message[..]);
assert!(Pair::verify(&signature, &message[..], &public));
}
#[test]
fn ss58check_roundtrip_works() {
let (pair, _) = Pair::generate();
let public = pair.public();
let s = public.to_ss58check();
println!("Correct: {}", s);
let cmp = Public::from_ss58check(&s).unwrap();
assert_eq!(cmp, public);
}
#[test]
fn verify_from_old_wasm_works() {
// The values in this test case are compared to the output of `node-test.js` in
// schnorrkel-js.
//
// This is to make sure that the wasm library is compatible.
let pk = Pair::from_seed(&array_bytes::hex2array_unchecked(
"0000000000000000000000000000000000000000000000000000000000000000",
));
let public = pk.public();
let js_signature = Signature::from_raw(array_bytes::hex2array_unchecked(
"28a854d54903e056f89581c691c1f7d2ff39f8f896c9e9c22475e60902cc2b3547199e0e91fa32902028f2ca2355e8cdd16cfe19ba5e8b658c94aa80f3b81a00"
));
assert!(Pair::verify_deprecated(&js_signature, b"BIZINIKIWI", &public));
assert!(!Pair::verify(&js_signature, b"BIZINIKIWI", &public));
}
#[test]
fn signature_serialization_works() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let message = b"Something important";
let signature = pair.sign(&message[..]);
let serialized_signature = serde_json::to_string(&signature).unwrap();
// Signature is 64 bytes, so 128 chars + 2 quote chars
assert_eq!(serialized_signature.len(), 130);
let signature = serde_json::from_str(&serialized_signature).unwrap();
assert!(Pair::verify(&signature, &message[..], &pair.public()));
}
#[test]
fn signature_serialization_doesnt_panic() {
fn deserialize_signature(text: &str) -> Result<Signature, serde_json::error::Error> {
serde_json::from_str(text)
}
assert!(deserialize_signature("Not valid json.").is_err());
assert!(deserialize_signature("\"Not an actual signature.\"").is_err());
// Poorly-sized
assert!(deserialize_signature("\"abc123\"").is_err());
}
#[test]
fn vrf_sign_verify() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let data = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]).into();
let signature = pair.vrf_sign(&data);
assert!(public.vrf_verify(&data, &signature));
}
#[test]
fn vrf_sign_verify_with_extra() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]);
let data = VrfTranscript::new(b"label", &[(b"domain1", b"data1")])
.into_sign_data()
.with_extra(extra);
let signature = pair.vrf_sign(&data);
assert!(public.vrf_verify(&data, &signature));
}
#[test]
fn vrf_make_bytes_matches() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let ctx = b"vrfbytes";
let input = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]);
let pre_output = pair.vrf_pre_output(&input);
let out1 = pair.make_bytes::<32>(ctx, &input);
let out2 = pre_output.make_bytes::<32>(ctx, &input, &public).unwrap();
assert_eq!(out1, out2);
let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]);
let data = input.clone().into_sign_data().with_extra(extra);
let signature = pair.vrf_sign(&data);
assert!(public.vrf_verify(&data, &signature));
let out3 = public.make_bytes::<32>(ctx, &input, &signature.pre_output).unwrap();
assert_eq!(out2, out3);
}
#[test]
fn vrf_backend_compat() {
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let ctx = b"vrfbytes";
let input = VrfInput::new(b"label", &[(b"domain1", b"data1")]);
let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]);
let data = input.clone().into_sign_data().with_extra(extra.clone());
let signature = pair.vrf_sign(&data);
assert!(public.vrf_verify(&data, &signature));
let out1 = pair.make_bytes::<32>(ctx, &input);
let out2 = public.make_bytes::<32>(ctx, &input, &signature.pre_output).unwrap();
assert_eq!(out1, out2);
// Direct call to backend version of sign after check with extra params
let (inout, proof, _) = pair
.0
.vrf_sign_extra_after_check(input.0.clone(), |inout| {
let out3 = inout.make_bytes::<[u8; 32]>(ctx);
assert_eq!(out2, out3);
Some(extra.0.clone())
})
.unwrap();
let signature2 =
VrfSignature { pre_output: VrfPreOutput(inout.to_preout()), proof: VrfProof(proof) };
assert!(public.vrf_verify(&data, &signature2));
assert_eq!(signature.pre_output, signature2.pre_output);
}
#[test]
fn good_proof_of_possession_should_work_bad_proof_of_possession_should_fail() {
let owner = b"owner";
let not_owner = b"not owner";
let mut pair = Pair::from_seed(b"12345678901234567890123456789012");
let other_pair = Pair::from_seed(b"23456789012345678901234567890123");
let proof_of_possession = pair.generate_proof_of_possession(owner);
assert!(Pair::verify_proof_of_possession(owner, &proof_of_possession, &pair.public()));
assert_eq!(
Pair::verify_proof_of_possession(owner, &proof_of_possession, &other_pair.public()),
false
);
assert!(!Pair::verify_proof_of_possession(not_owner, &proof_of_possession, &pair.public()));
}
}
+201
View File
@@ -0,0 +1,201 @@
// 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.
//! Types that should only be used for testing!
use crate::crypto::KeyTypeId;
/// Key type for generic Ed25519 key.
pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25");
/// Key type for generic Sr25519 key.
pub const SR25519: KeyTypeId = KeyTypeId(*b"sr25");
/// Key type for generic ECDSA key.
pub const ECDSA: KeyTypeId = KeyTypeId(*b"ecds");
/// Key type for generic Bandersnatch key.
pub const BANDERSNATCH: KeyTypeId = KeyTypeId(*b"band");
/// Key type for generic BLS12-377 key.
pub const BLS377: KeyTypeId = KeyTypeId(*b"bls7");
/// Key type for generic BLS12-381 key.
pub const BLS381: KeyTypeId = KeyTypeId(*b"bls8");
/// Key type for (ECDSA,BLS12-377) key pair
pub const ECDSA_BLS377: KeyTypeId = KeyTypeId(*b"ecb7");
/// Key type for (ECDSA,BLS12-381) key pair
pub const ECDSA_BLS381: KeyTypeId = KeyTypeId(*b"ecb8");
/// Macro for exporting functions from wasm in with the expected signature for using it with the
/// wasm executor. This is useful for tests where you need to call a function in wasm.
///
/// The input parameters are expected to be SCALE encoded and will be automatically decoded for you.
/// The output value is also SCALE encoded when returned back to the host.
///
/// The functions are feature-gated with `#[cfg(not(feature = "std"))]`, so they are only available
/// from within wasm.
///
/// # Example
///
/// ```
/// # use pezsp_core::wasm_export_functions;
///
/// wasm_export_functions! {
/// fn test_in_wasm(value: bool, another_value: Vec<u8>) -> bool {
/// value && another_value.is_empty()
/// }
///
/// fn without_return_value() {
/// // do something
/// }
/// }
/// ```
#[macro_export]
macro_rules! wasm_export_functions {
(
$(
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),* $(,)?
) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* }
)*
) => {
$(
$crate::wasm_export_functions! {
@IMPL
fn $name (
$( $arg_name: $arg_ty ),*
) $( -> $ret_ty )? { $( $fn_impl )* }
}
)*
};
(@IMPL
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),*
) { $( $fn_impl:tt )* }
) => {
#[no_mangle]
#[allow(unreachable_code)]
#[cfg(not(feature = "std"))]
pub fn $name(input_data: *mut u8, input_len: usize) -> u64 {
let input: &[u8] = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
::core::slice::from_raw_parts(input_data, input_len)
}
};
{
let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode(
&mut &input[..],
).expect("Input data is correctly encoded");
(|| { $( $fn_impl )* })()
}
$crate::to_bizinikiwi_wasm_fn_return_value(&())
}
};
(@IMPL
fn $name:ident (
$( $arg_name:ident: $arg_ty:ty ),*
) $( -> $ret_ty:ty )? { $( $fn_impl:tt )* }
) => {
#[no_mangle]
#[allow(unreachable_code)]
#[cfg(not(feature = "std"))]
pub fn $name(input_data: *mut u8, input_len: usize) -> u64 {
let input: &[u8] = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
::core::slice::from_raw_parts(input_data, input_len)
}
};
let output $( : $ret_ty )? = {
let ($( $arg_name ),*) : ($( $arg_ty ),*) = $crate::Decode::decode(
&mut &input[..],
).expect("Input data is correctly encoded");
(|| { $( $fn_impl )* })()
};
$crate::to_bizinikiwi_wasm_fn_return_value(&output)
}
};
}
/// A task executor that can be used in tests.
///
/// Internally this just wraps a `ThreadPool` with a pool size of `8`. This
/// should ensure that we have enough threads in tests for spawning blocking futures.
#[cfg(feature = "std")]
#[derive(Clone)]
pub struct TaskExecutor(futures::executor::ThreadPool);
#[cfg(feature = "std")]
impl TaskExecutor {
/// Create a new instance of `Self`.
pub fn new() -> Self {
let mut builder = futures::executor::ThreadPoolBuilder::new();
Self(builder.pool_size(8).create().expect("Failed to create thread pool"))
}
}
#[cfg(feature = "std")]
impl Default for TaskExecutor {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "std")]
impl crate::traits::SpawnNamed for TaskExecutor {
fn spawn_blocking(
&self,
_name: &'static str,
_group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
self.0.spawn_ok(future);
}
fn spawn(
&self,
_name: &'static str,
_group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
self.0.spawn_ok(future);
}
}
#[cfg(feature = "std")]
impl crate::traits::SpawnEssentialNamed for TaskExecutor {
fn spawn_essential_blocking(
&self,
_: &'static str,
_: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
self.0.spawn_ok(future);
}
fn spawn_essential(
&self,
_: &'static str,
_: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
self.0.spawn_ok(future);
}
}
+268
View File
@@ -0,0 +1,268 @@
// 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.
//! Shareable Bizinikiwi traits.
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
use core::fmt::{Debug, Display};
pub use pezsp_externalities::{Externalities, ExternalitiesExt};
/// The context in which a call is done.
///
/// Depending on the context the executor may chooses different kind of heap sizes for the runtime
/// instance.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum CallContext {
/// The call is happening in some offchain context.
Offchain,
/// The call is happening in some on-chain context like building or importing a block.
Onchain,
}
/// Code execution engine.
pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'static {
/// Externalities error type.
type Error: Display + Debug + Send + Sync + 'static;
/// Call a given method in the runtime.
///
/// Returns a tuple of the result (either the output data or an execution error) together with a
/// `bool`, which is true if native execution was used.
fn call(
&self,
ext: &mut dyn Externalities,
runtime_code: &RuntimeCode,
method: &str,
data: &[u8],
context: CallContext,
) -> (Result<Vec<u8>, Self::Error>, bool);
}
/// Something that can fetch the runtime `:code`.
pub trait FetchRuntimeCode {
/// Fetch the runtime `:code`.
///
/// If the `:code` could not be found/not available, `None` should be returned.
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>>;
}
/// Wrapper to use a `u8` slice or `Vec` as [`FetchRuntimeCode`].
pub struct WrappedRuntimeCode<'a>(pub Cow<'a, [u8]>);
impl<'a> FetchRuntimeCode for WrappedRuntimeCode<'a> {
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
Some(self.0.as_ref().into())
}
}
/// Type that implements [`FetchRuntimeCode`] and always returns `None`.
pub struct NoneFetchRuntimeCode;
impl FetchRuntimeCode for NoneFetchRuntimeCode {
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
None
}
}
/// The Wasm code of a Bizinikiwi runtime.
#[derive(Clone)]
pub struct RuntimeCode<'a> {
/// The code fetcher that can be used to lazily fetch the code.
pub code_fetcher: &'a dyn FetchRuntimeCode,
/// The optional heap pages this `code` should be executed with.
///
/// If `None` are given, the default value of the executor will be used.
pub heap_pages: Option<u64>,
/// The hash of `code`.
///
/// The hashing algorithm isn't that important, as long as all runtime
/// code instances use the same.
pub hash: Vec<u8>,
}
impl<'a> PartialEq for RuntimeCode<'a> {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}
impl<'a> RuntimeCode<'a> {
/// Create an empty instance.
///
/// This is only useful for tests that don't want to execute any code.
pub fn empty() -> Self {
Self { code_fetcher: &NoneFetchRuntimeCode, hash: Vec::new(), heap_pages: None }
}
}
impl<'a> FetchRuntimeCode for RuntimeCode<'a> {
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
self.code_fetcher.fetch_runtime_code()
}
}
/// Could not find the `:code` in the externalities while initializing the [`RuntimeCode`].
#[derive(Debug)]
pub struct CodeNotFound;
impl core::fmt::Display for CodeNotFound {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
write!(f, "the storage entry `:code` doesn't have any code")
}
}
/// A trait that allows reading version information from the binary.
pub trait ReadRuntimeVersion: Send + Sync {
/// Reads the runtime version information from the given wasm code.
///
/// The version information may be embedded into the wasm binary itself. If it is not present,
/// then this function may fallback to the legacy way of reading the version.
///
/// The legacy mechanism involves instantiating the passed wasm runtime and calling
/// `Core_version` on it. This is a very expensive operation.
///
/// `ext` is only needed in case the calling into runtime happens. Otherwise it is ignored.
///
/// Compressed wasm blobs are supported and will be decompressed if needed. If uncompression
/// fails, the error is returned.
///
/// # Errors
///
/// If the version information present in binary, but is corrupted - returns an error.
///
/// Otherwise, if there is no version information present, and calling into the runtime takes
/// place, then an error would be returned if `Core_version` is not provided.
fn read_runtime_version(
&self,
wasm_code: &[u8],
ext: &mut dyn Externalities,
) -> Result<Vec<u8>, String>;
}
impl ReadRuntimeVersion for alloc::sync::Arc<dyn ReadRuntimeVersion> {
fn read_runtime_version(
&self,
wasm_code: &[u8],
ext: &mut dyn Externalities,
) -> Result<Vec<u8>, String> {
(**self).read_runtime_version(wasm_code, ext)
}
}
pezsp_externalities::decl_extension! {
/// An extension that provides functionality to read version information from a given wasm blob.
pub struct ReadRuntimeVersionExt(Box<dyn ReadRuntimeVersion>);
}
impl ReadRuntimeVersionExt {
/// Creates a new instance of the extension given a version determinator instance.
pub fn new<T: ReadRuntimeVersion + 'static>(inner: T) -> Self {
Self(Box::new(inner))
}
}
/// Something that can spawn tasks (blocking and non-blocking) with an assigned name
/// and optional group.
pub trait SpawnNamed: dyn_clone::DynClone + Send + Sync {
/// Spawn the given blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn_blocking(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
/// Spawn the given non-blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
}
dyn_clone::clone_trait_object!(SpawnNamed);
impl SpawnNamed for Box<dyn SpawnNamed> {
fn spawn_blocking(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
(**self).spawn_blocking(name, group, future)
}
fn spawn(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
(**self).spawn(name, group, future)
}
}
/// Something that can spawn essential tasks (blocking and non-blocking) with an assigned name
/// and optional group.
///
/// Essential tasks are special tasks that should take down the node when they end.
pub trait SpawnEssentialNamed: dyn_clone::DynClone + Send + Sync {
/// Spawn the given blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn_essential_blocking(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
/// Spawn the given non-blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn_essential(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
}
dyn_clone::clone_trait_object!(SpawnEssentialNamed);
impl SpawnEssentialNamed for Box<dyn SpawnEssentialNamed> {
fn spawn_essential_blocking(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
(**self).spawn_essential_blocking(name, group, future)
}
fn spawn_essential(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
) {
(**self).spawn_essential(name, group, future)
}
}
+92
View File
@@ -0,0 +1,92 @@
// 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.
//! An unsigned fixed-size integer.
pub use primitive_types::{U256, U512};
#[cfg(test)]
mod tests {
use super::*;
use codec::{Decode, Encode};
macro_rules! test {
($name: ident, $test_name: ident) => {
#[test]
fn $test_name() {
let tests = vec![
($name::from(0), "0x0"),
($name::from(1), "0x1"),
($name::from(2), "0x2"),
($name::from(10), "0xa"),
($name::from(15), "0xf"),
($name::from(16), "0x10"),
($name::from(1_000), "0x3e8"),
($name::from(100_000), "0x186a0"),
($name::from(u64::MAX), "0xffffffffffffffff"),
($name::from(u64::MAX) + $name::from(1), "0x10000000000000000"),
];
for (number, expected) in tests {
assert_eq!(
format!("{:?}", expected),
serde_json::to_string_pretty(&number).expect("Json pretty print failed")
);
assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap());
}
// Invalid examples
assert!(serde_json::from_str::<$name>("\"0x\"").unwrap_err().is_data());
assert!(serde_json::from_str::<$name>("\"0xg\"").unwrap_err().is_data());
assert!(serde_json::from_str::<$name>("\"\"").unwrap_err().is_data());
}
};
}
test!(U256, test_u256);
#[test]
fn test_u256_codec() {
let res1 = vec![
120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
];
let res2 = vec![
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
];
assert_eq!(U256::from(120).encode(), res1);
assert_eq!(U256::max_value().encode(), res2);
assert_eq!(U256::decode(&mut &res1[..]), Ok(U256::from(120)));
assert_eq!(U256::decode(&mut &res2[..]), Ok(U256::max_value()));
}
#[test]
fn test_large_values() {
assert_eq!(
serde_json::to_string_pretty(&!U256::zero()).expect("Json pretty print failed"),
"\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""
);
assert!(serde_json::from_str::<U256>(
"\"0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""
)
.unwrap_err()
.is_data());
}
}