From 19eb56a3fc51140b269e339ecb7e9a4a378c26ff Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 17:09:47 +0200 Subject: [PATCH] Bandersnatch VRF (#14412) * Introduce bandersnatch vrf * Some documentation * Fix tests * Fix docs refs * Some more docs * Comments about key derivation * Make clippy happy * Fix ring context enc/dec test * Fix docs * Switch to upstream ring-vrf * Use sub-domains to construct VrfInput * Bandersnatch VRF experimental feature * Restore upstream dep * Fix feature flags * Apply typo fix Co-authored-by: Anton * Bump bandersnatch-vrfs * Weiestrass form has been selected * Rename bandersnatch testing app crypto id * Support for seed recovery * Clarified domain size <-> key size relationship * cargo fmt * Trigger CI * Some required tweaks to crypto types * Remove leftovers from Cargo.toml * Remove some TODO notes * Simplification of structs construction * Trigger CI * Apply review suggestion Co-authored-by: Koute * Docs typo * Fix keystore tests * Consistence * Add ref to git rependency * Static check of MAX_VRF_IOS value * Clarify behavior for out of ring keys signatures * Add test for ring-vrf to the keystore * Fix docs --------- Co-authored-by: Anton Co-authored-by: Koute --- Cargo.lock | 137 ++- client/keystore/Cargo.toml | 13 +- client/keystore/src/local.rs | 65 + primitives/application-crypto/Cargo.toml | 13 +- .../application-crypto/src/bandersnatch.rs | 57 + primitives/application-crypto/src/lib.rs | 2 + primitives/application-crypto/src/traits.rs | 40 +- primitives/core/Cargo.toml | 16 +- primitives/core/src/bandersnatch.rs | 1042 +++++++++++++++++ primitives/core/src/crypto.rs | 4 +- primitives/core/src/ecdsa.rs | 2 +- primitives/core/src/ed25519.rs | 2 +- primitives/core/src/lib.rs | 2 + primitives/core/src/sr25519.rs | 4 +- primitives/core/src/testing.rs | 4 +- primitives/io/Cargo.toml | 12 +- primitives/io/src/lib.rs | 23 +- primitives/keyring/Cargo.toml | 6 + primitives/keyring/src/bandersnatch.rs | 209 ++++ primitives/keyring/src/lib.rs | 6 + primitives/keystore/Cargo.toml | 10 +- primitives/keystore/src/lib.rs | 161 ++- primitives/keystore/src/testing.rs | 129 +- 23 files changed, 1900 insertions(+), 59 deletions(-) create mode 100644 primitives/application-crypto/src/bandersnatch.rs create mode 100644 primitives/core/src/bandersnatch.rs create mode 100644 primitives/keyring/src/bandersnatch.rs diff --git a/Cargo.lock b/Cargo.lock index a606e6a50cc8e..0289ea601eab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,6 +445,21 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "ark-transcript", + "digest 0.10.7", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -478,6 +493,19 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + [[package]] name = "array-bytes" version = "6.1.0" @@ -652,6 +680,27 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bandersnatch_vrfs" +version = "0.0.1" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-serialize", + "ark-std", + "dleq_vrf", + "fflonk", + "merlin 3.0.0", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "ring 0.1.0", + "sha2 0.10.7", + "zeroize", +] + [[package]] name = "base-x" version = "0.2.11" @@ -1248,6 +1297,20 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "common" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "common-path" version = "1.0.0" @@ -1866,6 +1929,22 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" +[[package]] +name = "dleq_vrf" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-secret-scalar", + "ark-serialize", + "ark-std", + "ark-transcript", + "arrayvec 0.7.4", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -2179,6 +2258,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "fflonk" +version = "0.1.0" +source = "git+https://github.com/w3f/fflonk#26a5045b24e169cffc1f9328ca83d71061145c40" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "merlin 3.0.0", +] + [[package]] name = "fiat-crypto" version = "0.1.20" @@ -4630,6 +4722,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -8201,6 +8305,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "common", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "ring" version = "0.16.20" @@ -8342,7 +8461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", - "ring", + "ring 0.16.20", "sct", "webpki", ] @@ -8354,7 +8473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct", ] @@ -8386,7 +8505,7 @@ version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -9896,7 +10015,7 @@ dependencies = [ "arrayvec 0.5.2", "curve25519-dalek 2.1.3", "getrandom 0.1.16", - "merlin", + "merlin 2.0.1", "rand 0.7.3", "rand_core 0.5.1", "sha2 0.8.2", @@ -9922,7 +10041,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10236,7 +10355,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek 4.0.0-rc.1", "rand_core 0.6.4", - "ring", + "ring 0.16.20", "rustc_version 0.4.0", "sha2 0.10.7", "subtle", @@ -10633,6 +10752,8 @@ name = "sp-core" version = "21.0.0" dependencies = [ "array-bytes", + "arrayvec 0.7.4", + "bandersnatch_vrfs", "bitflags", "blake2", "bounded-collections", @@ -10647,7 +10768,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin", + "merlin 2.0.1", "parity-scale-codec", "parking_lot 0.12.1", "paste", @@ -13028,7 +13149,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 0ecdb896199cb..b5af0bc90343a 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -26,9 +26,18 @@ sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } tempfile = "3.1.0" [features] -# This feature adds BLS crypto primitives. It should not be used in production since -# the BLS implementation and interface may still be subject to significant change. +# 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 = [ "sp-core/bls-experimental", "sp-keystore/bls-experimental", ] + +# 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 = [ + "sp-core/bandersnatch-experimental", + "sp-keystore/bandersnatch-experimental", +] diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 4167e486ecf62..97bc7c71a4a58 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -19,6 +19,8 @@ use parking_lot::RwLock; use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -234,6 +236,69 @@ impl Keystore for LocalKeystore { Ok(sig) } + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + /// Generate a new pair compatible with the 'bandersnatch' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + ) -> std::result::Result, TraitError> { + self.vrf_sign::(key_type, public, data) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> std::result::Result, TraitError> { + self.vrf_output::(key_type, public, input) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> std::result::Result, TraitError> { + let sig = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.ring_vrf_sign(data, prover)); + Ok(sig) + } + #[cfg(feature = "bls-experimental")] fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type) diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index eb7f253f1b955..f27ec881e1961 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -52,9 +52,18 @@ full_crypto = [ "sp-io/disable_oom", ] -# This feature adds BLS crypto primitives. It should not be used in production since -# the BLS implementation and interface may still be subject to significant change. +# 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 = [ "sp-core/bls-experimental", "sp-io/bls-experimental", ] + +# 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 = [ + "sp-core/bandersnatch-experimental", + "sp-io/bandersnatch-experimental", +] diff --git a/primitives/application-crypto/src/bandersnatch.rs b/primitives/application-crypto/src/bandersnatch.rs new file mode 100644 index 0000000000000..fc7383815d702 --- /dev/null +++ b/primitives/application-crypto/src/bandersnatch.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// 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. + +//! Bandersnatch VRF application crypto types. + +use crate::{KeyTypeId, RuntimePublic}; +pub use sp_core::bandersnatch::*; +use sp_std::vec::Vec; + +mod app { + crate::app_crypto!(super, sp_core::testing::BANDERSNATCH); +} + +#[cfg(feature = "full_crypto")] +pub use app::Pair as AppPair; +pub use app::{Public as AppPublic, Signature as AppSignature}; + +impl RuntimePublic for Public { + type Signature = Signature; + + /// Dummy implementation. Returns an empty vector. + fn all(_key_type: KeyTypeId) -> Vec { + Vec::new() + } + + fn generate_pair(key_type: KeyTypeId, seed: Option>) -> Self { + sp_io::crypto::bandersnatch_generate(key_type, seed) + } + + /// Dummy implementation. Returns `None`. + fn sign>(&self, _key_type: KeyTypeId, _msg: &M) -> Option { + None + } + + /// Dummy implementation. Returns `false`. + fn verify>(&self, _msg: &M, _signature: &Self::Signature) -> bool { + false + } + + fn to_raw_vec(&self) -> Vec { + sp_core::crypto::ByteArray::to_raw_vec(self) + } +} diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index 95a25c11a717d..5384220bc9ca3 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -43,6 +43,8 @@ pub use serde; #[doc(hidden)] pub use sp_std::{ops::Deref, vec::Vec}; +#[cfg(feature = "bandersnatch-experimental")] +pub mod bandersnatch; #[cfg(feature = "bls-experimental")] pub mod bls377; #[cfg(feature = "bls-experimental")] diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index 88d4bf36915d0..e9b1080f63d9c 100644 --- a/primitives/application-crypto/src/traits.rs +++ b/primitives/application-crypto/src/traits.rs @@ -23,7 +23,7 @@ use sp_core::crypto::Pair; use sp_core::crypto::{CryptoType, CryptoTypeId, IsWrappedBy, KeyTypeId, Public}; use sp_std::{fmt::Debug, vec::Vec}; -/// An application-specific cryptographic object. +/// Application-specific cryptographic object. /// /// Combines all the core types and constants that are defined by a particular /// cryptographic scheme when it is used in a specific application domain. @@ -31,7 +31,7 @@ use sp_std::{fmt::Debug, vec::Vec}; /// Typically, the implementers of this trait are its associated types themselves. /// This provides a convenient way to access generic information about the scheme /// given any of the associated types. -pub trait AppCrypto: 'static + Send + Sync + Sized + CryptoType + Clone { +pub trait AppCrypto: 'static + Sized + CryptoType { /// Identifier for application-specific key type. const ID: KeyTypeId; @@ -61,38 +61,30 @@ pub trait MaybeHash {} #[cfg(all(not(feature = "std"), not(feature = "full_crypto")))] impl MaybeHash for T {} -/// A application's public key. -pub trait AppPublic: - AppCrypto + Public + Ord + PartialOrd + Eq + PartialEq + Debug + MaybeHash + Codec -{ - /// The wrapped type which is just a plain instance of `Public`. - type Generic: IsWrappedBy - + Public - + Ord - + PartialOrd - + Eq - + PartialEq - + Debug - + MaybeHash - + Codec; -} - -/// A application's key pair. +/// Application-specific key pair. #[cfg(feature = "full_crypto")] -pub trait AppPair: AppCrypto + Pair::Public> { +pub trait AppPair: + AppCrypto + Pair::Public, Signature = ::Signature> +{ /// The wrapped type which is just a plain instance of `Pair`. type Generic: IsWrappedBy + Pair::Public as AppPublic>::Generic> + Pair::Signature as AppSignature>::Generic>; } -/// A application's signature. -pub trait AppSignature: AppCrypto + Eq + PartialEq + Debug { +/// Application-specific public key. +pub trait AppPublic: AppCrypto + Public + Debug + MaybeHash + Codec { + /// The wrapped type which is just a plain instance of `Public`. + type Generic: IsWrappedBy + Public + Debug + MaybeHash + Codec; +} + +/// Application-specific signature. +pub trait AppSignature: AppCrypto + Eq + PartialEq + Debug + Clone { /// The wrapped type which is just a plain instance of `Signature`. type Generic: IsWrappedBy + Eq + PartialEq + Debug; } -/// A runtime interface for a public key. +/// Runtime interface for a public key. pub trait RuntimePublic: Sized { /// The signature that will be generated when signing with the corresponding private key. type Signature: Debug + Eq + PartialEq + Clone; @@ -123,7 +115,7 @@ pub trait RuntimePublic: Sized { fn to_raw_vec(&self) -> Vec; } -/// A runtime interface for an application's public key. +/// Runtime interface for an application's public key. pub trait RuntimeAppPublic: Sized { /// An identifier for this application-specific key type. const ID: KeyTypeId; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 59bdea961cf8a..ee4bf8924186c 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -13,6 +13,7 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +arrayvec = { version = "0.7.2", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive","max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } @@ -53,8 +54,11 @@ merlin = { version = "2.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } sp-core-hashing = { version = "9.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../runtime-interface" } + # bls crypto w3f-bls = { version = "0.1.3", default-features = false, optional = true} +# bandersnatch crypto +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "c86ebd4", default-features = false, optional = true } [dev-dependencies] criterion = "0.4.0" @@ -71,12 +75,14 @@ bench = false [features] default = ["std"] std = [ + "arrayvec/std", "merlin/std", "full_crypto", "log/std", "thiserror", "lazy_static", "parking_lot", + "bandersnatch_vrfs/getrandom", "bounded-collections/std", "primitive-types/std", "primitive-types/serde", @@ -143,6 +149,12 @@ full_crypto = [ "sp-runtime-interface/disable_target_static_assertions", ] -# This feature adds BLS crypto primitives. It should not be used in production since -# the BLS implementation and interface may still be subject to significant change. +# 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 = ["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 = ["bandersnatch_vrfs"] diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs new file mode 100644 index 0000000000000..c3ba7f41058e9 --- /dev/null +++ b/primitives/core/src/bandersnatch.rs @@ -0,0 +1,1042 @@ +// This file is part of Substrate. + +// 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 traditional VRF or as an anonymized ring VRF. + +#[cfg(feature = "std")] +use crate::crypto::Ss58Codec; +use crate::crypto::{ + ByteArray, CryptoType, CryptoTypeId, Derive, Public as TraitPublic, UncheckedFrom, VrfPublic, +}; +#[cfg(feature = "full_crypto")] +use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError, VrfSecret}; + +use bandersnatch_vrfs::CanonicalSerialize; +#[cfg(feature = "full_crypto")] +use bandersnatch_vrfs::SecretKey; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use sp_runtime_interface::pass_by::PassByInner; +use sp_std::{boxed::Box, vec::Vec}; + +/// Identifier used to match public keys against bandersnatch-vrf keys. +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); + +/// Context used to produce a plain signature without any VRF input/output. +#[cfg(feature = "full_crypto")] +pub const SIGNING_CTX: &[u8] = b"SigningContext"; + +// Max ring domain size. +const RING_DOMAIN_SIZE: usize = 1024; + +#[cfg(feature = "full_crypto")] +const SEED_SERIALIZED_LEN: usize = 32; + +// Short-Weierstrass form serialized sizes. +const PUBLIC_SERIALIZED_LEN: usize = 33; +const SIGNATURE_SERIALIZED_LEN: usize = 65; +const PREOUT_SERIALIZED_LEN: usize = 33; +const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; +const RING_PROOF_SERIALIZED_LEN: usize = 592; + +// Max size of serialized ring-vrf context params. +// +// This size is dependent on the ring domain size and the actual value +// is equal to the SCALE encoded size of the `KZG` backend. +// +// Some values: +// ring_size → ~serialized_size +// 512 → 74 KB +// 1024 → 147 KB +// 2048 → 295 KB +// NOTE: This is quite big but looks like there is an upcoming fix +// in the backend. +const RING_CONTEXT_SERIALIZED_LEN: usize = 147752; + +/// Bandersnatch public key. +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive( + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Encode, + Decode, + PassByInner, + MaxEncodedLen, + TypeInfo, +)] +pub struct Public(pub [u8; PUBLIC_SERIALIZED_LEN]); + +impl UncheckedFrom<[u8; PUBLIC_SERIALIZED_LEN]> for Public { + fn unchecked_from(raw: [u8; PUBLIC_SERIALIZED_LEN]) -> Self { + Public(raw) + } +} + +impl AsRef<[u8; PUBLIC_SERIALIZED_LEN]> for Public { + fn as_ref(&self) -> &[u8; PUBLIC_SERIALIZED_LEN] { + &self.0 + } +} + +impl AsRef<[u8]> for Public { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for Public { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl TryFrom<&[u8]> for Public { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != PUBLIC_SERIALIZED_LEN { + return Err(()) + } + let mut r = [0u8; PUBLIC_SERIALIZED_LEN]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) + } +} + +impl ByteArray for Public { + const LEN: usize = PUBLIC_SERIALIZED_LEN; +} + +impl TraitPublic for Public {} + +impl CryptoType for Public { + #[cfg(feature = "full_crypto")] + type Pair = Pair; +} + +impl Derive for Public {} + +impl sp_std::fmt::Debug for Public { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_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 sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +/// Bandersnatch signature. +/// +/// The signature is created via the [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as `label`. +#[cfg_attr(feature = "full_crypto", derive(Hash))] +#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, PassByInner, MaxEncodedLen, TypeInfo)] +pub struct Signature([u8; SIGNATURE_SERIALIZED_LEN]); + +impl UncheckedFrom<[u8; SIGNATURE_SERIALIZED_LEN]> for Signature { + fn unchecked_from(raw: [u8; SIGNATURE_SERIALIZED_LEN]) -> Self { + Signature(raw) + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != SIGNATURE_SERIALIZED_LEN { + return Err(()) + } + let mut r = [0u8; SIGNATURE_SERIALIZED_LEN]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) + } +} + +impl ByteArray for Signature { + const LEN: usize = SIGNATURE_SERIALIZED_LEN; +} + +impl CryptoType for Signature { + #[cfg(feature = "full_crypto")] + type Pair = Pair; +} + +impl sp_std::fmt::Debug for Signature { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "{}", crate::hexdisplay::HexDisplay::from(&self.0)) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +/// The raw secret seed, which can be used to reconstruct the secret [`Pair`]. +#[cfg(feature = "full_crypto")] +type Seed = [u8; SEED_SERIALIZED_LEN]; + +/// Bandersnatch secret key. +#[cfg(feature = "full_crypto")] +#[derive(Clone)] +pub struct Pair { + secret: SecretKey, + seed: Seed, +} + +#[cfg(feature = "full_crypto")] +impl Pair { + /// Get the key seed. + pub fn seed(&self) -> Seed { + self.seed + } +} + +#[cfg(feature = "full_crypto")] +impl TraitPair for Pair { + type Seed = Seed; + type Public = Public; + type Signature = Signature; + + /// Make a new key pair from secret seed material. + /// + /// The slice must be 64 bytes long or it will return an error. + fn from_seed_slice(seed_slice: &[u8]) -> Result { + if seed_slice.len() != SEED_SERIALIZED_LEN { + return Err(SecretStringError::InvalidSeedLength) + } + let mut seed = [0; SEED_SERIALIZED_LEN]; + seed.copy_from_slice(seed_slice); + let secret = SecretKey::from_seed(&seed); + Ok(Pair { secret, seed }) + } + + /// Derive a child key from a series of given (hard) junctions. + /// + /// Soft junctions are not supported. + fn derive>( + &self, + path: Iter, + _seed: Option, + ) -> Result<(Pair, Option), DeriveError> { + let derive_hard = |seed, cc| -> Seed { + ("bandersnatch-vrf-HDKD", seed, cc).using_encoded(sp_core_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))) + } + + /// Get the public key. + fn public(&self) -> Public { + let public = self.secret.to_public(); + let mut raw = [0; PUBLIC_SERIALIZED_LEN]; + public + .serialize_compressed(raw.as_mut_slice()) + .expect("key buffer length is good; qed"); + Public::unchecked_from(raw) + } + + /// Sign raw data. + fn sign(&self, data: &[u8]) -> Signature { + let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data], None); + self.vrf_sign(&data).signature + } + + /// Verify a signature on a message. + /// + /// Returns `true` if the signature is good. + fn verify>(signature: &Signature, data: M, public: &Public) -> bool { + let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data.as_ref()], None); + let signature = + vrf::VrfSignature { signature: *signature, vrf_outputs: vrf::VrfIosVec::default() }; + public.vrf_verify(&data, &signature) + } + + /// Return a vector filled with seed raw data. + fn to_raw_vec(&self) -> Vec { + self.seed().to_vec() + } +} + +#[cfg(feature = "full_crypto")] +impl CryptoType for Pair { + type Pair = Pair; +} + +/// Bandersnatch VRF types and operations. +pub mod vrf { + use super::*; + use crate::{bounded::BoundedVec, crypto::VrfCrypto, ConstU32}; + use bandersnatch_vrfs::{ + CanonicalDeserialize, CanonicalSerialize, IntoVrfInput, Message, PublicKey, + ThinVrfSignature, Transcript, + }; + + /// Max number of inputs/outputs which can be handled by the VRF signing procedures. + /// The number is quite arbitrary and fullfils the current usage of the primitive. + /// If required it can be extended in the future. + pub const MAX_VRF_IOS: u32 = 3; + + /// Bounded vector used for VRF inputs and outputs. + /// + /// Can contain at most [`MAX_VRF_IOS`] elements. + pub type VrfIosVec = BoundedVec>; + + /// VRF input to construct a [`VrfOutput`] instance and embeddable within [`VrfSignData`]. + #[derive(Clone, Debug)] + pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); + + impl VrfInput { + /// Construct a new VRF input. + pub fn new(domain: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> Self { + let msg = Message { domain: domain.as_ref(), message: data.as_ref() }; + VrfInput(msg.into_vrf_input()) + } + } + + /// VRF (pre)output derived from [`VrfInput`] using a [`VrfSecret`]. + /// + /// This is used to produce an arbitrary number of verifiable *random* bytes. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct VrfOutput(pub(super) bandersnatch_vrfs::VrfPreOut); + + impl Encode for VrfOutput { + fn encode(&self) -> Vec { + let mut bytes = [0; PREOUT_SERIALIZED_LEN]; + self.0 + .serialize_compressed(bytes.as_mut_slice()) + .expect("preout serialization can't fail"); + bytes.encode() + } + } + + impl Decode for VrfOutput { + fn decode(i: &mut R) -> Result { + let buf = <[u8; PREOUT_SERIALIZED_LEN]>::decode(i)?; + let preout = bandersnatch_vrfs::VrfPreOut::deserialize_compressed(buf.as_slice()) + .map_err(|_| "vrf-preout decode error: bad preout")?; + Ok(VrfOutput(preout)) + } + } + + impl MaxEncodedLen for VrfOutput { + fn max_encoded_len() -> usize { + <[u8; PREOUT_SERIALIZED_LEN]>::max_encoded_len() + } + } + + impl TypeInfo for VrfOutput { + type Identity = [u8; PREOUT_SERIALIZED_LEN]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + /// A *Fiat-Shamir* transcript and a sequence of [`VrfInput`]s ready to be signed. + /// + /// The `transcript` will be used as messages for the *Fiat-Shamir* + /// transform part of the scheme. This data keeps the signature secure + /// but doesn't contribute to the actual VRF output. If unsure just give + /// it a unique label depending on the actual usage of the signing data. + /// + /// The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and which + /// are used to construct the [`VrfOutput`]s in the signature. + #[derive(Clone)] + pub struct VrfSignData { + /// VRF inputs to be signed. + pub vrf_inputs: VrfIosVec, + /// Associated Fiat-Shamir transcript. + pub transcript: Transcript, + } + + impl VrfSignData { + /// Construct a new data to be signed. + /// + /// The `transcript_data` is used to construct the *Fiat-Shamir* `Transcript`. + /// Fails if the `vrf_inputs` yields more elements than [`MAX_VRF_IOS`] + /// + /// Refer to the [`VrfSignData`] for more details about the usage of + /// `transcript_data` and `vrf_inputs` + pub fn new( + label: &'static [u8], + transcript_data: impl IntoIterator>, + vrf_inputs: impl IntoIterator, + ) -> Result { + let vrf_inputs: Vec = vrf_inputs.into_iter().collect(); + if vrf_inputs.len() > MAX_VRF_IOS as usize { + return Err(()) + } + Ok(Self::new_unchecked(label, transcript_data, vrf_inputs)) + } + + /// Construct a new data to be signed. + /// + /// The `transcript_data` is used to construct the *Fiat-Shamir* `Transcript`. + /// At most the first [`MAX_VRF_IOS`] elements of `vrf_inputs` are used. + /// + /// Refer to the [`VrfSignData`] for more details about the usage of + /// `transcript_data` and `vrf_inputs` + pub fn new_unchecked( + label: &'static [u8], + transcript_data: impl IntoIterator>, + vrf_inputs: impl IntoIterator, + ) -> Self { + let vrf_inputs: Vec = vrf_inputs.into_iter().collect(); + let vrf_inputs = VrfIosVec::truncate_from(vrf_inputs); + let mut transcript = Transcript::new_labeled(label); + transcript_data + .into_iter() + .for_each(|data| transcript.append_slice(data.as_ref())); + VrfSignData { transcript, vrf_inputs } + } + + /// Append a raw message to the transcript. + pub fn push_transcript_data(&mut self, data: &[u8]) { + self.transcript.append_slice(data); + } + + /// Append a [`VrfInput`] to the vrf inputs to be signed. + /// + /// On failure, gives back the [`VrfInput`] parameter. + pub fn push_vrf_input(&mut self, vrf_input: VrfInput) -> Result<(), VrfInput> { + self.vrf_inputs.try_push(vrf_input) + } + + /// Create challenge from the transcript contained within the signing data. + pub fn challenge(&self) -> [u8; N] { + let mut output = [0; N]; + let mut transcript = self.transcript.clone(); + let mut reader = transcript.challenge(b"Prehashed for bandersnatch"); + reader.read_bytes(&mut output); + output + } + } + + /// VRF signature. + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct VrfSignature { + /// VRF (pre)outputs. + pub vrf_outputs: VrfIosVec, + /// VRF signature. + pub signature: Signature, + } + + #[cfg(feature = "full_crypto")] + impl VrfCrypto for Pair { + type VrfInput = VrfInput; + type VrfOutput = VrfOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } + + #[cfg(feature = "full_crypto")] + impl VrfSecret for Pair { + fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { + const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); + // Workaround to overcome backend signature generic over the number of IOs. + match data.vrf_inputs.len() { + 0 => self.vrf_sign_gen::<0>(data), + 1 => self.vrf_sign_gen::<1>(data), + 2 => self.vrf_sign_gen::<2>(data), + 3 => self.vrf_sign_gen::<3>(data), + _ => unreachable!(), + } + } + + fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { + let output = self.secret.0.vrf_preout(&input.0); + VrfOutput(output) + } + } + + impl VrfCrypto for Public { + type VrfInput = VrfInput; + type VrfOutput = VrfOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } + + impl VrfPublic for Public { + fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { + const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); + let preouts_len = signature.vrf_outputs.len(); + if preouts_len != data.vrf_inputs.len() { + return false + } + // Workaround to overcome backend signature generic over the number of IOs. + match preouts_len { + 0 => self.vrf_verify_gen::<0>(data, signature), + 1 => self.vrf_verify_gen::<1>(data, signature), + 2 => self.vrf_verify_gen::<2>(data, signature), + 3 => self.vrf_verify_gen::<3>(data, signature), + _ => unreachable!(), + } + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + fn vrf_sign_gen(&self, data: &VrfSignData) -> VrfSignature { + let ios: Vec<_> = data + .vrf_inputs + .iter() + .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) + .collect(); + + let signature: ThinVrfSignature = + self.secret.sign_thin_vrf(data.transcript.clone(), ios.as_slice()); + + let mut sign_bytes = [0; SIGNATURE_SERIALIZED_LEN]; + signature + .signature + .serialize_compressed(sign_bytes.as_mut_slice()) + .expect("serialization can't fail"); + + let outputs: Vec<_> = signature.preoutputs.into_iter().map(VrfOutput).collect(); + let outputs = VrfIosVec::truncate_from(outputs); + VrfSignature { signature: Signature(sign_bytes), vrf_outputs: outputs } + } + + /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. + pub fn make_bytes( + &self, + context: &'static [u8], + input: &VrfInput, + ) -> [u8; N] { + let transcript = Transcript::new_labeled(context); + let inout = self.secret.clone().0.vrf_inout(input.0.clone()); + inout.vrf_output_bytes(transcript) + } + } + + impl Public { + fn vrf_verify_gen( + &self, + data: &VrfSignData, + signature: &VrfSignature, + ) -> bool { + let Ok(public) = PublicKey::deserialize_compressed(self.as_slice()) else { + return false + }; + + let Ok(preouts) = signature + .vrf_outputs + .iter() + .map(|o| o.0.clone()) + .collect::>() + .into_inner() + else { + return false + }; + + // Deserialize only the proof, the rest has already been deserialized + // This is another hack used because backend signature type is generic over + // the number of ios. + let Ok(signature) = + ThinVrfSignature::<0>::deserialize_compressed(signature.signature.as_ref()) + .map(|s| s.signature) + else { + return false + }; + let signature = ThinVrfSignature { signature, preoutputs: preouts }; + + let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); + + signature.verify_thin_vrf(data.transcript.clone(), inputs, &public).is_ok() + } + } + + impl VrfOutput { + /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. + pub fn make_bytes( + &self, + context: &'static [u8], + input: &VrfInput, + ) -> [u8; N] { + let transcript = Transcript::new_labeled(context); + let inout = + bandersnatch_vrfs::VrfInOut { input: input.0.clone(), preoutput: self.0.clone() }; + inout.vrf_output_bytes(transcript) + } + } +} + +/// Bandersnatch Ring-VRF types and operations. +pub mod ring_vrf { + use super::{vrf::*, *}; + pub use bandersnatch_vrfs::ring::{RingProof, RingProver, RingVerifier, KZG}; + use bandersnatch_vrfs::{CanonicalDeserialize, PedersenVrfSignature, PublicKey}; + + /// Context used to produce ring signatures. + #[derive(Clone)] + pub struct RingContext(KZG); + + impl RingContext { + /// Build an dummy instance used for testing purposes. + pub fn new_testing() -> Self { + Self(KZG::testing_kzg_setup([0; 32], RING_DOMAIN_SIZE as u32)) + } + + /// Get the keyset max size. + pub fn max_keyset_size(&self) -> usize { + self.0.max_keyset_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) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + for public_key in public_keys { + let pk = PublicKey::deserialize_compressed(public_key.as_slice()).ok()?; + pks.push(pk.0 .0.into()); + } + + let prover_key = self.0.prover_key(pks); + let ring_prover = self.0.init_ring_prover(prover_key, public_idx); + Some(ring_prover) + } + + /// Get ring verifier for the `public_keys` set. + pub fn verifier(&self, public_keys: &[Public]) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + for public_key in public_keys { + let pk = PublicKey::deserialize_compressed(public_key.as_slice()).ok()?; + pks.push(pk.0 .0.into()); + } + + let verifier_key = self.0.verifier_key(pks); + let ring_verifier = self.0.init_ring_verifier(verifier_key); + Some(ring_verifier) + } + } + + impl Encode for RingContext { + fn encode(&self) -> Vec { + let mut buf = Box::new([0; RING_CONTEXT_SERIALIZED_LEN]); + self.0 + .serialize_compressed(buf.as_mut_slice()) + .expect("preout serialization can't fail"); + buf.encode() + } + } + + impl Decode for RingContext { + fn decode(i: &mut R) -> Result { + let buf = >::decode(i)?; + let kzg = + KZG::deserialize_compressed(buf.as_slice()).map_err(|_| "KZG decode error")?; + Ok(RingContext(kzg)) + } + } + + impl MaxEncodedLen for RingContext { + fn max_encoded_len() -> usize { + <[u8; RING_CONTEXT_SERIALIZED_LEN]>::max_encoded_len() + } + } + + impl TypeInfo for RingContext { + type Identity = [u8; RING_CONTEXT_SERIALIZED_LEN]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + /// Ring VRF signature. + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct RingVrfSignature { + /// VRF (pre)outputs. + pub outputs: VrfIosVec, + /// Pedersen VRF signature. + signature: [u8; PEDERSEN_SIGNATURE_SERIALIZED_LEN], + /// Ring proof. + ring_proof: [u8; RING_PROOF_SERIALIZED_LEN], + } + + #[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 { + const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); + // Workaround to overcome backend signature generic over the number of IOs. + match data.vrf_inputs.len() { + 0 => self.ring_vrf_sign_gen::<0>(data, prover), + 1 => self.ring_vrf_sign_gen::<1>(data, prover), + 2 => self.ring_vrf_sign_gen::<2>(data, prover), + 3 => self.ring_vrf_sign_gen::<3>(data, prover), + _ => unreachable!(), + } + } + + fn ring_vrf_sign_gen( + &self, + data: &VrfSignData, + prover: &RingProver, + ) -> RingVrfSignature { + let ios: Vec<_> = data + .vrf_inputs + .iter() + .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) + .collect(); + + let ring_signature: bandersnatch_vrfs::RingVrfSignature = + self.secret.sign_ring_vrf(data.transcript.clone(), ios.as_slice(), prover); + + let outputs: Vec<_> = ring_signature.preoutputs.into_iter().map(VrfOutput).collect(); + let outputs = VrfIosVec::truncate_from(outputs); + + let mut signature = [0; PEDERSEN_SIGNATURE_SERIALIZED_LEN]; + ring_signature + .signature + .serialize_compressed(signature.as_mut_slice()) + .expect("ped-signature serialization can't fail"); + + let mut ring_proof = [0; RING_PROOF_SERIALIZED_LEN]; + ring_signature + .ring_proof + .serialize_compressed(ring_proof.as_mut_slice()) + .expect("ring-proof serialization can't fail"); + + RingVrfSignature { outputs, signature, ring_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 verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); + let preouts_len = self.outputs.len(); + if preouts_len != data.vrf_inputs.len() { + return false + } + // Workaround to overcome backend signature generic over the number of IOs. + match preouts_len { + 0 => self.verify_gen::<0>(data, verifier), + 1 => self.verify_gen::<1>(data, verifier), + 2 => self.verify_gen::<2>(data, verifier), + 3 => self.verify_gen::<3>(data, verifier), + _ => unreachable!(), + } + } + + fn verify_gen(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + let Ok(preoutputs) = self + .outputs + .iter() + .map(|o| o.0.clone()) + .collect::>() + .into_inner() + else { + return false + }; + + let Ok(signature) = + PedersenVrfSignature::deserialize_compressed(self.signature.as_slice()) + else { + return false + }; + + let Ok(ring_proof) = RingProof::deserialize_compressed(self.ring_proof.as_slice()) + else { + return false + }; + + let ring_signature = + bandersnatch_vrfs::RingVrfSignature { signature, preoutputs, ring_proof }; + + let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); + + ring_signature + .verify_ring_vrf(data.transcript.clone(), inputs, verifier) + .is_ok() + } + } +} + +#[cfg(test)] +mod tests { + use super::{ring_vrf::*, vrf::*, *}; + use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; + const DEV_SEED: &[u8; SEED_SERIALIZED_LEN] = &[0xcb; SEED_SERIALIZED_LEN]; + + #[allow(unused)] + fn b2h(bytes: &[u8]) -> String { + array_bytes::bytes2hex("", bytes) + } + + fn h2b(hex: &str) -> Vec { + array_bytes::hex2bytes_unchecked(hex) + } + + #[test] + fn assumptions_sanity_check() { + // Backend + let ring_ctx = RingContext::new_testing(); + let pair = SecretKey::from_seed(DEV_SEED); + let public = pair.to_public(); + + assert_eq!(public.0.size_of_serialized(), PUBLIC_SERIALIZED_LEN); + assert_eq!(ring_ctx.max_keyset_size(), RING_DOMAIN_SIZE - 257); + + // Wrapper + let inputs: Vec<_> = (0..MAX_VRF_IOS - 1).map(|_| VrfInput::new(b"", &[])).collect(); + let mut sign_data = VrfSignData::new(b"", &[b""], inputs).unwrap(); + let res = sign_data.push_vrf_input(VrfInput::new(b"", b"")); + assert!(res.is_ok()); + let res = sign_data.push_vrf_input(VrfInput::new(b"", b"")); + assert!(res.is_err()); + let inputs: Vec<_> = (0..MAX_VRF_IOS + 1).map(|_| VrfInput::new(b"", b"")).collect(); + let res = VrfSignData::new(b"mydata", &[b"tdata"], inputs); + assert!(res.is_err()); + } + + #[test] + fn derive_works() { + let pair = Pair::from_string(&format!("{}//Alice//Hard", DEV_PHRASE), None).unwrap(); + let known = h2b("2b340c18b94dc1916979cb83daf3ed4ac106742ddc06afc42cf26be3b18a523f80"); + 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 sign_verify() { + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + let msg = b"hello"; + + let signature = pair.sign(msg); + assert!(Pair::verify(&signature, msg, &public)); + } + + #[test] + fn vrf_sign_verify() { + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + let i3 = VrfInput::new(b"dom3", b"baz"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); + + let signature = pair.vrf_sign(&data); + + assert!(public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_sign_verify_bad_inputs() { + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"aaaa"], [i1.clone(), i2.clone()]); + let signature = pair.vrf_sign(&data); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"bbb"], [i1, i2.clone()]); + assert!(!public.vrf_verify(&data, &signature)); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"aaa"], [i2]); + assert!(!public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_make_bytes_matches() { + let pair = Pair::from_seed(DEV_SEED); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); + let signature = pair.vrf_sign(&data); + + let o10 = pair.make_bytes::<32>(b"ctx1", &i1); + let o11 = signature.vrf_outputs[0].make_bytes::<32>(b"ctx1", &i1); + assert_eq!(o10, o11); + + let o20 = pair.make_bytes::<48>(b"ctx2", &i2); + let o21 = signature.vrf_outputs[1].make_bytes::<48>(b"ctx2", &i2); + assert_eq!(o20, o21); + } + + #[test] + fn encode_decode_vrf_signature() { + // Transcript data is hashed together and signed. + // It doesn't contribute to serialized length. + let pair = Pair::from_seed(DEV_SEED); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let expected_len = + data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + SIGNATURE_SERIALIZED_LEN + 1; + assert_eq!(bytes.len(), expected_len); + + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], []); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn ring_vrf_sign_verify() { + let ring_ctx = RingContext::new_testing(); + + let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(DEV_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + let i3 = VrfInput::new(b"dom3", b"baz"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(signature.verify(&data, &verifier)); + } + + #[test] + fn ring_vrf_sign_verify_with_out_of_ring_key() { + let ring_ctx = RingContext::new_testing(); + + let pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + let pair = Pair::from_seed(DEV_SEED); + + // Just pick one index to patch with the actual public key + let i1 = VrfInput::new(b"dom1", b"foo"); + let data = VrfSignData::new_unchecked(b"mydata", Some(b"tdata"), Some(i1)); + + // pair.public != pks[0] + let prover = ring_ctx.prover(&pks, 0).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let verifier = ring_ctx.verifier(&pks).unwrap(); + assert!(!signature.verify(&data, &verifier)); + } + + #[test] + fn encode_decode_ring_vrf_signature() { + let ring_ctx = RingContext::new_testing(); + + let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(DEV_SEED); + + // Just pick one... + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + let i3 = VrfInput::new(b"dom3", b"baz"); + + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let expected = pair.ring_vrf_sign(&data, &prover); + + let bytes = expected.encode(); + + let expected_len = data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + + PEDERSEN_SIGNATURE_SERIALIZED_LEN + + RING_PROOF_SERIALIZED_LEN + + 1; + assert_eq!(bytes.len(), expected_len); + + let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); + assert_eq!(expected, decoded); + } + + #[test] + fn encode_decode_ring_vrf_context() { + let ctx1 = RingContext::new_testing(); + let enc1 = ctx1.encode(); + + assert_eq!(enc1.len(), RingContext::max_encoded_len()); + + let ctx2 = RingContext::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = ctx2.encode(); + + assert_eq!(enc1, enc2); + } +} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 91e5898fda8eb..6afe4b752a690 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -15,9 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] //! Cryptographic utilities. -// end::description[] use crate::{ed25519, sr25519}; #[cfg(feature = "std")] @@ -486,7 +484,7 @@ pub trait ByteArray: AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8], Error } /// Trait suitable for typical cryptographic key public type. -pub trait Public: CryptoType + ByteArray + Derive + PartialEq + Eq + Clone + Send {} +pub trait Public: CryptoType + ByteArray + Derive + PartialEq + Eq + Clone + Send + Sync {} /// An opaque 32-byte cryptographic identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo)] diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 143482e94cd12..05bc679386c3d 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -417,7 +417,7 @@ impl TraitPair for Pair { } /// Verify a signature on a message. Returns true if the signature is good. - fn verify>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { + fn verify>(sig: &Signature, message: M, public: &Public) -> bool { sig.recover(message).map(|actual| actual == *public).unwrap_or_default() } diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index d46f2b5043d61..151a7229315eb 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -421,7 +421,7 @@ impl TraitPair for Pair { /// Verify a signature on a message. /// /// Returns true if the signature is good. - fn verify>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { + fn verify>(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_ref()) else { return false }; public.verify(&signature, message.as_ref()).is_ok() diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index da3b438190954..3a0e1f33f16c9 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -55,6 +55,8 @@ pub mod crypto; pub mod hexdisplay; pub use paste; +#[cfg(feature = "bandersnatch-experimental")] +pub mod bandersnatch; #[cfg(feature = "bls-experimental")] pub mod bls; pub mod defer; diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index bcd64ed1cc3cf..ffa52ef97d1f5 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -504,7 +504,7 @@ impl TraitPair for Pair { self.0.sign(context.bytes(message)).into() } - fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { + fn verify>(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() @@ -568,7 +568,7 @@ pub mod vrf { impl VrfTranscript { /// Build a new transcript instance. /// - /// Each `data` element is a tuple `(domain, message)` composing the transcipt. + /// 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)); diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index 6faf4ffa3042a..25f5f9012c996 100644 --- a/primitives/core/src/testing.rs +++ b/primitives/core/src/testing.rs @@ -21,10 +21,12 @@ use crate::crypto::KeyTypeId; /// Key type for generic Ed25519 key. pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25"); -/// Key type for generic Sr 25519 key. +/// 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. diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 141e74d52632e..8590778714121 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -93,8 +93,16 @@ disable_allocator = [] # runtime without first upgrading your host client! improved_panic_error_reporting = [] -# This feature adds BLS crypto primitives. It should not be used in production since -# the BLS implementation and interface may still be subject to significant change. +# 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 = [ "sp-keystore/bls-experimental", ] + +# 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 = [ + "sp-keystore/bandersnatch-experimental", +] diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 4540fa500330e..0bc434a2a59fc 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -92,6 +92,8 @@ use sp_core::{ #[cfg(feature = "std")] use sp_keystore::KeystoreExt; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; use sp_core::{ crypto::KeyTypeId, ecdsa, ed25519, @@ -1190,13 +1192,13 @@ pub trait Crypto { Ok(pubkey.serialize()) } - #[cfg(feature = "bls-experimental")] /// Generate an `bls12-377` key for the given key type using an optional `seed` and /// store it in the keystore. /// /// The `seed` needs to be a valid utf8. /// /// Returns the public key. + #[cfg(feature = "bls-experimental")] fn bls377_generate(&mut self, id: KeyTypeId, seed: Option>) -> bls377::Public { let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); self.extension::() @@ -1204,6 +1206,25 @@ pub trait Crypto { .bls377_generate_new(id, seed) .expect("`bls377_generate` failed") } + + /// Generate a `bandersnatch` key pair for the given key type using an optional + /// `seed` and store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Returns the public key. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_generate( + &mut self, + id: KeyTypeId, + seed: Option>, + ) -> bandersnatch::Public { + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); + self.extension::() + .expect("No `keystore` associated for the current context!") + .bandersnatch_generate_new(id, seed) + .expect("`bandernatch_generate` failed") + } } /// Interface that provides functions for hashing with different algorithms. diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 2b08755dba1ad..8e9f793636066 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -18,3 +18,9 @@ lazy_static = "1.4.0" strum = { version = "0.24.1", features = ["derive"], default-features = false } sp-core = { version = "21.0.0", path = "../core" } sp-runtime = { version = "24.0.0", path = "../runtime" } + +[features] +# 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 = ["sp-core/bandersnatch-experimental"] diff --git a/primitives/keyring/src/bandersnatch.rs b/primitives/keyring/src/bandersnatch.rs new file mode 100644 index 0000000000000..8de6786a6fbf6 --- /dev/null +++ b/primitives/keyring/src/bandersnatch.rs @@ -0,0 +1,209 @@ +// This file is part of Substrate. + +// 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 set of well-known keys used for testing. + +pub use sp_core::bandersnatch; +use sp_core::{ + bandersnatch::{Pair, Public, Signature}, + crypto::UncheckedFrom, + ByteArray, Pair as PairT, +}; + +use lazy_static::lazy_static; +use std::{collections::HashMap, ops::Deref, sync::Mutex}; + +/// Set of test accounts. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] +pub enum Keyring { + Alice, + Bob, + Charlie, + Dave, + Eve, + Ferdie, + One, + Two, +} + +const PUBLIC_RAW_LEN: usize = ::LEN; + +impl Keyring { + pub fn from_public(who: &Public) -> Option { + Self::iter().find(|&k| &Public::from(k) == who) + } + + pub fn from_raw_public(who: [u8; PUBLIC_RAW_LEN]) -> Option { + Self::from_public(&Public::unchecked_from(who)) + } + + pub fn to_raw_public(self) -> [u8; PUBLIC_RAW_LEN] { + *Public::from(self).as_ref() + } + + pub fn to_raw_public_vec(self) -> Vec { + Public::from(self).to_raw_vec() + } + + pub fn sign(self, msg: &[u8]) -> Signature { + Pair::from(self).sign(msg) + } + + pub fn pair(self) -> Pair { + Pair::from_string(&format!("//{}", <&'static str>::from(self)), None) + .expect("static values are known good; qed") + } + + /// Returns an iterator over all test accounts. + pub fn iter() -> impl Iterator { + ::iter() + } + + pub fn public(self) -> Public { + self.pair().public() + } + + pub fn to_seed(self) -> String { + format!("//{}", self) + } + + /// Create a crypto `Pair` from a numeric value. + pub fn numeric(idx: usize) -> Pair { + Pair::from_string(&format!("//{}", idx), None).expect("numeric values are known good; qed") + } +} + +impl From for &'static str { + fn from(k: Keyring) -> Self { + match k { + Keyring::Alice => "Alice", + Keyring::Bob => "Bob", + Keyring::Charlie => "Charlie", + Keyring::Dave => "Dave", + Keyring::Eve => "Eve", + Keyring::Ferdie => "Ferdie", + Keyring::One => "One", + Keyring::Two => "Two", + } + } +} + +#[derive(Debug)] +pub struct ParseKeyringError; + +impl std::fmt::Display for ParseKeyringError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ParseKeyringError") + } +} + +impl std::str::FromStr for Keyring { + type Err = ParseKeyringError; + + fn from_str(s: &str) -> Result::Err> { + match s { + "Alice" => Ok(Keyring::Alice), + "Bob" => Ok(Keyring::Bob), + "Charlie" => Ok(Keyring::Charlie), + "Dave" => Ok(Keyring::Dave), + "Eve" => Ok(Keyring::Eve), + "Ferdie" => Ok(Keyring::Ferdie), + "One" => Ok(Keyring::One), + "Two" => Ok(Keyring::Two), + _ => Err(ParseKeyringError), + } + } +} + +lazy_static! { + static ref PRIVATE_KEYS: Mutex> = + Mutex::new(Keyring::iter().map(|who| (who, who.pair())).collect()); + static ref PUBLIC_KEYS: HashMap = PRIVATE_KEYS + .lock() + .unwrap() + .iter() + .map(|(&who, pair)| (who, pair.public())) + .collect(); +} + +impl From for Public { + fn from(k: Keyring) -> Self { + *(*PUBLIC_KEYS).get(&k).unwrap() + } +} + +impl From for Pair { + fn from(k: Keyring) -> Self { + k.pair() + } +} + +impl From for [u8; PUBLIC_RAW_LEN] { + fn from(k: Keyring) -> Self { + *(*PUBLIC_KEYS).get(&k).unwrap().as_ref() + } +} + +impl From for &'static [u8; PUBLIC_RAW_LEN] { + fn from(k: Keyring) -> Self { + PUBLIC_KEYS.get(&k).unwrap().as_ref() + } +} + +impl AsRef<[u8; PUBLIC_RAW_LEN]> for Keyring { + fn as_ref(&self) -> &[u8; PUBLIC_RAW_LEN] { + PUBLIC_KEYS.get(self).unwrap().as_ref() + } +} + +impl AsRef for Keyring { + fn as_ref(&self) -> &Public { + PUBLIC_KEYS.get(self).unwrap() + } +} + +impl Deref for Keyring { + type Target = [u8; PUBLIC_RAW_LEN]; + fn deref(&self) -> &[u8; PUBLIC_RAW_LEN] { + PUBLIC_KEYS.get(self).unwrap().as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::{bandersnatch::Pair, Pair as PairT}; + + #[test] + fn should_work() { + assert!(Pair::verify( + &Keyring::Alice.sign(b"I am Alice!"), + b"I am Alice!", + &Keyring::Alice.public(), + )); + assert!(!Pair::verify( + &Keyring::Alice.sign(b"I am Alice!"), + b"I am Bob!", + &Keyring::Alice.public(), + )); + assert!(!Pair::verify( + &Keyring::Alice.sign(b"I am Alice!"), + b"I am Alice!", + &Keyring::Bob.public(), + )); + } +} diff --git a/primitives/keyring/src/lib.rs b/primitives/keyring/src/lib.rs index 7432aff12544a..1db18f7edbdc8 100644 --- a/primitives/keyring/src/lib.rs +++ b/primitives/keyring/src/lib.rs @@ -23,11 +23,17 @@ pub mod sr25519; /// Test account crypto for ed25519. pub mod ed25519; +/// Test account crypto for bandersnatch. +#[cfg(feature = "bandersnatch-experimental")] +pub mod bandersnatch; + /// Convenience export: Sr25519's Keyring is exposed as `AccountKeyring`, /// since it tends to be used for accounts (although it may also be used /// by authorities). pub use sr25519::Keyring as AccountKeyring; +#[cfg(feature = "bandersnatch-experimental")] +pub use bandersnatch::Keyring as BandersnatchKeyring; pub use ed25519::Keyring as Ed25519Keyring; pub use sr25519::Keyring as Sr25519Keyring; diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 1ae4f782042ce..7e551b7cbf268 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -31,6 +31,12 @@ std = [ "sp-externalities/std", ] -# This feature adds BLS crypto primitives. It should not be used in production since -# the BLS implementation and interface may still be subject to significant change. +# 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 = ["sp-core/bls-experimental"] + +# 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 = ["sp-core/bandersnatch-experimental"] diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 07583d11d527a..82062fe7b40a7 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -19,6 +19,8 @@ pub mod testing; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -174,6 +176,91 @@ pub trait Keystore: Send + Sync { msg: &[u8; 32], ) -> Result, Error>; + /// Returns all the bandersnatch public keys for the given key type. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec; + + /// Generate a new bandersnatch key pair for the given key type and an optional seed. + /// + /// Returns an `bandersnatch::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result; + + /// Generate an bandersnatch signature for a given message. + /// + /// Receives [`KeyTypeId`] and an [`bandersnatch::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns an [`bandersnatch::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> Result, Error>; + + /// Generate a bandersnatch VRF signature for the given data. + /// + /// Receives [`KeyTypeId`] and an [`bandersnatch::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + ) -> Result, Error>; + + /// Generate a bandersnatch VRF (pre)output for a given input data. + /// + /// Receives [`KeyTypeId`] and an [`bandersnatch::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> Result, Error>; + + /// Generate a bandersnatch ring-VRF signature for the given data. + /// + /// Receives [`KeyTypeId`] and an [`bandersnatch::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Also takes a [`bandersnatch::ring_vrf::RingProver`] instance obtained from + /// a valid [`bandersnatch::ring_vrf::RingContext`]. + /// + /// The ring signature is verifiable if the public key corresponding to the + /// signing [`bandersnatch::Pair`] is part of the ring from which the + /// [`bandersnatch::ring_vrf::RingProver`] has been constructed. + /// If not, the produced signature is just useless. + /// + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> Result, Error>; + /// Returns all bls12-381 public keys for the given key type. #[cfg(feature = "bls-experimental")] fn bls381_public_keys(&self, id: KeyTypeId) -> Vec; @@ -258,6 +345,7 @@ pub trait Keystore: Send + Sync { /// - sr25519 /// - ed25519 /// - ecdsa + /// - bandersnatch /// - bls381 /// - bls377 /// @@ -291,6 +379,12 @@ pub trait Keystore: Send + Sync { self.ecdsa_sign(id, &public, msg)?.map(|s| s.encode()) }, + #[cfg(feature = "bandersnatch-experimental")] + bandersnatch::CRYPTO_ID => { + let public = bandersnatch::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.bandersnatch_sign(id, &public, msg)?.map(|s| s.encode()) + }, #[cfg(feature = "bls-experimental")] bls381::CRYPTO_ID => { let public = bls381::Public::from_slice(public) @@ -400,16 +494,59 @@ impl Keystore for Arc { (**self).ecdsa_sign_prehashed(key_type, public, msg) } - fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { - (**self).insert(key_type, suri, public) + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + (**self).bandersnatch_public_keys(key_type) } - fn keys(&self, key_type: KeyTypeId) -> Result>, Error> { - (**self).keys(key_type) + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).bandersnatch_generate_new(key_type, seed) } - fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { - (**self).has_keys(public_keys) + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).bandersnatch_sign(key_type, public, msg) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + ) -> Result, Error> { + (**self).bandersnatch_vrf_sign(key_type, public, input) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> Result, Error> { + (**self).bandersnatch_vrf_output(key_type, public, input) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> Result, Error> { + (**self).bandersnatch_ring_vrf_sign(key_type, public, input, prover) } #[cfg(feature = "bls-experimental")] @@ -459,6 +596,18 @@ impl Keystore for Arc { ) -> Result, Error> { (**self).bls377_sign(key_type, public, msg) } + + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { + (**self).insert(key_type, suri, public) + } + + fn keys(&self, key_type: KeyTypeId) -> Result>, Error> { + (**self).keys(key_type) + } + + fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { + (**self).has_keys(public_keys) + } } /// A shared pointer to a keystore implementation. diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index e18931a7af883..efa35fd24bf46 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -19,6 +19,8 @@ use crate::{Error, Keystore, KeystorePtr}; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -214,6 +216,64 @@ impl Keystore for MemoryKeystore { Ok(sig) } + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + self.generate_new::(key_type, seed) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + ) -> Result, Error> { + self.vrf_sign::(key_type, public, data) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> Result, Error> { + let sig = self + .pair::(key_type, public) + .map(|pair| pair.ring_vrf_sign(data, prover)); + Ok(sig) + } + + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> Result, Error> { + self.vrf_output::(key_type, public, input) + } + #[cfg(feature = "bls-experimental")] fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type) @@ -330,7 +390,7 @@ mod tests { } #[test] - fn vrf_sign() { + fn sr25519_vrf_sign() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -359,7 +419,7 @@ mod tests { } #[test] - fn vrf_output() { + fn sr25519_vrf_output() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -406,4 +466,69 @@ mod tests { let res = store.ecdsa_sign_prehashed(ECDSA, &pair.public(), &msg).unwrap(); assert!(res.is_some()); } + + #[test] + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_vrf_sign() { + use sp_core::testing::BANDERSNATCH; + + let store = MemoryKeystore::new(); + + let secret_uri = "//Alice"; + let key_pair = + bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); + + let in1 = bandersnatch::vrf::VrfInput::new("in", "foo"); + let sign_data = + bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", Some("m1"), Some(in1)); + + let result = store.bandersnatch_vrf_sign(BANDERSNATCH, &key_pair.public(), &sign_data); + assert!(result.unwrap().is_none()); + + store + .insert(BANDERSNATCH, secret_uri, key_pair.public().as_ref()) + .expect("Inserts unknown key"); + + let result = store.bandersnatch_vrf_sign(BANDERSNATCH, &key_pair.public(), &sign_data); + + assert!(result.unwrap().is_some()); + } + + #[test] + #[cfg(feature = "bandersnatch-experimental")] + fn bandersnatch_ring_vrf_sign() { + use sp_core::testing::BANDERSNATCH; + + let store = MemoryKeystore::new(); + + let ring_ctx = bandersnatch::ring_vrf::RingContext::new_testing(); + + let mut pks: Vec<_> = (0..16) + .map(|i| bandersnatch::Pair::from_seed(&[i as u8; 32]).public()) + .collect(); + + let prover_idx = 3; + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + + let secret_uri = "//Alice"; + let pair = bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); + pks[prover_idx] = pair.public(); + + let in1 = bandersnatch::vrf::VrfInput::new("in1", "foo"); + let sign_data = + bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", &["m1", "m2"], [in1]); + + let result = + store.bandersnatch_ring_vrf_sign(BANDERSNATCH, &pair.public(), &sign_data, &prover); + assert!(result.unwrap().is_none()); + + store + .insert(BANDERSNATCH, secret_uri, pair.public().as_ref()) + .expect("Inserts unknown key"); + + let result = + store.bandersnatch_ring_vrf_sign(BANDERSNATCH, &pair.public(), &sign_data, &prover); + + assert!(result.unwrap().is_some()); + } }