diff --git a/Cargo.lock b/Cargo.lock index b46cf17e6a4a4..6228ef0a6ddcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -384,6 +396,21 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf#4957177a717c7555c8df2869012201017b62e66b" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "ark-transcript", + "digest 0.10.6", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -417,6 +444,19 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf#4957177a717c7555c8df2869012201017b62e66b" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "digest 0.10.6", + "rand_core 0.6.4", + "sha3", +] + [[package]] name = "array-bytes" version = "4.2.0" @@ -676,6 +716,26 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bandersnatch_vrfs" +version = "0.0.1" +source = "git+https://github.com/w3f/ring-vrf#4957177a717c7555c8df2869012201017b62e66b" +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_core 0.6.4", + "ring 0.1.0", + "sha2 0.10.6", + "zeroize", +] + [[package]] name = "base-x" version = "0.2.11" @@ -1304,6 +1364,20 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "common" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#1e42bb632263f4dff86b400ec9a13af21db72360" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -2038,6 +2112,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#4957177a717c7555c8df2869012201017b62e66b" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-secret-scalar", + "ark-serialize", + "ark-std", + "ark-transcript", + "arrayvec 0.7.2", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -2367,6 +2457,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "fflonk" +version = "0.1.0" +source = "git+https://github.com/w3f/fflonk#f60bc946e2a4340b1c2d00d30c654e82a5887983" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "merlin 3.0.0", +] + [[package]] name = "fiat-crypto" version = "0.1.20" @@ -4459,7 +4562,7 @@ dependencies = [ "libp2p-core 0.39.2", "libp2p-identity", "rcgen 0.10.0", - "ring", + "ring 0.16.20", "rustls 0.20.8", "thiserror", "webpki 0.22.0", @@ -4896,6 +4999,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" @@ -8161,7 +8276,7 @@ checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" dependencies = [ "bytes", "rand 0.8.5", - "ring", + "ring 0.16.20", "rustc-hash", "rustls 0.20.8", "slab", @@ -8311,7 +8426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", - "ring", + "ring 0.16.20", "time 0.3.21", "x509-parser 0.13.2", "yasna", @@ -8324,7 +8439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", - "ring", + "ring 0.16.20", "time 0.3.21", "yasna", ] @@ -8465,6 +8580,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#1e42bb632263f4dff86b400ec9a13af21db72360" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "common", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "ring" version = "0.16.20" @@ -8632,7 +8762,7 @@ checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.1", "log", - "ring", + "ring 0.16.20", "sct 0.6.1", "webpki 0.21.4", ] @@ -8644,7 +8774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", - "ring", + "ring 0.16.20", "sct 0.7.0", "webpki 0.22.0", ] @@ -10232,7 +10362,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", @@ -10258,7 +10388,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10268,7 +10398,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10606,7 +10736,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.6", "subtle", @@ -10904,6 +11034,8 @@ name = "sp-core" version = "7.0.0" dependencies = [ "array-bytes 4.2.0", + "arrayvec 0.7.2", + "bandersnatch_vrfs", "bitflags", "blake2", "bounded-collections", @@ -10919,7 +11051,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin", + "merlin 2.0.1", "parity-scale-codec", "parking_lot 0.12.1", "paste", @@ -11623,7 +11755,7 @@ dependencies = [ "lazy_static", "md-5", "rand 0.8.5", - "ring", + "ring 0.16.20", "subtle", "thiserror", "tokio", @@ -12645,7 +12777,7 @@ dependencies = [ "log", "md-5", "rand 0.8.5", - "ring", + "ring 0.16.20", "stun", "thiserror", "tokio", @@ -13342,7 +13474,7 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -13352,7 +13484,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -13381,7 +13513,7 @@ dependencies = [ "rand 0.8.5", "rcgen 0.9.3", "regex", - "ring", + "ring 0.16.20", "rtcp", "rtp", "rustls 0.19.1", @@ -13446,7 +13578,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rcgen 0.9.3", - "ring", + "ring 0.16.20", "rustls 0.19.1", "sec1 0.3.0", "serde", @@ -13893,7 +14025,7 @@ dependencies = [ "lazy_static", "nom", "oid-registry 0.4.0", - "ring", + "ring 0.16.20", "rusticata-macros", "thiserror", "time 0.3.21", diff --git a/Cargo.toml b/Cargo.toml index 7563a4a643286..06740f2639d05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -334,3 +334,6 @@ inherits = "release" lto = "fat" # https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units codegen-units = 1 + +#[patch."https://github.com/w3f/ring-vrf"] +# bandersnatch_vrfs = { path = "/mnt/ssd/users/develop/w3f/ring-vrf/bandersnatch_vrfs" } diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 81e047e90ab31..be62b948b703d 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -26,9 +26,18 @@ sp-keystore = { version = "0.13.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 change. bls-experimental = [ - "sp-core/bls-experimental", - "sp-keystore/bls-experimental", + "sp-core/bls-experimental", + "sp-keystore/bls-experimental", +] + +# This feature adds Bandersnatch-VRFs crypto primitives. +# It should not be used in production since the implementation +# and interface may still be subject to significant change. +bsnvrf-experimental = [ + "sp-core/bsnvrf-experimental", + "sp-keystore/bsnvrf-experimental", ] diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 4167e486ecf62..3eadb31845984 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 = "bsnvrf-experimental")] +use sp_core::bsnvrf; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -134,7 +136,7 @@ impl Keystore for LocalKeystore { self.public_keys::(key_type) } - /// Generate a new pair compatible with the 'ed25519' signature scheme. + /// Generate a new key pair. /// /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. fn sr25519_generate_new( @@ -176,7 +178,7 @@ impl Keystore for LocalKeystore { self.public_keys::(key_type) } - /// Generate a new pair compatible with the 'sr25519' signature scheme. + /// Generate a new key pair. /// /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. fn ed25519_generate_new( @@ -200,7 +202,7 @@ impl Keystore for LocalKeystore { self.public_keys::(key_type) } - /// Generate a new pair compatible with the 'ecdsa' signature scheme. + /// Generate a new key pair. /// /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. fn ecdsa_generate_new( @@ -234,13 +236,60 @@ impl Keystore for LocalKeystore { Ok(sig) } + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate a new key pair. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bsnvrf_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + data: &bsnvrf::vrf::VrfSignData, + ) -> std::result::Result, TraitError> { + self.vrf_sign::(key_type, public, data) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_vrf_output( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + input: &bsnvrf::vrf::VrfInput, + ) -> std::result::Result, TraitError> { + 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) } #[cfg(feature = "bls-experimental")] - /// Generate a new pair compatible with the 'bls381' signature scheme. + /// Generate a new key pair. /// /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. fn bls381_generate_new( @@ -267,7 +316,7 @@ impl Keystore for LocalKeystore { } #[cfg(feature = "bls-experimental")] - /// Generate a new pair compatible with the 'bls377' signature scheme. + /// Generate a new key-pair. /// /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. fn bls377_generate_new( diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index 37b58bc2bf649..3f5f2379b3430 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -44,3 +44,11 @@ full_crypto = [ "sp-io/disable_panic_handler", "sp-io/disable_oom", ] + +# This feature adds Bandersnatch-VRFs crypto primitives. +# It should not be used in production since the implementation +# and interface may still be subject to significant change. +bsnvrf-experimental = [ + "sp-core/bsnvrf-experimental", + "sp-io/bsnvrf-experimental", +] diff --git a/primitives/application-crypto/src/bsnvrf.rs b/primitives/application-crypto/src/bsnvrf.rs new file mode 100644 index 0000000000000..86a9b11eed0b5 --- /dev/null +++ b/primitives/application-crypto/src/bsnvrf.rs @@ -0,0 +1,54 @@ +// 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::bsnvrf::*; +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; + + fn all(_key_type: KeyTypeId) -> Vec { + vec![] + } + + fn generate_pair(key_type: KeyTypeId, seed: Option>) -> Self { + sp_io::crypto::bsnvrf_generate(key_type, seed) + } + + fn sign>(&self, _key_type: KeyTypeId, _msg: &M) -> Option { + None + } + + 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 3e8f2f5a77b3a..281e97a549dee 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -41,16 +41,19 @@ pub use serde; #[doc(hidden)] pub use sp_std::{ops::Deref, vec::Vec}; +pub use traits::*; + +mod traits; + #[cfg(feature = "bls-experimental")] pub mod bls377; #[cfg(feature = "bls-experimental")] pub mod bls381; +#[cfg(feature = "bsnvrf-experimental")] +pub mod bsnvrf; pub mod ecdsa; pub mod ed25519; pub mod sr25519; -mod traits; - -pub use traits::*; /// Declares `Public`, `Pair` and `Signature` types which are functionally equivalent /// to the corresponding types defined by `$module` but are new application-specific diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index 88d4bf36915d0..d8869f19d0dab 100644 --- a/primitives/application-crypto/src/traits.rs +++ b/primitives/application-crypto/src/traits.rs @@ -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 + Send + Sized + CryptoType + Clone { /// Identifier for application-specific key type. const ID: KeyTypeId; diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index dc161525a8511..397eafc882241 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -324,6 +324,7 @@ where /// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. #[derive(Decode, Encode, PartialEq, TypeInfo)] pub struct OpaqueKeyOwnershipProof(Vec); + impl OpaqueKeyOwnershipProof { /// Create a new `OpaqueKeyOwnershipProof` using the given encoded /// representation. diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 0c203a03d904d..e39f9377b69ad 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.2.2", 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 } @@ -55,6 +56,9 @@ sp-runtime-interface = { version = "7.0.0", default-features = false, path = ".. # bls crypto w3f-bls = { version = "0.1.3", default-features = false, optional = true} +# bandersnatch vrfs +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", default-features = false, optional = true } + [dev-dependencies] sp-serializer = { version = "4.0.0-dev", path = "../serializer" } rand = "0.8.5" @@ -73,12 +77,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", @@ -129,6 +135,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 change. bls-experimental = ["w3f-bls"] + +# This feature adds Bandersnatch-VRFs crypto primitives. +# It should not be used in production since the implementation +# and interface may still be subject to significant change. +bsnvrf-experimental = ["bandersnatch_vrfs"] diff --git a/primitives/core/src/bsnvrf.rs b/primitives/core/src/bsnvrf.rs new file mode 100644 index 0000000000000..1304c418047f0 --- /dev/null +++ b/primitives/core/src/bsnvrf.rs @@ -0,0 +1,692 @@ +// 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. + +//! VRF defined using Bandersnatch, an elliptic curve built over the BLS12-381 scalar field. + +#[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}; + +#[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::vec::Vec; + +/// Identifier used to match public keys against bandersnatch-vrf keys. +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bs38"); + +#[cfg(feature = "full_crypto")] +const SIGNING_CTX: &[u8] = b"SigningContext"; +#[cfg(feature = "full_crypto")] +const SEED_SERIALIZED_LEN: usize = 32; +const PUBLIC_SERIALIZED_LEN: usize = 32; +const SIGNATURE_SERIALIZED_LEN: usize = 64; + +/// XXX. +#[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.0), &s[0..8]) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +/// Signature without attached VRF pre-out. +#[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 recreate the `Pair`. +#[cfg(feature = "full_crypto")] +type Seed = [u8; SEED_SERIALIZED_LEN]; + +/// Keypair +#[cfg(feature = "full_crypto")] +#[derive(Clone)] +pub struct Pair(SecretKey); + +#[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_raw = [0; SEED_SERIALIZED_LEN]; + seed_raw.copy_from_slice(seed_slice); + let secret = SecretKey::from_seed(&seed_raw); + Ok(Pair(secret)) + } + + /// 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> { + // TODO davxy is this good? + let derive_hard_junction = |secret_seed, cc| -> Seed { + ("bandersnatch-vrf-HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) + }; + + let mut acc = [0; SEED_SERIALIZED_LEN]; + 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 { + let public = self.0.to_public(); + let mut raw = [0; PUBLIC_SERIALIZED_LEN]; + public.0.serialize(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(SIGNING_CTX, &[data], vrf::VrfIosVec::default()); + self.vrf_sign(&data).signature + } + + /// Verify a signature on a message. + /// + /// Returns true if the signature is good. + fn verify>(signature: &Self::Signature, data: M, public: &Self::Public) -> bool { + let data = vrf::VrfSignData::new(SIGNING_CTX, &[data.as_ref()], vrf::VrfIosVec::default()); + let signature = + vrf::VrfSignature { signature: *signature, vrf_outputs: vrf::VrfIosVec::default() }; + public.vrf_verify(&data, &signature) + } + + /// Return a vec filled with seed raw data. + fn to_raw_vec(&self) -> Vec { + // TODO davxy: makes sense??? Should we returne the seed or serialized secret key? + // If we return the serialized secret there is no method to reconstruct if ... + // unimplemented!() + panic!() + } +} + +#[cfg(feature = "full_crypto")] +impl CryptoType for Pair { + type Pair = Pair; +} + +/// VRF related 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, + }; + + const PREOUT_SERIALIZED_LEN: usize = 32; + + /// Max number of VRF inputs/outputs per sign operation. + /// In other words, the max number of `VrfInput`s and `VrfOutput`s embeddable + /// in `VrfSignData` and `VrfSignature`, respectively. + pub const MAX_VRF_IOS: u32 = 3; + + pub(super) type VrfIosVec = BoundedVec>; + + /// Input used for VRF operations. + #[derive(Clone)] + pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); + + impl VrfInput { + /// Build a new VRF input. + /// + /// Each message tuple has the form: (domain, data). + // TODO davxy (temporary hack) + // `bandersnatch_vrfs::Message` maps to a single (domain, data) tuple. + // We need something to push multiple messages together with a `label`. + // One solution is to construct the labeled Transcript here and then use + // `dleq_vrf::into_vrf_input()`. + // But has been commented out: + // https://github.com/w3f/ring-vrf/blob/8ab7b7b56e844f80b76afb1742b201fd69fb6046/dleq_vrf/src/vrf.rs#L38-L55 + pub fn new(label: &'static [u8], messages: &[(&[u8], &[u8])]) -> Self { + let mut buf = Vec::new(); + messages.into_iter().for_each(|(domain, message)| { + buf.extend_from_slice(domain); + buf.extend_from_slice(message); + }); + let msg = Message { domain: label, message: buf.as_slice() }; + VrfInput(msg.into_vrf_input()) + } + } + + /// VRF Pre-output derived from some `VrfInput` using a `VrfSecret`. + /// + /// This can be used to generate an arbitrary number of bytes va the `make_bytes` method. + #[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. + pub struct VrfSignData { + /// Associated Fiat-Shamir transcript + pub transcript: Transcript, + /// VRF inputs to be signed. + pub vrf_inputs: VrfIosVec, + } + + impl VrfSignData { + /// Construct a new data ready to be signed or verified. + pub fn new>>( + label: &'static [u8], + transcript_data: &[&[u8]], + vrf_inputs: T, + ) -> Self { + let mut transcript = Transcript::new_labeled(label); + transcript_data.iter().for_each(|data| transcript.append_slice(data)); + VrfSignData { transcript, vrf_inputs: vrf_inputs.into() } + } + + /// Construct a new data to be signed from an iterator of `VrfInputs`. + /// + /// Returns `Err` if the `vrf_inputs` yields more elements than `MAX_VRF_IOS` + pub fn from_iter>( + label: &'static [u8], + transcript_data: &[&[u8]], + vrf_inputs: T, + ) -> Result { + let vrf_inputs: Vec = vrf_inputs.into_iter().collect(); + let bounded = VrfIosVec::try_from(vrf_inputs).map_err(|_| ())?; + Ok(Self::new(label, transcript_data, bounded)) + } + + /// Appends a message to the transcript + pub fn push_transcript_data(&mut self, data: &[u8]) { + self.transcript.append_slice(data); + } + + /// Appends a `VrfInput` to the vrf inputs to be signed. + /// On failure, returns the `VrfInput`. + pub fn push_vrf_input(&mut self, vrf_input: VrfInput) -> Result<(), VrfInput> { + self.vrf_inputs.try_push(vrf_input) + } + + /// Create challenge from input transcript within the signing data. + pub fn challenge(&self) -> [u8; N] { + let mut output = [0; N]; + let mut t = self.transcript.clone(); + let mut reader = t.challenge(b"Prehashed for Ed25519"); + reader.read_bytes(&mut output); + output + } + } + + /// VRF signature. + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct VrfSignature { + /// VRF signature + pub signature: Signature, + /// VRF pre-outputs + pub vrf_outputs: VrfIosVec, + } + + #[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 { + // TODO davxy: + // (maybe temporary) Hack used because backend signature type is generic over the number + // of ios @burdges can we provide a vec or boxed version? + 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), + _ => panic!("Max VRF inputs is set to: {}", MAX_VRF_IOS), + } + } + + fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { + let output = self.0 .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 { + let preouts_len = signature.vrf_outputs.len(); + if preouts_len != data.vrf_inputs.len() { + return false + } + // TODO davxy: + // (maybe temporary) Hack used because backend signature type is generic over the number + // of ios @burdges can we provide a vec or boxed version? + 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), + _ => panic!("Max VRF input messages is set to: {}", MAX_VRF_IOS), + } + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + fn vrf_sign_gen(&self, data: &VrfSignData) -> VrfSignature { + let ios: Vec<_> = data + .vrf_inputs + .iter() + .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) + .collect(); + + let signature: ThinVrfSignature = + self.0.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 output bytes from the given VRF input. + /// + /// Index is relative to one of the `VrfInput` messages used during construction. + pub fn make_bytes( + &self, + context: &'static [u8], + input: &VrfInput, + ) -> [u8; N] { + let transcript = Transcript::new_labeled(context); + let inout = self.0.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_ref()) 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 + // TODO davxy. This is another hack used because backend signature type is generic over + // the number of ios. @burdges can you provide a vec version? + 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 output bytes for the given 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) + } + } +} + +#[cfg(test)] +mod tests { + use super::{vrf::*, *}; + use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; + const DEV_SEED: &[u8; SEED_SERIALIZED_LEN] = &[0; 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 backend_assumptions_check() { + let pair = SecretKey::from_seed(DEV_SEED); + let public = pair.to_public(); + + assert_eq!(public.0.size_of_serialized(), PUBLIC_SERIALIZED_LEN); + } + + #[test] + fn derive_hard_known_pair() { + let pair = Pair::from_string(&format!("{}//Alice", DEV_PHRASE), None).unwrap(); + // known address of DEV_PHRASE with 1.1 + let known = h2b("b0d3648bd5a3542afa16c06fee04cba37cc55c83a8894d36d87897bda0c65eec"); + assert_eq!(pair.public().as_ref(), known); + } + + #[test] + fn verify_known_signature() { + let pair = Pair::from_seed(DEV_SEED); + let public = pair.public(); + + let signature_raw = + h2b("524b0cbc4eb9579e2cd115fe55e2625e8265b3ea599ac903e67b08c2c669780cf43ca9c1e0a8a63c1dba121a606f95d3466cfe1880acc502c2792775125a7fcc" + ); + let signature = Signature::from_slice(&signature_raw).unwrap(); + + assert!(Pair::verify(&signature, b"hello", &public)); + } + + #[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"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + let i3 = VrfInput::new(b"in3", &[(b"domy", b"yay"), (b"domz", b"nay")]); + + let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + + 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"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + + let data = + VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]).unwrap(); + + let signature = pair.vrf_sign(&data); + + let data = VrfSignData::from_iter(b"mydata", &[b"data"], [i1, i2.clone()]).unwrap(); + assert!(!public.vrf_verify(&data, &signature)); + + let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], [i2]).unwrap(); + assert!(!public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_make_bytes_matches() { + let pair = Pair::from_seed(DEV_SEED); + + let i1 = VrfInput::new(b"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + let data = + VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]).unwrap(); + 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"in1", &[(b"dom1", b"foo"), (b"dom2", b"bar")]); + let i2 = VrfInput::new(b"in2", &[(b"domx", b"hello")]); + let data = + VrfSignData::from_iter(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]).unwrap(); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); + assert_eq!(expected, decoded); + + let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], []).unwrap(); + let expected = pair.vrf_sign(&data); + + let bytes = expected.encode(); + + let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); + assert_eq!(expected, decoded); + } +} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 27b24057b0e80..ae4b067b2c192 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")] @@ -480,7 +478,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: ByteArray + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync {} +pub trait Public: ByteArray + Derive + CryptoType + PartialEq + Eq + Clone + Send {} /// An opaque 32-byte cryptographic identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -828,7 +826,7 @@ impl sp_std::str::FromStr for SecretUri { /// /// For now it just specifies how to create a key from a phrase and derivation path. #[cfg(feature = "full_crypto")] -pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { +pub trait Pair: CryptoType + Sized + Clone + Send + 'static { /// The type which is used to encode a public key. type Public: Public + Hash; diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index f9541b02e2903..2687058283249 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -57,6 +57,8 @@ pub use paste; #[cfg(feature = "bls-experimental")] pub mod bls; +#[cfg(feature = "bsnvrf-experimental")] +pub mod bsnvrf; pub mod defer; pub mod ecdsa; pub mod ed25519; diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index 6faf4ffa3042a..a1889d6779af6 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"bb12"); /// 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 c6e716396aea4..05e3faa1a87ca 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -94,3 +94,11 @@ disable_allocator = [] # host function to be supported by the host. Do *not* enable it for your # runtime without first upgrading your host client! improved_panic_error_reporting = [] + +# This feature adds Bandersnatch-VRF crypto primitives. +# It should not be used in production since the implementation +# and interface may still be subject to significant change. +bsnvrf-experimental = [ + "sp-core/bsnvrf-experimental", + "sp-keystore/bsnvrf-experimental", +] diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 750b5d5924637..ab7b241a21150 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -44,6 +44,8 @@ use sp_core::{ #[cfg(feature = "std")] use sp_keystore::KeystoreExt; +#[cfg(feature = "bsnvrf-experimental")] +use sp_core::bsnvrf; use sp_core::{ crypto::KeyTypeId, ecdsa, ed25519, @@ -1140,6 +1142,21 @@ pub trait Crypto { .map_err(|_| EcdsaVerifyError::BadSignature)?; Ok(pubkey.serialize()) } + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate a `bsnvrf` 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. + fn bsnvrf_generate(&mut self, id: KeyTypeId, seed: Option>) -> bsnvrf::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!") + .bsnvrf_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 db3a8de2b2433..219436b225fa3 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -18,3 +18,8 @@ lazy_static = "1.4.0" strum = { version = "0.24.1", features = ["derive"], default-features = false } sp-core = { version = "7.0.0", path = "../core" } sp-runtime = { version = "7.0.0", path = "../runtime" } + +[features] +bsnvrf-experimental = [ + "sp-core/bsnvrf-experimental", +] diff --git a/primitives/keyring/src/bsnvrf.rs b/primitives/keyring/src/bsnvrf.rs new file mode 100644 index 0000000000000..df0538c67cd05 --- /dev/null +++ b/primitives/keyring/src/bsnvrf.rs @@ -0,0 +1,241 @@ +// 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. + +//! Support code for the runtime. A set of test accounts. + +pub use sp_core::bsnvrf; +use sp_core::{ + bsnvrf::{Pair, Public, Signature}, + crypto::UncheckedFrom, + ByteArray, Pair as PairT, H256, +}; +use sp_runtime::AccountId32; + +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, +} + +impl Keyring { + pub fn from_public(who: &Public) -> Option { + Self::iter().find(|&k| &Public::from(k) == who) + } + + pub fn from_account_id(who: &AccountId32) -> Option { + Self::iter().find(|&k| &k.to_account_id() == who) + } + + pub fn from_raw_public(who: [u8; 32]) -> Option { + Self::from_public(&Public::unchecked_from(who)) + } + + pub fn to_raw_public(self) -> [u8; 32] { + *Public::from(self).as_ref() + } + + pub fn from_h256_public(who: H256) -> Option { + Self::from_public(&Public::unchecked_from(who.into())) + } + + pub fn to_h256_public(self) -> H256 { + AsRef::<[u8; 32]>::as_ref(&Public::from(self)).into() + } + + pub fn to_raw_public_vec(self) -> Vec { + Public::from(self).to_raw_vec() + } + + pub fn to_account_id(self) -> AccountId32 { + self.to_raw_public().into() + } + + 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") + } + + /// Get account id of a `numeric` account. + pub fn numeric_id(idx: usize) -> AccountId32 { + (*AsRef::<[u8; 32]>::as_ref(&Self::numeric(idx).public())).into() + } +} + +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 AccountId32 { + fn from(k: Keyring) -> Self { + k.to_account_id() + } +} + +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; 32] { + fn from(k: Keyring) -> Self { + *(*PUBLIC_KEYS).get(&k).unwrap().as_ref() + } +} + +impl From for H256 { + fn from(k: Keyring) -> Self { + AsRef::<[u8; 32]>::as_ref(PUBLIC_KEYS.get(&k).unwrap()).into() + } +} + +impl From for &'static [u8; 32] { + fn from(k: Keyring) -> Self { + PUBLIC_KEYS.get(&k).unwrap().as_ref() + } +} + +impl AsRef<[u8; 32]> for Keyring { + fn as_ref(&self) -> &[u8; 32] { + 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; 32]; + fn deref(&self) -> &[u8; 32] { + PUBLIC_KEYS.get(self).unwrap().as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::{bsnvrf::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..69df7fc74907e 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; +#[cfg(feature = "bsnvrf-experimental")] +/// Test account crypto for Bandersnatch-Vrf +pub mod bsnvrf; + /// 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 = "bsnvrf-experimental")] +pub use bsnvrf::Keyring as BsnVrfKeyring; 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 a749b95a483b1..a6e44ef3b17cd 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -34,6 +34,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 change. bls-experimental = ["sp-core/bls-experimental"] + +# This feature adds Bandersnatch-VRF crypto primitives. +# It should not be used in production since the implementation +# and interface may still be subject to significant change. +bsnvrf-experimental = ["sp-core/bsnvrf-experimental"] diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 1d2a27cb8726c..e2c28011ef8a2 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -19,6 +19,8 @@ pub mod testing; +#[cfg(feature = "bsnvrf-experimental")] +use sp_core::bsnvrf; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -174,6 +176,68 @@ pub trait Keystore: Send + Sync { msg: &[u8; 32], ) -> Result, Error>; + #[cfg(feature = "bsnvrf-experimental")] + /// Returns all bsnvrf public keys for the given key type. + fn bsnvrf_public_keys(&self, key_type: KeyTypeId) -> Vec; + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate a new bsnvrf key pair for the given key type and an optional seed. + /// + /// Returns an `ed25519::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + fn bsnvrf_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result; + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate an bsnvrf signature for the given data. + /// + /// Receives [`KeyTypeId`] and an [`bsnvrf::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. + // TODO davxy: maybe we can remove this and just pass through the `vrf_sign` + // as this reduces to the other with a sign-data with 0 vrf-inputs. + fn bsnvrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + msg: &[u8], + ) -> Result, Error>; + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate a bsnvrf VRF signature for the given data. + /// + /// Receives [`KeyTypeId`] and an [`bsnvrf::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. + fn bsnvrf_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + input: &bsnvrf::vrf::VrfSignData, + ) -> Result, Error>; + + #[cfg(feature = "bsnvrf-experimental")] + /// Generate a bsnvrf VRF output for a given input data. + /// + /// Receives [`KeyTypeId`] and an [`bsnvrf::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. + fn bsnvrf_vrf_output( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + input: &bsnvrf::vrf::VrfInput, + ) -> Result, Error>; + #[cfg(feature = "bls-experimental")] /// Returns all bls12-381 public keys for the given key type. fn bls381_public_keys(&self, id: KeyTypeId) -> Vec; @@ -258,6 +322,7 @@ pub trait Keystore: Send + Sync { /// - sr25519 /// - ed25519 /// - ecdsa + /// - bsnvrf /// - bls381 /// - bls377 /// @@ -291,6 +356,12 @@ pub trait Keystore: Send + Sync { self.ecdsa_sign(id, &public, msg)?.map(|s| s.encode()) }, + #[cfg(feature = "bsnvrf-experimental")] + bsnvrf::CRYPTO_ID => { + let public = bsnvrf::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.bsnvrf_sign(id, &public, msg)?.map(|s| s.encode()) + }, #[cfg(feature = "bls-experimental")] bls381::CRYPTO_ID => { let public = bls381::Public::from_slice(public) diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index e18931a7af883..95fe2d4325bdb 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -19,6 +19,8 @@ use crate::{Error, Keystore, KeystorePtr}; +#[cfg(feature = "bsnvrf-experimental")] +use sp_core::bsnvrf; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ @@ -214,6 +216,50 @@ impl Keystore for MemoryKeystore { Ok(sig) } + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + self.generate_new::(key_type, seed) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + data: &bsnvrf::vrf::VrfSignData, + ) -> Result, Error> { + self.vrf_sign::(key_type, public, data) + } + + #[cfg(feature = "bsnvrf-experimental")] + fn bsnvrf_vrf_output( + &self, + key_type: KeyTypeId, + public: &bsnvrf::Public, + input: &bsnvrf::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)