From 8c39821be1532d61ab3c82701a22e371afa4d22e Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sun, 18 Jun 2023 14:57:48 +0200 Subject: [PATCH 01/36] Introduce bandersnatch vrf --- Cargo.lock | 161 ++- Cargo.toml | 14 + client/keystore/Cargo.toml | 4 +- client/keystore/src/local.rs | 58 + .../application-crypto/src/bandersnatch.rs | 54 + primitives/application-crypto/src/lib.rs | 1 + primitives/application-crypto/src/traits.rs | 2 +- primitives/core/Cargo.toml | 4 + primitives/core/src/bandersnatch.rs | 1006 +++++++++++++++++ primitives/core/src/crypto.rs | 6 +- primitives/core/src/lib.rs | 1 + primitives/core/src/testing.rs | 4 +- primitives/io/src/lib.rs | 19 + primitives/keyring/src/bandersnatch.rs | 209 ++++ primitives/keyring/src/lib.rs | 4 + primitives/keystore/src/lib.rs | 50 + primitives/keystore/src/testing.rs | 62 +- 17 files changed, 1629 insertions(+), 30 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 8564a4198d4bc..5e53ae6e8df25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,21 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +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" @@ -511,6 +526,19 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + [[package]] name = "array-bytes" version = "4.2.0" @@ -764,6 +792,27 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bandersnatch_vrfs" +version = "0.0.1" +source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +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.6", + "zeroize", +] + [[package]] name = "base-x" version = "0.2.11" @@ -1392,6 +1441,20 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "common" +version = "0.1.0" +source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "common-path" version = "1.0.0" @@ -2131,6 +2194,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/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +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" @@ -2488,6 +2567,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "fflonk" +version = "0.1.0" +source = "git+https://github.com/davxy/fflonk?branch=working-fork#a2664567b88d96e1dc2f82f8799b2ca60171c81d" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "merlin 3.0.0", +] + [[package]] name = "fiat-crypto" version = "0.1.20" @@ -4558,7 +4650,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "rcgen 0.10.0", - "ring", + "ring 0.16.20", "rustls 0.20.8", "thiserror", "webpki 0.22.0", @@ -5017,6 +5109,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" @@ -8320,7 +8424,7 @@ checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" dependencies = [ "bytes", "rand 0.8.5", - "ring", + "ring 0.16.20", "rustc-hash", "rustls 0.20.8", "slab", @@ -8470,7 +8574,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", @@ -8483,7 +8587,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", ] @@ -8612,6 +8716,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "common", + "fflonk", + "merlin 3.0.0", +] + [[package]] name = "ring" version = "0.16.20" @@ -8779,7 +8898,7 @@ checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.1", "log", - "ring", + "ring 0.16.20", "sct 0.6.1", "webpki 0.21.4", ] @@ -8791,7 +8910,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", ] @@ -8803,7 +8922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct 0.7.0", ] @@ -8835,7 +8954,7 @@ version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10372,7 +10491,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", @@ -10398,7 +10517,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10408,7 +10527,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -10746,7 +10865,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", @@ -11139,6 +11258,8 @@ name = "sp-core" version = "21.0.0" dependencies = [ "array-bytes 4.2.0", + "arrayvec 0.7.2", + "bandersnatch_vrfs", "bitflags", "blake2", "bounded-collections", @@ -11154,7 +11275,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin", + "merlin 2.0.1", "parity-scale-codec", "parking_lot 0.12.1", "paste", @@ -11876,7 +11997,7 @@ dependencies = [ "lazy_static", "md-5", "rand 0.8.5", - "ring", + "ring 0.16.20", "subtle", "thiserror", "tokio", @@ -12920,7 +13041,7 @@ dependencies = [ "log", "md-5", "rand 0.8.5", - "ring", + "ring 0.16.20", "stun", "thiserror", "tokio", @@ -13599,7 +13720,7 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -13609,7 +13730,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring", + "ring 0.16.20", "untrusted", ] @@ -13638,7 +13759,7 @@ dependencies = [ "rand 0.8.5", "rcgen 0.9.3", "regex", - "ring", + "ring 0.16.20", "rtcp", "rtp", "rustls 0.19.1", @@ -13703,7 +13824,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", @@ -14150,7 +14271,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 ce93421bc9ff1..80b31bf69d974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -337,3 +337,17 @@ 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 = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } +# bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } + +[patch."https://github.com/w3f/fflonk"] +fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } +# fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } + +[patch."https://github.com/w3f/ring-proof"] +common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +# common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } +# ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 171e2c841562b..aa1527a4f67eb 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -29,6 +29,6 @@ tempfile = "3.1.0" # 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. bls-experimental = [ - "sp-core/bls-experimental", - "sp-keystore/bls-experimental", + "sp-core/bls-experimental", + "sp-keystore/bls-experimental", ] diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 4167e486ecf62..0a9dce517ff86 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -22,6 +22,7 @@ use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ + bandersnatch, crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSecret}, ecdsa, ed25519, sr25519, }; @@ -234,6 +235,63 @@ impl Keystore for LocalKeystore { Ok(sig) } + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } + + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } + + // TODO DAVXY + // Maybe we can expose just this bandersnatch sign (the above one reduces to this with + // input len = 0) + 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) + } + + 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) + } + + 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/src/bandersnatch.rs b/primitives/application-crypto/src/bandersnatch.rs new file mode 100644 index 0000000000000..b53963c9fff3c --- /dev/null +++ b/primitives/application-crypto/src/bandersnatch.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::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; + + 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) + } + + 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 46f59719f9c29..d032b80fa9a24 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -43,6 +43,7 @@ pub use serde; #[doc(hidden)] pub use sp_std::{ops::Deref, vec::Vec}; +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..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/core/Cargo.toml b/primitives/core/Cargo.toml index 98df775d289c1..e155e4a8c2aeb 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -13,6 +13,8 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", default-features = false } +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 } @@ -72,12 +74,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", diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs new file mode 100644 index 0000000000000..1b942b62b9a1c --- /dev/null +++ b/primitives/core/src/bandersnatch.rs @@ -0,0 +1,1006 @@ +// 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. + +//! TODO DOCS. + +#[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"bs38"); + +#[cfg(feature = "full_crypto")] +const SIGNING_CTX: &[u8] = b"SigningContext"; + +#[cfg(feature = "full_crypto")] +const SEED_SERIALIZED_LEN: usize = 32; + +// Edwards form sizes (TODO davxy: propably in the end we'll use this form) +// const PUBLIC_SERIALIZED_LEN: usize = 32; +// const SIGNATURE_SERIALIZED_LEN: usize = 64; + +// Short-Weierstrass form sizes +const PUBLIC_SERIALIZED_LEN: usize = 33; +const SIGNATURE_SERIALIZED_LEN: usize = 65; + +// Edwards form sizes (TODO davxy: probably in the end we'll use this form) +// const PREOUT_SERIALIZED_LEN: usize = 32; + +// Short-Weierstrass form sizes +const PREOUT_SERIALIZED_LEN: usize = 33; + +// Size of serialized pedersen-vrf signature +// Short-Weierstrass form sizes +const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; + +// Size of serialized ring-proof +// Short-Weierstrass form sizes +const RING_PROOF_SERIALIZED_LEN: usize = 592; + +// Sise of serialized ring-vrf context params +const RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN: usize = 147744; + +/// 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.as_ref()), &s[0..8]) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +/// TODO davxy: DOCS +#[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]; + +/// TODO davxy: DOCS +#[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 + .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(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, + }; + + /// Max number of VRF inputs/outputs + pub const MAX_VRF_IOS: u32 = 3; + + /// Bounded vector used for VRF inputs and outputs. + pub type VrfIosVec = BoundedVec>; + + /// Input to be used for VRF sign and verify operations. + #[derive(Clone, Debug)] + pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); + + impl VrfInput { + /// Build a new VRF input. + /// + /// Each message tuple has the form: (domain, data). + // TODO: Maybe we should access directly the transcript. + // I see a commented method in bandersnatch_vrfs crate that fullfil what we need... + pub fn new(label: &'static [u8], messages: &[(&[u8], &[u8])]) -> Self { + let _ = label; + 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: b"TODO-DAVXY-FIXME", message: buf.as_slice() }; + VrfInput(msg.into_vrf_input()) + } + } + + /// TODO davxy docs + #[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() + } + } + + /// TODO davxy docs + 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 to be signed. + 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 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 { + // 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 + } + // 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_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. @burdges can we provide a vec or boxed 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) + } + } +} + +/// Ring VRF related 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}; + + /// TODO davxy + #[derive(Clone)] + pub struct RingVrfContext(pub KZG); + + impl RingVrfContext { + /// TODO davxy: This is a temporary function with temporary parameters. + /// + /// Initialization cerimony should be performed via some other means + /// For now we call this once here. + pub fn new_testing() -> Self { + let kzg_seed = [0; 32]; + let domain_size = 2usize.pow(10); + let kzg = KZG::testing_kzg_setup(kzg_seed, domain_size); + Self(kzg) + } + + /// Get the keyset size + pub fn max_keyset_size(&self) -> usize { + self.0.max_keyset_size() + } + + /// TODO davxy + pub fn prover(&self, public_keys: &[Public], public_idx: usize) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + if !public_keys.iter().all(|public_key| { + match PublicKey::deserialize_compressed(public_key.as_slice()) { + Ok(pk) => { + let sw_affine = pk.0 .0.into(); + pks.push(sw_affine); + true + }, + _ => false, + } + }) { + return None + }; + let prover_key = self.0.prover_key(pks); + let ring_prover = self.0.init_ring_prover(prover_key, public_idx); + + Some(ring_prover) + } + + /// TODO davxy + pub fn verifier(&self, public_keys: &[Public]) -> Option { + let mut pks = Vec::with_capacity(public_keys.len()); + if !public_keys.iter().all(|public_key| { + match PublicKey::deserialize_compressed(public_key.as_slice()) { + Ok(pk) => { + let sw_affine = pk.0 .0.into(); + pks.push(sw_affine); + true + }, + _ => false, + } + }) { + return None + }; + + let verifier_key = self.0.verifier_key(pks); + let ring_verifier = self.0.init_ring_verifier(verifier_key); + + Some(ring_verifier) + } + } + + // TODO davxy: why this isn't implemented automagically, is there some other required bound??? + impl codec::EncodeLike for RingVrfContext {} + + impl Encode for RingVrfContext { + fn encode(&self) -> Vec { + let mut buf = Box::new([0; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]); + self.0 + .serialize_compressed(buf.as_mut_slice()) + .expect("preout serialization can't fail"); + buf.encode() + } + } + + impl Decode for RingVrfContext { + fn decode(i: &mut R) -> Result { + let buf = >::decode(i)?; + let kzg = + KZG::deserialize_compressed(buf.as_slice()).map_err(|_| "KZG decode error")?; + Ok(RingVrfContext(kzg)) + } + } + + impl MaxEncodedLen for RingVrfContext { + fn max_encoded_len() -> usize { + <[u8; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]>::max_encoded_len() + } + } + + impl TypeInfo for RingVrfContext { + type Identity = [u8; RING_VRF_CONTEXT_PARAMS_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 { + /// TODO davxy + pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { + // 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.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), + _ => panic!("Max VRF inputs is set to: {}", MAX_VRF_IOS), + } + } + + fn ring_vrf_sign_gen( + &self, + data: &VrfSignData, + prover: &RingProver, + ) -> RingVrfSignature { + let ios: Vec<_> = data + .vrf_inputs + .iter() + .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) + .collect(); + + let ring_signature: bandersnatch_vrfs::RingVrfSignature = + self.0.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 { + /// TODO davxy + pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { + let preouts_len = self.outputs.len(); + if preouts_len != data.vrf_inputs.len() { + return false + } + // 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.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), + _ => panic!("Max VRF input messages is set to: {}", MAX_VRF_IOS), + } + } + + 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] = &[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] + #[ignore] + 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] + #[ignore] + 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 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[..]).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); + } + + #[test] + fn ring_vrf_sign_verify() { + let ring_ctx = RingVrfContext::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); + let public = pair.public(); + + // Just pick one... + let prover_idx = 3; + pks[prover_idx] = public.clone(); + + 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 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 encode_decode_ring_vrf_signature() { + let ring_ctx = RingVrfContext::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); + let public = pair.public(); + + // Just pick one... + let prover_idx = 3; + pks[prover_idx] = public.clone(); + + 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 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[..]).unwrap(); + assert_eq!(expected, decoded); + } + + // Requires parity-scale-codec v0.3.6 + #[test] + #[ignore] + fn encode_decode_ring_vrf_context() { + let ring_ctx = RingVrfContext::new_testing(); + + let encoded = ring_ctx.encode(); + println!("SIZE: {}", encoded.len()); + + assert_eq!(encoded.len(), RingVrfContext::max_encoded_len()); + + let _decoded = RingVrfContext::decode(&mut &encoded[..]).unwrap(); + + // TODO davxy... just use unsafe pointers comparison + } +} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 5947603cd0942..99670e8a1dfba 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: 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)] @@ -834,7 +832,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 951b481253b4e..dd003e8186b41 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -55,6 +55,7 @@ pub mod crypto; pub mod hexdisplay; pub use paste; +pub mod bandersnatch; #[cfg(feature = "bls-experimental")] pub mod bls; pub mod defer; 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/src/lib.rs b/primitives/io/src/lib.rs index 750b5d5924637..124cebf643453 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -45,6 +45,7 @@ use sp_core::{ use sp_keystore::KeystoreExt; use sp_core::{ + bandersnatch, crypto::KeyTypeId, ecdsa, ed25519, offchain::{ @@ -1140,6 +1141,24 @@ pub trait Crypto { .map_err(|_| EcdsaVerifyError::BadSignature)?; Ok(pubkey.serialize()) } + + /// 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. + 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/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..fba15a121e818 100644 --- a/primitives/keyring/src/lib.rs +++ b/primitives/keyring/src/lib.rs @@ -23,11 +23,15 @@ pub mod sr25519; /// Test account crypto for ed25519. pub mod ed25519; +/// Test account crypto for bandersnatch. +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; +pub use bandersnatch::Keyring as BandersnatchKeyring; pub use ed25519::Keyring as Ed25519Keyring; pub use sr25519::Keyring as Sr25519Keyring; diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 1d2a27cb8726c..c3c15c03f8a32 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -22,6 +22,7 @@ pub mod testing; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ + bandersnatch, crypto::{ByteArray, CryptoTypeId, KeyTypeId}, ecdsa, ed25519, sr25519, }; @@ -174,6 +175,49 @@ pub trait Keystore: Send + Sync { msg: &[u8; 32], ) -> Result, Error>; + /// DAVXY TODO + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec; + + /// DAVXY TODO + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result; + + /// DAVXY TODO + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> Result, Error>; + + /// DAVXY TODO + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + ) -> Result, Error>; + + /// DAVXY TODO + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> Result, Error>; + + /// DAVXY TODO + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> 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 +302,7 @@ pub trait Keystore: Send + Sync { /// - sr25519 /// - ed25519 /// - ecdsa + /// - bandersnatch /// - bls381 /// - bls377 /// @@ -291,6 +336,11 @@ pub trait Keystore: Send + Sync { self.ecdsa_sign(id, &public, msg)?.map(|s| s.encode()) }, + 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) diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index e18931a7af883..e3f3d94ddef86 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -22,6 +22,7 @@ use crate::{Error, Keystore, KeystorePtr}; #[cfg(feature = "bls-experimental")] use sp_core::{bls377, bls381}; use sp_core::{ + bandersnatch, crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, }; @@ -214,6 +215,58 @@ impl Keystore for MemoryKeystore { Ok(sig) } + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + self.generate_new::(key_type, seed) + } + + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) + } + + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + ) -> Result, Error> { + self.vrf_sign::(key_type, public, data) + } + + 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) + } + + 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 +383,7 @@ mod tests { } #[test] - fn vrf_sign() { + fn sr25519_vrf_sign() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -359,7 +412,7 @@ mod tests { } #[test] - fn vrf_output() { + fn sr25519_vrf_output() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -406,4 +459,9 @@ mod tests { let res = store.ecdsa_sign_prehashed(ECDSA, &pair.public(), &msg).unwrap(); assert!(res.is_some()); } + + #[test] + fn bandersnatch_vrf_sign() { + panic!("TODO") + } } From a9446fdb673a83d9a3af6676f6e34341f94a83f8 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sun, 18 Jun 2023 17:45:54 +0200 Subject: [PATCH 02/36] Some documentation --- client/keystore/src/local.rs | 7 +- .../application-crypto/src/bandersnatch.rs | 3 + primitives/core/src/bandersnatch.rs | 180 ++++++++++-------- 3 files changed, 107 insertions(+), 83 deletions(-) diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 0a9dce517ff86..1d05cd178fa61 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -19,13 +19,13 @@ use parking_lot::RwLock; use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use sp_core::{ bandersnatch, crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSecret}, ecdsa, ed25519, sr25519, }; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use sp_keystore::{Error as TraitError, Keystore, KeystorePtr}; use std::{ collections::HashMap, @@ -239,6 +239,9 @@ impl Keystore for LocalKeystore { 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. fn bandersnatch_generate_new( &self, key_type: KeyTypeId, diff --git a/primitives/application-crypto/src/bandersnatch.rs b/primitives/application-crypto/src/bandersnatch.rs index b53963c9fff3c..fc7383815d702 100644 --- a/primitives/application-crypto/src/bandersnatch.rs +++ b/primitives/application-crypto/src/bandersnatch.rs @@ -32,6 +32,7 @@ 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() } @@ -40,10 +41,12 @@ impl RuntimePublic for Public { 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 } diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 1b942b62b9a1c..94831ccd78d1c 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -15,7 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! TODO DOCS. +//! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch), +//! an elliptic curve built over BLS12-381 scalar field. +//! +//! The Bandersnatch curve can be represented in twisted Edwards coordinates, allowing +//! efficieny inside zk-SNARKS circuits. #[cfg(feature = "std")] use crate::crypto::Ss58Codec; @@ -37,17 +41,21 @@ use sp_std::{boxed::Box, vec::Vec}; /// Identifier used to match public keys against bandersnatch-vrf keys. pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bs38"); +/// Context used to produce #[cfg(feature = "full_crypto")] -const SIGNING_CTX: &[u8] = b"SigningContext"; +pub const SIGNING_CTX: &[u8] = b"SigningContext"; #[cfg(feature = "full_crypto")] const SEED_SERIALIZED_LEN: usize = 32; -// Edwards form sizes (TODO davxy: propably in the end we'll use this form) +// Edwards form sizes +// @burdges @swasilyev: currently ring-proof is using SHORT-WEIERSTRASS +// I had to temporary patch bandersnatch_vrfs crate to use SW instrad of ED... +// (@davxy: probably we'll use ED form) // const PUBLIC_SERIALIZED_LEN: usize = 32; // const SIGNATURE_SERIALIZED_LEN: usize = 64; -// Short-Weierstrass form sizes +// Short-Weierstrass form sizes (TEMPORARY) const PUBLIC_SERIALIZED_LEN: usize = 33; const SIGNATURE_SERIALIZED_LEN: usize = 65; @@ -66,9 +74,16 @@ const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; const RING_PROOF_SERIALIZED_LEN: usize = 592; // Sise of serialized ring-vrf context params +// @burdges @swasilyev: This is quite big... +// This size grows with the domain size. +// Example values +// domsize -> serialized-size-in-kilobytes +// 2^9 -> 74 KB +// 2^10 -> 147 KB +// 2^11 -> 295 KB const RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN: usize = 147744; -/// XXX. +/// Bandersnatch public key. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive( Clone, @@ -148,7 +163,9 @@ impl sp_std::fmt::Debug for Public { } } -/// TODO davxy: DOCS +/// Bandersnatch signature. +/// +/// The signature is created via the [VrfSecret::vrf_sign] using [SIGNING_CTX] as `label` parameter. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, PassByInner, MaxEncodedLen, TypeInfo)] pub struct Signature([u8; SIGNATURE_SERIALIZED_LEN]); @@ -205,11 +222,11 @@ impl sp_std::fmt::Debug for Signature { } } -/// The raw secret seed, which can be used to recreate the `Pair`. +/// The raw secret seed, which can be used to reconstruct the secret `Pair`. #[cfg(feature = "full_crypto")] type Seed = [u8; SEED_SERIALIZED_LEN]; -/// TODO davxy: DOCS +/// Bandersnatch secret key. #[cfg(feature = "full_crypto")] #[derive(Clone)] pub struct Pair(SecretKey); @@ -284,9 +301,9 @@ impl TraitPair for Pair { /// 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!() + // TODO @davxy: this function existance makes sense??? Should we return the seed or + // serialized secret key? If we return the serialized secret there is no method to + // reconstruct if ... unimplemented!() panic!() } } @@ -296,7 +313,7 @@ impl CryptoType for Pair { type Pair = Pair; } -/// VRF related types and operations. +/// Bandersnatch VRF types and operations. pub mod vrf { use super::*; use crate::{bounded::BoundedVec, crypto::VrfCrypto, ConstU32}; @@ -305,13 +322,15 @@ pub mod vrf { ThinVrfSignature, Transcript, }; - /// Max number of VRF inputs/outputs + /// Max number of inputs/outputs which can be handled by the VRF signing procedures. 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>; - /// Input to be used for VRF sign and verify operations. + /// VRF input to construct a `[VrfOutput]` instance and embeddable within `[VrfSignData]`. #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); @@ -319,9 +338,15 @@ pub mod vrf { /// Build a new VRF input. /// /// Each message tuple has the form: (domain, data). - // TODO: Maybe we should access directly the transcript. - // I see a commented method in bandersnatch_vrfs crate that fullfil what we need... pub fn new(label: &'static [u8], messages: &[(&[u8], &[u8])]) -> Self { + // ⚠️ TODO @davxy @burdges (temporary hack and needs to be fixed) + // `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 + // THIS IS JUST A PLACEHOLDER and we are ignoring the lablel and concat the dom++msgs. let _ = label; let mut buf = Vec::new(); messages.into_iter().for_each(|(domain, message)| { @@ -333,7 +358,9 @@ pub mod vrf { } } - /// TODO davxy docs + /// VRF (pre)output derived from `[VrfInput]` using a `[VrfSecret]`. + /// + /// This is used to produce a verifiable arbitrary number of "random" bytes. #[derive(Clone, Debug, PartialEq, Eq)] pub struct VrfOutput(pub(super) bandersnatch_vrfs::VrfPreOut); @@ -370,16 +397,23 @@ pub mod vrf { } } - /// TODO davxy docs + /// 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, + /// Associated Fiat-Shamir transcript + pub transcript: Transcript, } impl VrfSignData { - /// Construct a new data to be signed. + /// Construct a new signable data instance. + /// + /// The `[transcript_data]` will be used as messages for the Fiat-Shamir + /// transform part of the scheme. If unsure just give it a unique label + /// depending on the actual usage of the signing data + /// (@burges: or leave it empty? There is already the `label` field for contextualization). + /// The `[vrf_inputs]` is a sequence of `[VrfInput]`s to be signed and which + /// contribute to the actual output bytes produced via the VRF. pub fn new>>( label: &'static [u8], transcript_data: &[&[u8]], @@ -392,7 +426,8 @@ pub mod vrf { /// 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` + /// Fails if the `vrf_inputs` yields more elements than `[MAX_VRF_IOS]` + // TODO @davxy: maybe we can just provide one constructor... pub fn from_iter>( label: &'static [u8], transcript_data: &[&[u8]], @@ -427,9 +462,9 @@ pub mod vrf { /// VRF signature. #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct VrfSignature { - /// VRF pre-outputs + /// VRF (pre)outputs. pub vrf_outputs: VrfIosVec, - /// VRF signature + /// Transcript @davxy TODO doc signature. pub signature: Signature, } @@ -444,14 +479,15 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { + // TODO: @davxy // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a vec or boxed version? + // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? 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), + _ => unreachable!(), } } @@ -475,13 +511,13 @@ pub mod vrf { return false } // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a vec or boxed version? + // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? 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), + _ => unreachable!(), } } } @@ -509,9 +545,7 @@ pub mod vrf { 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. + /// Generate an arbitrary number of bytes from the given `[context]` and VRF `[input]`. pub fn make_bytes( &self, context: &'static [u8], @@ -544,7 +578,7 @@ pub mod vrf { // 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. @burdges can we provide a vec or boxed version? + // of ios. let Ok(signature) = ThinVrfSignature::<0>::deserialize_compressed(signature.signature.as_ref()).map(|s| s.signature) else { return false }; @@ -557,7 +591,7 @@ pub mod vrf { } impl VrfOutput { - /// Generate output bytes for the given VRF input. + /// Generate an arbitrary number of bytes from the given `[context]` and VRF `[input]`. pub fn make_bytes( &self, context: &'static [u8], @@ -571,80 +605,58 @@ pub mod vrf { } } -/// Ring VRF related types and operations. +/// 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}; - /// TODO davxy + /// Context used to produce ring signatures. #[derive(Clone)] - pub struct RingVrfContext(pub KZG); + pub struct RingVrfContext(KZG); impl RingVrfContext { - /// TODO davxy: This is a temporary function with temporary parameters. - /// - /// Initialization cerimony should be performed via some other means - /// For now we call this once here. + /// Build an dummy instance used for testing purposes. pub fn new_testing() -> Self { - let kzg_seed = [0; 32]; + let seed = [0; 32]; let domain_size = 2usize.pow(10); - let kzg = KZG::testing_kzg_setup(kzg_seed, domain_size); + let kzg = KZG::testing_kzg_setup(seed, domain_size); Self(kzg) } - /// Get the keyset size + /// Get the keyset size. + /// TODO @swasilyev: shouldn't be equal to the domain_size used at init time? pub fn max_keyset_size(&self) -> usize { self.0.max_keyset_size() } - /// TODO davxy + /// 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()); - if !public_keys.iter().all(|public_key| { - match PublicKey::deserialize_compressed(public_key.as_slice()) { - Ok(pk) => { - let sw_affine = pk.0 .0.into(); - pks.push(sw_affine); - true - }, - _ => false, - } - }) { - return None - }; + 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) } - /// TODO davxy + /// 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()); - if !public_keys.iter().all(|public_key| { - match PublicKey::deserialize_compressed(public_key.as_slice()) { - Ok(pk) => { - let sw_affine = pk.0 .0.into(); - pks.push(sw_affine); - true - }, - _ => false, - } - }) { - return None - }; + 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) } } - // TODO davxy: why this isn't implemented automagically, is there some other required bound??? - impl codec::EncodeLike for RingVrfContext {} - impl Encode for RingVrfContext { fn encode(&self) -> Vec { let mut buf = Box::new([0; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]); @@ -691,20 +703,23 @@ pub mod ring_vrf { #[cfg(feature = "full_crypto")] impl Pair { - /// TODO davxy + /// Produce a ring-vrf signature. + /// + /// The signature is valid if the signing [`Pair`] is part of the ring from which + /// the [`RingProver`] has been derived. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a vec or boxed version? + // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? 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), - _ => panic!("Max VRF inputs is set to: {}", MAX_VRF_IOS), + _ => unreachable!(), } } - fn ring_vrf_sign_gen( + fn ring_vrf_sign_gen( &self, data: &VrfSignData, prover: &RingProver, @@ -738,20 +753,23 @@ pub mod ring_vrf { } impl RingVrfSignature { - /// TODO davxy + /// Verify a ring-vrf signature. + /// + /// The signature is valid if has been produced by a member of the ring from which + /// the [`RingVerifier`] has been derived. pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let preouts_len = self.outputs.len(); if preouts_len != data.vrf_inputs.len() { return false } // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a vec or boxed version? + // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? 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), - _ => panic!("Max VRF input messages is set to: {}", MAX_VRF_IOS), + _ => unreachable!(), } } From bb3e02273e9309fca980a19db1bca0cf4b2455a2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sun, 18 Jun 2023 17:52:26 +0200 Subject: [PATCH 03/36] Fix tests --- primitives/core/src/bandersnatch.rs | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 94831ccd78d1c..d5b3aab82ec0b 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -719,7 +719,7 @@ pub mod ring_vrf { } } - fn ring_vrf_sign_gen( + fn ring_vrf_sign_gen( &self, data: &VrfSignData, prover: &RingProver, @@ -827,28 +827,13 @@ mod tests { } #[test] - #[ignore] 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"); + // known address of DEV_PHRASE + let known = h2b("646b261d8058ba8a3c4ebe152dc837fb2259433270dd5bdc79095874cc78b62f00"); assert_eq!(pair.public().as_ref(), known); } - #[test] - #[ignore] - 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); @@ -954,7 +939,7 @@ mod tests { let pair = Pair::from_seed(DEV_SEED); let public = pair.public(); - // Just pick one... + // Just pick one index to patch with the actual public key let prover_idx = 3; pks[prover_idx] = public.clone(); From bdd3df4ec618de1010ad7c5c808666771127ed36 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sun, 18 Jun 2023 18:36:01 +0200 Subject: [PATCH 04/36] Fix docs refs --- primitives/core/src/bandersnatch.rs | 45 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index d5b3aab82ec0b..0f71be1b92c1f 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -165,7 +165,7 @@ impl sp_std::fmt::Debug for Public { /// Bandersnatch signature. /// -/// The signature is created via the [VrfSecret::vrf_sign] using [SIGNING_CTX] as `label` parameter. +/// 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]); @@ -222,7 +222,7 @@ impl sp_std::fmt::Debug for Signature { } } -/// The raw secret seed, which can be used to reconstruct the secret `Pair`. +/// The raw secret seed, which can be used to reconstruct the secret [`Pair`]. #[cfg(feature = "full_crypto")] type Seed = [u8; SEED_SERIALIZED_LEN]; @@ -291,7 +291,7 @@ impl TraitPair for Pair { /// Verify a signature on a message. /// - /// Returns true if the signature is good. + /// 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 = @@ -299,7 +299,7 @@ impl TraitPair for Pair { public.vrf_verify(&data, &signature) } - /// Return a vec filled with seed raw data. + /// Return a vector filled with seed raw data. fn to_raw_vec(&self) -> Vec { // TODO @davxy: this function existance makes sense??? Should we return the seed or // serialized secret key? If we return the serialized secret there is no method to @@ -327,17 +327,17 @@ pub mod vrf { /// Bounded vector used for VRF inputs and outputs. /// - /// Can contain at most `[MAX_VRF_IOS]` elements. + /// Can contain at most [`MAX_VRF_IOS`] elements. pub type VrfIosVec = BoundedVec>; - /// VRF input to construct a `[VrfOutput]` instance and embeddable within `[VrfSignData]`. + /// VRF input to construct a [`VrfOutput`] instance and embeddable within [`VrfSignData`]. #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); impl VrfInput { /// Build a new VRF input. /// - /// Each message tuple has the form: (domain, data). + /// Each message tuple has the form: message := (domain, data). pub fn new(label: &'static [u8], messages: &[(&[u8], &[u8])]) -> Self { // ⚠️ TODO @davxy @burdges (temporary hack and needs to be fixed) // `bandersnatch_vrfs::Message` maps to a single (domain, data) tuple. @@ -358,7 +358,7 @@ pub mod vrf { } } - /// VRF (pre)output derived from `[VrfInput]` using a `[VrfSecret]`. + /// VRF (pre)output derived from [`VrfInput`] using a [`VrfSecret`]. /// /// This is used to produce a verifiable arbitrary number of "random" bytes. #[derive(Clone, Debug, PartialEq, Eq)] @@ -397,7 +397,7 @@ pub mod vrf { } } - /// A Fiat-Shamir transcript and a sequence of `[VrfInput]`s ready to be signed. + /// A Fiat-Shamir transcript and a sequence of [`VrfInput`]s ready to be signed. pub struct VrfSignData { /// VRF inputs to be signed. pub vrf_inputs: VrfIosVec, @@ -408,11 +408,11 @@ pub mod vrf { impl VrfSignData { /// Construct a new signable data instance. /// - /// The `[transcript_data]` will be used as messages for the Fiat-Shamir + /// The `transcript_data` will be used as messages for the Fiat-Shamir /// transform part of the scheme. If unsure just give it a unique label /// depending on the actual usage of the signing data /// (@burges: or leave it empty? There is already the `label` field for contextualization). - /// The `[vrf_inputs]` is a sequence of `[VrfInput]`s to be signed and which + /// The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and which /// contribute to the actual output bytes produced via the VRF. pub fn new>>( label: &'static [u8], @@ -424,9 +424,9 @@ pub mod vrf { VrfSignData { transcript, vrf_inputs: vrf_inputs.into() } } - /// Construct a new data to be signed from an iterator of `VrfInputs`. + /// Construct a new data to be signed from an iterator of [`VrfInput`]s. /// - /// Fails if the `vrf_inputs` yields more elements than `[MAX_VRF_IOS]` + /// Fails if the `vrf_inputs` yields more elements than [`MAX_VRF_IOS`] // TODO @davxy: maybe we can just provide one constructor... pub fn from_iter>( label: &'static [u8], @@ -443,8 +443,9 @@ pub mod vrf { self.transcript.append_slice(data); } - /// Appends a `VrfInput` to the vrf inputs to be signed. - /// On failure, returns the `VrfInput`. + /// Appends 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) } @@ -464,7 +465,7 @@ pub mod vrf { pub struct VrfSignature { /// VRF (pre)outputs. pub vrf_outputs: VrfIosVec, - /// Transcript @davxy TODO doc signature. + /// VRF signature. pub signature: Signature, } @@ -545,7 +546,7 @@ pub mod vrf { VrfSignature { signature: Signature(sign_bytes), vrf_outputs: outputs } } - /// Generate an arbitrary number of bytes from the given `[context]` and VRF `[input]`. + /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. pub fn make_bytes( &self, context: &'static [u8], @@ -577,8 +578,8 @@ pub mod vrf { }; // 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. + // 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 }; @@ -591,7 +592,7 @@ pub mod vrf { } impl VrfOutput { - /// Generate an arbitrary number of bytes from the given `[context]` and VRF `[input]`. + /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. pub fn make_bytes( &self, context: &'static [u8], @@ -630,7 +631,7 @@ pub mod ring_vrf { self.0.max_keyset_size() } - /// Get ring prover for the key at index [public_idx] in the `[public_keys]` set. + /// 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 { @@ -643,7 +644,7 @@ pub mod ring_vrf { Some(ring_prover) } - /// Get ring verifier for the `[public_keys]` set. + /// 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 { From e0e493abd12e8522ab95063f100cc7430e85d8b0 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 19 Jun 2023 18:00:08 +0200 Subject: [PATCH 05/36] Some more docs --- client/keystore/src/local.rs | 2 +- primitives/keystore/src/lib.rs | 42 ++++++++++++++++++++++++------ primitives/keystore/src/testing.rs | 39 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 1d05cd178fa61..47cb0f57fe8ff 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -259,7 +259,7 @@ impl Keystore for LocalKeystore { self.sign::(key_type, public, msg) } - // TODO DAVXY + // TODO @davxy // Maybe we can expose just this bandersnatch sign (the above one reduces to this with // input len = 0) fn bandersnatch_vrf_sign( diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index c3c15c03f8a32..a8c8c317c8d6f 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -19,13 +19,13 @@ pub mod testing; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use sp_core::{ bandersnatch, crypto::{ByteArray, CryptoTypeId, KeyTypeId}, ecdsa, ed25519, sr25519, }; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use std::sync::Arc; @@ -175,17 +175,27 @@ pub trait Keystore: Send + Sync { msg: &[u8; 32], ) -> Result, Error>; - /// DAVXY TODO + /// Returns all the bandersnatch public keys for the given key type. fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec; - /// DAVXY TODO + /// 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. fn bandersnatch_generate_new( &self, key_type: KeyTypeId, seed: Option<&str>, ) -> Result; - /// DAVXY TODO + /// 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. fn bandersnatch_sign( &self, key_type: KeyTypeId, @@ -193,7 +203,13 @@ pub trait Keystore: Send + Sync { msg: &[u8], ) -> Result, Error>; - /// DAVXY TODO + /// 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. fn bandersnatch_vrf_sign( &self, key_type: KeyTypeId, @@ -201,7 +217,6 @@ pub trait Keystore: Send + Sync { input: &bandersnatch::vrf::VrfSignData, ) -> Result, Error>; - /// DAVXY TODO fn bandersnatch_vrf_output( &self, key_type: KeyTypeId, @@ -209,7 +224,18 @@ pub trait Keystore: Send + Sync { input: &bandersnatch::vrf::VrfInput, ) -> Result, Error>; - /// DAVXY TODO + /// 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 [`RingProver`] instance obtained from a valid [`RingVrfContext`]. + /// + /// The signature is valid if the signing key is part of the ring from which + /// the [`RingProver`] has been derived. + /// + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. fn bandersnatch_ring_vrf_sign( &self, key_type: KeyTypeId, diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index e3f3d94ddef86..7d57129ea02a2 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -19,13 +19,13 @@ use crate::{Error, Keystore, KeystorePtr}; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use sp_core::{ bandersnatch, crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, }; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -352,7 +352,7 @@ mod tests { use super::*; use sp_core::{ sr25519, - testing::{ECDSA, ED25519, SR25519}, + testing::{BANDERSNATCH, ECDSA, ED25519, SR25519}, }; #[test] @@ -462,6 +462,37 @@ mod tests { #[test] fn bandersnatch_vrf_sign() { - panic!("TODO") + 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( + b"input1", + &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], + ); + let in2 = bandersnatch::vrf::VrfInput::new( + b"input2", + &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], + ); + let in3 = bandersnatch::vrf::VrfInput::new( + b"input3", + &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], + ); + let sign_data = + bandersnatch::vrf::VrfSignData::from_iter(b"Test", &[b"msg1"], [in1, in2, in3]) + .unwrap(); + + 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()); } } From b6653978dd38b529cac95e4ac82c5fb11048ec76 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 19 Jun 2023 19:20:55 +0200 Subject: [PATCH 06/36] Comments about key derivation --- primitives/core/src/bandersnatch.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 0f71be1b92c1f..d363ec31fc969 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -39,30 +39,29 @@ 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"bs38"); +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); -/// Context used to produce +/// Context used to produce a plain signature without any VRF input/output. #[cfg(feature = "full_crypto")] pub const SIGNING_CTX: &[u8] = b"SigningContext"; #[cfg(feature = "full_crypto")] const SEED_SERIALIZED_LEN: usize = 32; -// Edwards form sizes +// Edwards form sizes. // @burdges @swasilyev: currently ring-proof is using SHORT-WEIERSTRASS // I had to temporary patch bandersnatch_vrfs crate to use SW instrad of ED... -// (@davxy: probably we'll use ED form) // const PUBLIC_SERIALIZED_LEN: usize = 32; // const SIGNATURE_SERIALIZED_LEN: usize = 64; -// Short-Weierstrass form sizes (TEMPORARY) +// Short-Weierstrass form sizes. const PUBLIC_SERIALIZED_LEN: usize = 33; const SIGNATURE_SERIALIZED_LEN: usize = 65; // Edwards form sizes (TODO davxy: probably in the end we'll use this form) // const PREOUT_SERIALIZED_LEN: usize = 32; -// Short-Weierstrass form sizes +// Short-Weierstrass form sizes. const PREOUT_SERIALIZED_LEN: usize = 33; // Size of serialized pedersen-vrf signature @@ -70,10 +69,10 @@ const PREOUT_SERIALIZED_LEN: usize = 33; const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; // Size of serialized ring-proof -// Short-Weierstrass form sizes +// Short-Weierstrass form sizes. const RING_PROOF_SERIALIZED_LEN: usize = 592; -// Sise of serialized ring-vrf context params +// Size of serialized ring-vrf context params // @burdges @swasilyev: This is quite big... // This size grows with the domain size. // Example values @@ -258,12 +257,12 @@ impl TraitPair for Pair { 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]; + // TODO @burdges : we need a serializable dleq_vrf::SecretKey to initialize acc + let mut acc = [0; 32]; for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -301,10 +300,8 @@ impl TraitPair for Pair { /// Return a vector filled with seed raw data. fn to_raw_vec(&self) -> Vec { - // TODO @davxy: this function existance makes sense??? Should we return the seed or - // serialized secret key? If we return the serialized secret there is no method to - // reconstruct if ... unimplemented!() - panic!() + // TODO @burdges: we need a serializable Secret in dleq_vrf? + unimplemented!() } } From 1faf0e2a275fc438749d30f6c7056750a2fb1a85 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 21 Jun 2023 16:52:54 +0200 Subject: [PATCH 07/36] Make clippy happy --- primitives/core/src/bandersnatch.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index d363ec31fc969..8ae3130030a98 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -935,11 +935,10 @@ mod tests { assert!(pks.len() <= ring_ctx.max_keyset_size()); let pair = Pair::from_seed(DEV_SEED); - let public = pair.public(); // Just pick one index to patch with the actual public key let prover_idx = 3; - pks[prover_idx] = public.clone(); + pks[prover_idx] = 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")]); @@ -962,11 +961,10 @@ mod tests { assert!(pks.len() <= ring_ctx.max_keyset_size()); let pair = Pair::from_seed(DEV_SEED); - let public = pair.public(); // Just pick one... let prover_idx = 3; - pks[prover_idx] = public.clone(); + pks[prover_idx] = 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")]); From 7812da8ae69a4faaa7b3ea813c190de7387a8162 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 21 Jun 2023 17:52:43 +0200 Subject: [PATCH 08/36] Fix ring context enc/dec test --- primitives/core/src/bandersnatch.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 8ae3130030a98..b678f5299f4aa 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -915,7 +915,7 @@ mod tests { data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + SIGNATURE_SERIALIZED_LEN + 1; assert_eq!(bytes.len(), expected_len); - let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); assert_eq!(expected, decoded); let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], []).unwrap(); @@ -923,7 +923,7 @@ mod tests { let bytes = expected.encode(); - let decoded = VrfSignature::decode(&mut &bytes[..]).unwrap(); + let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); assert_eq!(expected, decoded); } @@ -983,23 +983,21 @@ mod tests { 1; assert_eq!(bytes.len(), expected_len); - let decoded = RingVrfSignature::decode(&mut &bytes[..]).unwrap(); + let decoded = RingVrfSignature::decode(&mut bytes.as_slice()).unwrap(); assert_eq!(expected, decoded); } - // Requires parity-scale-codec v0.3.6 #[test] - #[ignore] fn encode_decode_ring_vrf_context() { - let ring_ctx = RingVrfContext::new_testing(); - - let encoded = ring_ctx.encode(); - println!("SIZE: {}", encoded.len()); + let ctx1 = RingVrfContext::new_testing(); + let enc1 = ctx1.encode(); - assert_eq!(encoded.len(), RingVrfContext::max_encoded_len()); + println!("SIZE: {}", enc1.len()); + assert_eq!(enc1.len(), RingVrfContext::max_encoded_len()); - let _decoded = RingVrfContext::decode(&mut &encoded[..]).unwrap(); + let ctx2 = RingVrfContext::decode(&mut enc1.as_slice()).unwrap(); + let enc2 = ctx2.encode(); - // TODO davxy... just use unsafe pointers comparison + assert_eq!(enc1, enc2); } } From dd53abc24fe6fd427c5b9c51c0b59821b8e7e41b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 21 Jun 2023 18:09:19 +0200 Subject: [PATCH 09/36] Fix docs --- primitives/keystore/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index a8c8c317c8d6f..d26dc328e5d84 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -229,10 +229,11 @@ pub trait Keystore: Send + Sync { /// 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 [`RingProver`] instance obtained from a valid [`RingVrfContext`]. + /// Also takes a [`bandersnatch::ring_vrf::RingProver`] instance obtained from + /// a valid [`bandersnatch::ring_vrf::RingVrfContext`]. /// /// The signature is valid if the signing key is part of the ring from which - /// the [`RingProver`] has been derived. + /// the `RingProver` has been derived. /// /// Returns `None` if the given `key_type` and `public` combination doesn't /// exist in the keystore or an `Err` when something failed. From 03ca535028bcdbdcd1c87fafa2cf6448b198ec9d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 22 Jun 2023 14:01:29 +0200 Subject: [PATCH 10/36] Switch to upstream ring-vrf --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 812b2840cb4fb..57220d764c436 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +source = "git+https://github.com/w3f/ring-vrf#07d1df31614b66e172c6b1cb2622e9419f502676" dependencies = [ "ark-ec", "ark-ff", @@ -529,7 +529,7 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +source = "git+https://github.com/w3f/ring-vrf#07d1df31614b66e172c6b1cb2622e9419f502676" dependencies = [ "ark-ff", "ark-serialize", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "bandersnatch_vrfs" version = "0.0.1" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +source = "git+https://github.com/w3f/ring-vrf#07d1df31614b66e172c6b1cb2622e9419f502676" dependencies = [ "ark-bls12-381", "ark-ec", @@ -2197,7 +2197,7 @@ checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/davxy/ring-vrf?branch=refactory-and-tests#b391e66f73656b123bcd103c3e81de18e253236d" +source = "git+https://github.com/w3f/ring-vrf#07d1df31614b66e172c6b1cb2622e9419f502676" dependencies = [ "ark-ec", "ark-ff", diff --git a/Cargo.toml b/Cargo.toml index 80b31bf69d974..e48ba92443896 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -338,8 +338,8 @@ 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 = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } +# [patch."https://github.com/w3f/ring-vrf"] +# bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } # bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } [patch."https://github.com/w3f/fflonk"] From afb84c519f6f06068f12e7d7084283dea2bd3e15 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 26 Jun 2023 10:08:50 +0200 Subject: [PATCH 11/36] Use sub-domains to construct VrfInput --- primitives/core/src/bandersnatch.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index b678f5299f4aa..2f23f5ec134b6 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -334,23 +334,21 @@ pub mod vrf { impl VrfInput { /// Build a new VRF input. /// - /// Each message tuple has the form: message := (domain, data). - pub fn new(label: &'static [u8], messages: &[(&[u8], &[u8])]) -> Self { - // ⚠️ TODO @davxy @burdges (temporary hack and needs to be fixed) - // `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 - // THIS IS JUST A PLACEHOLDER and we are ignoring the lablel and concat the dom++msgs. - let _ = label; + /// Each message tuple has the form: message_data := (sub-domain, data). + pub fn new(domain: &'static [u8], message_data: &[(&[u8], &[u8])]) -> Self { + // ⚠️ TODO @davxy @burdges (temporary hack and probably needs to be fixed) + // In sassafras we want to construct a single `VrfInput` from multiple datas + // E.g. the ticket score uses: epoch-randomness, attempt-index, epoch-index + // Currently, `bandersnatch_vrfs::Message` has a single (domain, data) fields, + // so we need a workaround here... let mut buf = Vec::new(); - messages.into_iter().for_each(|(domain, message)| { - buf.extend_from_slice(domain); - buf.extend_from_slice(message); + message_data.into_iter().for_each(|(sub_domain, data)| { + buf.extend_from_slice(sub_domain); + buf.extend_from_slice(&sub_domain.len().to_le_bytes()); + buf.extend_from_slice(data); + buf.extend_from_slice(&data.len().to_le_bytes()); }); - let msg = Message { domain: b"TODO-DAVXY-FIXME", message: buf.as_slice() }; + let msg = Message { domain, message: buf.as_slice() }; VrfInput(msg.into_vrf_input()) } } From b4e0279bca4c3f241e299f6e16605e6372fd6326 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 26 Jun 2023 11:15:20 +0200 Subject: [PATCH 12/36] Bandersnatch VRF experimental feature --- client/keystore/Cargo.toml | 13 +++++++++++-- client/keystore/src/local.rs | 13 ++++++++++--- primitives/application-crypto/Cargo.toml | 5 +++++ primitives/application-crypto/src/lib.rs | 1 + primitives/core/Cargo.toml | 14 +++++++++++--- primitives/core/src/lib.rs | 1 + primitives/io/src/lib.rs | 4 +++- primitives/keyring/Cargo.toml | 6 ++++++ primitives/keyring/src/lib.rs | 2 ++ primitives/keystore/Cargo.toml | 10 ++++++++-- primitives/keystore/src/lib.rs | 21 ++++++++++++++++++--- primitives/keystore/src/testing.rs | 18 ++++++++++++++---- 12 files changed, 90 insertions(+), 18 deletions(-) diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index aa1527a4f67eb..e598964cc0f4e 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 change. 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 change. +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 47cb0f57fe8ff..d9109bae9c3d5 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -19,13 +19,14 @@ 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::{ - bandersnatch, crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSecret}, ecdsa, ed25519, sr25519, }; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use sp_keystore::{Error as TraitError, Keystore, KeystorePtr}; use std::{ collections::HashMap, @@ -235,6 +236,7 @@ impl Keystore for LocalKeystore { Ok(sig) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type) } @@ -242,6 +244,7 @@ impl Keystore for LocalKeystore { /// 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, @@ -250,6 +253,7 @@ impl Keystore for LocalKeystore { self.generate_new::(key_type, seed) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_sign( &self, key_type: KeyTypeId, @@ -262,6 +266,7 @@ impl Keystore for LocalKeystore { // TODO @davxy // Maybe we can expose just this bandersnatch sign (the above one reduces to this with // input len = 0) + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign( &self, key_type: KeyTypeId, @@ -271,6 +276,7 @@ impl Keystore for LocalKeystore { self.vrf_sign::(key_type, public, data) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_output( &self, key_type: KeyTypeId, @@ -280,6 +286,7 @@ impl Keystore for LocalKeystore { self.vrf_output::(key_type, public, input) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_ring_vrf_sign( &self, key_type: KeyTypeId, diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index 60f05e90aba31..b4b732be7b195 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -51,3 +51,8 @@ full_crypto = [ "sp-io/disable_panic_handler", "sp-io/disable_oom", ] + +# This feature adds Bandersnatch crypto primitives. +# It shoud not be used in production since the implementation and interface may still +# be subject to significant change. +bandersnatch-experimental = ["sp-core/bandersnatch-experimental"] diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index d032b80fa9a24..af0d6dcbfed78 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -43,6 +43,7 @@ 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; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 404913fafee4f..763eaaafe1699 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -13,7 +13,6 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", default-features = false } 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"] } @@ -54,8 +53,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", default-features = false, optional = true } [dev-dependencies] rand = "0.8.5" @@ -147,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 change. 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 change. +bandersnatch-experimental = ["bandersnatch_vrfs"] diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index dd003e8186b41..ea813d029fcb4 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -55,6 +55,7 @@ pub mod crypto; pub mod hexdisplay; pub use paste; +#[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; #[cfg(feature = "bls-experimental")] pub mod bls; diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 124cebf643453..6af250d351649 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -44,8 +44,9 @@ use sp_core::{ #[cfg(feature = "std")] use sp_keystore::KeystoreExt; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; use sp_core::{ - bandersnatch, crypto::KeyTypeId, ecdsa, ed25519, offchain::{ @@ -1148,6 +1149,7 @@ pub trait Crypto { /// The `seed` needs to be a valid utf8. /// /// Returns the public key. + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_generate( &mut self, id: KeyTypeId, diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 2b08755dba1ad..3122643f08ba6 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 shoud not be used in production since the implementation and interface may still +# be subject to significant change. +bandersnatch-experimental = ["sp-core/bandersnatch-experimental"] diff --git a/primitives/keyring/src/lib.rs b/primitives/keyring/src/lib.rs index fba15a121e818..1db18f7edbdc8 100644 --- a/primitives/keyring/src/lib.rs +++ b/primitives/keyring/src/lib.rs @@ -24,6 +24,7 @@ pub mod sr25519; pub mod ed25519; /// Test account crypto for bandersnatch. +#[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; /// Convenience export: Sr25519's Keyring is exposed as `AccountKeyring`, @@ -31,6 +32,7 @@ pub mod bandersnatch; /// 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 4f317cf82eb41..17cb9ff102b53 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 crypto primitives. +# It should not be used in production since the implementation and interface may still +# be subject to significant change. +bandersnatch-experimental = ["sp-core/bandersnatch-experimental"] diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index d26dc328e5d84..817d33b01dac0 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -19,13 +19,14 @@ pub mod testing; +#[cfg(feature = "bandersnatch-experimental")] +use sp_core::bandersnatch; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use sp_core::{ - bandersnatch, crypto::{ByteArray, CryptoTypeId, KeyTypeId}, ecdsa, ed25519, sr25519, }; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use std::sync::Arc; @@ -176,12 +177,14 @@ pub trait Keystore: Send + Sync { ) -> 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, @@ -196,6 +199,7 @@ pub trait Keystore: Send + Sync { /// 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, @@ -210,6 +214,7 @@ pub trait Keystore: Send + Sync { /// /// 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, @@ -217,6 +222,14 @@ pub trait Keystore: Send + Sync { 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, @@ -237,6 +250,7 @@ pub trait Keystore: Send + Sync { /// /// 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, @@ -363,6 +377,7 @@ 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()))?; diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index 7d57129ea02a2..c15e2770e10ad 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -19,13 +19,14 @@ 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::{ - bandersnatch, crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, }; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -215,10 +216,12 @@ 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, @@ -227,6 +230,7 @@ impl Keystore for MemoryKeystore { self.generate_new::(key_type, seed) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_sign( &self, key_type: KeyTypeId, @@ -236,6 +240,7 @@ impl Keystore for MemoryKeystore { self.sign::(key_type, public, msg) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign( &self, key_type: KeyTypeId, @@ -245,6 +250,7 @@ impl Keystore for MemoryKeystore { self.vrf_sign::(key_type, public, data) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_ring_vrf_sign( &self, key_type: KeyTypeId, @@ -258,6 +264,7 @@ impl Keystore for MemoryKeystore { Ok(sig) } + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_output( &self, key_type: KeyTypeId, @@ -352,7 +359,7 @@ mod tests { use super::*; use sp_core::{ sr25519, - testing::{BANDERSNATCH, ECDSA, ED25519, SR25519}, + testing::{ECDSA, ED25519, SR25519}, }; #[test] @@ -461,7 +468,10 @@ mod tests { } #[test] + #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign() { + use sp_core::testing::BANDERSNATCH; + let store = MemoryKeystore::new(); let secret_uri = "//Alice"; From 584e07ef344b0941e1f0c80012cd0552e9af6da3 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 29 Jun 2023 08:13:49 +0200 Subject: [PATCH 13/36] Restore upstream dep --- Cargo.lock | 6 +++--- Cargo.toml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57220d764c436..bc0b11dde8c70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1444,7 +1444,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" dependencies = [ "ark-ec", "ark-ff", @@ -2570,7 +2570,7 @@ dependencies = [ [[package]] name = "fflonk" version = "0.1.0" -source = "git+https://github.com/davxy/fflonk?branch=working-fork#a2664567b88d96e1dc2f82f8799b2ca60171c81d" +source = "git+https://github.com/w3f/fflonk#26a5045b24e169cffc1f9328ca83d71061145c40" dependencies = [ "ark-ec", "ark-ff", @@ -8723,7 +8723,7 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/davxy/ring-proof?branch=working-fork#5bdca95a9d0434c722a98b3310db6e46e8fbd981" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" dependencies = [ "ark-ec", "ark-ff", diff --git a/Cargo.toml b/Cargo.toml index e48ba92443896..873b03578c34d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,12 +342,12 @@ codegen-units = 1 # bandersnatch_vrfs = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } # bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } -[patch."https://github.com/w3f/fflonk"] -fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } +# [patch."https://github.com/w3f/fflonk"] +# fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } # fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } -[patch."https://github.com/w3f/ring-proof"] -common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +# [patch."https://github.com/w3f/ring-proof"] +# common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } +# ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } # common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } # ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } From 7791d66cf17d39b2649c7f4be82898ea2f30982f Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 29 Jun 2023 08:14:13 +0200 Subject: [PATCH 14/36] Fix feature flags --- primitives/application-crypto/Cargo.toml | 5 ++++- primitives/io/Cargo.toml | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index b4b732be7b195..26373ce0fde46 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -55,4 +55,7 @@ full_crypto = [ # This feature adds Bandersnatch crypto primitives. # It shoud not be used in production since the implementation and interface may still # be subject to significant change. -bandersnatch-experimental = ["sp-core/bandersnatch-experimental"] +bandersnatch-experimental = [ + "sp-core/bandersnatch-experimental", + "sp-io/bandersnatch-experimental", +] diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 728455ef33e38..44c2f6f2a4155 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 crypto primitives. +# It should not be used in production since the implementation and interface may still +# be subject to significant change. +bandersnatch-experimental = [ + "sp-core/bandersnatch-experimental", + "sp-keystore/bandersnatch-experimental", +] From acaab76e669b8aa1bdad89ab41417089eb4d0b09 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 18 Jul 2023 18:16:26 +0200 Subject: [PATCH 15/36] Apply typo fix Co-authored-by: Anton --- primitives/core/src/bandersnatch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 2f23f5ec134b6..a20ea273eac5f 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -50,7 +50,7 @@ const SEED_SERIALIZED_LEN: usize = 32; // Edwards form sizes. // @burdges @swasilyev: currently ring-proof is using SHORT-WEIERSTRASS -// I had to temporary patch bandersnatch_vrfs crate to use SW instrad of ED... +// I had to temporary patch bandersnatch_vrfs crate to use SW instead of ED... // const PUBLIC_SERIALIZED_LEN: usize = 32; // const SIGNATURE_SERIALIZED_LEN: usize = 64; From f22354d515b026391c0aa90e43cfa69b84604fd7 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 18 Jul 2023 18:20:58 +0200 Subject: [PATCH 16/36] Bump bandersnatch-vrfs --- Cargo.lock | 10 ++--- primitives/core/src/bandersnatch.rs | 64 +++++++++++++++++------------ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efde8b5b1b665..ba6eace546214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#583242e34fb62001850178c29207a07dd84fff75" +source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ec", "ark-ff", @@ -529,7 +529,7 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#583242e34fb62001850178c29207a07dd84fff75" +source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ff", "ark-serialize", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "bandersnatch_vrfs" version = "0.0.1" -source = "git+https://github.com/w3f/ring-vrf#583242e34fb62001850178c29207a07dd84fff75" +source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-bls12-381", "ark-ec", @@ -2168,7 +2168,7 @@ checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#583242e34fb62001850178c29207a07dd84fff75" +source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ec", "ark-ff", @@ -12871,7 +12871,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.8.5", + "rand 0.7.3", "static_assertions", ] diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index a20ea273eac5f..d81dd6cc6c2ab 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -50,7 +50,7 @@ const SEED_SERIALIZED_LEN: usize = 32; // Edwards form sizes. // @burdges @swasilyev: currently ring-proof is using SHORT-WEIERSTRASS -// I had to temporary patch bandersnatch_vrfs crate to use SW instead of ED... +// I had to temporary patch bandersnatch_vrfs crate to use SW instrad of ED... // const PUBLIC_SERIALIZED_LEN: usize = 32; // const SIGNATURE_SERIALIZED_LEN: usize = 64; @@ -72,15 +72,20 @@ const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; // Short-Weierstrass form sizes. const RING_PROOF_SERIALIZED_LEN: usize = 592; -// Size of serialized ring-vrf context params +// Max ring domain size +const RING_MAX_SIZE: u32 = 1024; + +// Max size of serialized ring-vrf context params. +// +// This size is dependent on the ring size. +// +// Some values: +// ring-size → ~serialized-size +// 512 → 74 KB +// 1024 → 147 KB +// 2048 → 295 KB // @burdges @swasilyev: This is quite big... -// This size grows with the domain size. -// Example values -// domsize -> serialized-size-in-kilobytes -// 2^9 -> 74 KB -// 2^10 -> 147 KB -// 2^11 -> 295 KB -const RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN: usize = 147744; +const RING_CONTEXT_SERIALIZED_LEN: usize = 147752; /// Bandersnatch public key. #[cfg_attr(feature = "full_crypto", derive(Hash))] @@ -568,14 +573,18 @@ pub mod vrf { .iter() .map(|o| o.0.clone()) .collect::>() - .into_inner() else { - return false - }; + .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 { + let Ok(signature) = + ThinVrfSignature::<0>::deserialize_compressed(signature.signature.as_ref()) + .map(|s| s.signature) + else { return false }; let signature = ThinVrfSignature { signature, preoutputs: preouts }; @@ -614,14 +623,11 @@ pub mod ring_vrf { impl RingVrfContext { /// Build an dummy instance used for testing purposes. pub fn new_testing() -> Self { - let seed = [0; 32]; - let domain_size = 2usize.pow(10); - let kzg = KZG::testing_kzg_setup(seed, domain_size); - Self(kzg) + Self(KZG::testing_kzg_setup([0; 32], RING_MAX_SIZE)) } /// Get the keyset size. - /// TODO @swasilyev: shouldn't be equal to the domain_size used at init time? + /// TODO @swasilyev: shouldn't this be equal to the domain_size used at init time? pub fn max_keyset_size(&self) -> usize { self.0.max_keyset_size() } @@ -655,7 +661,7 @@ pub mod ring_vrf { impl Encode for RingVrfContext { fn encode(&self) -> Vec { - let mut buf = Box::new([0; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]); + let mut buf = Box::new([0; RING_CONTEXT_SERIALIZED_LEN]); self.0 .serialize_compressed(buf.as_mut_slice()) .expect("preout serialization can't fail"); @@ -665,7 +671,7 @@ pub mod ring_vrf { impl Decode for RingVrfContext { fn decode(i: &mut R) -> Result { - let buf = >::decode(i)?; + let buf = >::decode(i)?; let kzg = KZG::deserialize_compressed(buf.as_slice()).map_err(|_| "KZG decode error")?; Ok(RingVrfContext(kzg)) @@ -674,12 +680,12 @@ pub mod ring_vrf { impl MaxEncodedLen for RingVrfContext { fn max_encoded_len() -> usize { - <[u8; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]>::max_encoded_len() + <[u8; RING_CONTEXT_SERIALIZED_LEN]>::max_encoded_len() } } impl TypeInfo for RingVrfContext { - type Identity = [u8; RING_VRF_CONTEXT_PARAMS_SERIALIZED_LEN]; + type Identity = [u8; RING_CONTEXT_SERIALIZED_LEN]; fn type_info() -> scale_info::Type { Self::Identity::type_info() @@ -775,15 +781,19 @@ pub mod ring_vrf { .iter() .map(|o| o.0.clone()) .collect::>() - .into_inner() else { - return false - }; + .into_inner() + else { + return false + }; - let Ok(signature) = PedersenVrfSignature::deserialize_compressed(self.signature.as_slice()) else { + 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 { + let Ok(ring_proof) = RingProof::deserialize_compressed(self.ring_proof.as_slice()) + else { return false }; From 62d2ed4af94940e83b16b6ecb828c31bd39dc60c Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 18 Jul 2023 18:52:26 +0200 Subject: [PATCH 17/36] Weiestrass form has been selected --- primitives/core/src/bandersnatch.rs | 57 ++++++++++------------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index d81dd6cc6c2ab..8e3cc36d273c5 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -17,9 +17,6 @@ //! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch), //! an elliptic curve built over BLS12-381 scalar field. -//! -//! The Bandersnatch curve can be represented in twisted Edwards coordinates, allowing -//! efficieny inside zk-SNARKS circuits. #[cfg(feature = "std")] use crate::crypto::Ss58Codec; @@ -45,46 +42,31 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); #[cfg(feature = "full_crypto")] pub const SIGNING_CTX: &[u8] = b"SigningContext"; +// Max ring domain size +const RING_MAX_SIZE: u32 = 1024; + #[cfg(feature = "full_crypto")] const SEED_SERIALIZED_LEN: usize = 32; -// Edwards form sizes. -// @burdges @swasilyev: currently ring-proof is using SHORT-WEIERSTRASS -// I had to temporary patch bandersnatch_vrfs crate to use SW instrad of ED... -// const PUBLIC_SERIALIZED_LEN: usize = 32; -// const SIGNATURE_SERIALIZED_LEN: usize = 64; - -// Short-Weierstrass form sizes. +// Short-Weierstrass form serialized sizes. const PUBLIC_SERIALIZED_LEN: usize = 33; const SIGNATURE_SERIALIZED_LEN: usize = 65; - -// Edwards form sizes (TODO davxy: probably in the end we'll use this form) -// const PREOUT_SERIALIZED_LEN: usize = 32; - -// Short-Weierstrass form sizes. const PREOUT_SERIALIZED_LEN: usize = 33; - -// Size of serialized pedersen-vrf signature -// Short-Weierstrass form sizes const PEDERSEN_SIGNATURE_SERIALIZED_LEN: usize = 163; - -// Size of serialized ring-proof -// Short-Weierstrass form sizes. const RING_PROOF_SERIALIZED_LEN: usize = 592; -// Max ring domain size -const RING_MAX_SIZE: u32 = 1024; - // Max size of serialized ring-vrf context params. // // This size is dependent on the ring size. // // Some values: -// ring-size → ~serialized-size +// ring_size → ~serialized_size // 512 → 74 KB // 1024 → 147 KB // 2048 → 295 KB -// @burdges @swasilyev: This is quite big... +// TODO @swasilyev: This is quite big... can we do anything about it? +// Looks like the relationship is: +// serialized_size(ring_size) = 144·(ring_size + 2) + 4 const RING_CONTEXT_SERIALIZED_LEN: usize = 147752; /// Bandersnatch public key. @@ -266,7 +248,7 @@ impl TraitPair for Pair { ("bandersnatch-vrf-HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) }; - // TODO @burdges : we need a serializable dleq_vrf::SecretKey to initialize acc + // TODO @burdges : probably we need a serializable dleq_vrf::SecretKey to initialize acc let mut acc = [0; 32]; for j in path { match j { @@ -305,7 +287,7 @@ impl TraitPair for Pair { /// Return a vector filled with seed raw data. fn to_raw_vec(&self) -> Vec { - // TODO @burdges: we need a serializable Secret in dleq_vrf? + // TODO @burdges: for this we need a serializable dleq_vrfs::SecretKey unimplemented!() } } @@ -401,7 +383,7 @@ pub mod vrf { pub struct VrfSignData { /// VRF inputs to be signed. pub vrf_inputs: VrfIosVec, - /// Associated Fiat-Shamir transcript + /// Associated Fiat-Shamir transcript. pub transcript: Transcript, } @@ -411,9 +393,9 @@ pub mod vrf { /// The `transcript_data` will be used as messages for the Fiat-Shamir /// transform part of the scheme. If unsure just give it a unique label /// depending on the actual usage of the signing data - /// (@burges: or leave it empty? There is already the `label` field for contextualization). - /// The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and which - /// contribute to the actual output bytes produced via the VRF. + /// (TODO @burges: or leave it empty? There is already the `label` field for + /// contextualization). The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and + /// which contribute to the actual output bytes produced via the VRF. pub fn new>>( label: &'static [u8], transcript_data: &[&[u8]], @@ -480,9 +462,8 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { - // TODO: @davxy // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? match data.vrf_inputs.len() { 0 => self.vrf_sign_gen::<0>(data), 1 => self.vrf_sign_gen::<1>(data), @@ -512,7 +493,7 @@ pub mod vrf { return false } // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? match preouts_len { 0 => self.vrf_verify_gen::<0>(data, signature), 1 => self.vrf_verify_gen::<1>(data, signature), @@ -711,7 +692,7 @@ pub mod ring_vrf { /// the [`RingProver`] has been derived. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? match data.vrf_inputs.len() { 0 => self.ring_vrf_sign_gen::<0>(data, prover), 1 => self.ring_vrf_sign_gen::<1>(data, prover), @@ -764,8 +745,8 @@ pub mod ring_vrf { if preouts_len != data.vrf_inputs.len() { return false } - // Hack used because backend signature type is generic over the number of ios - // @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // Hack used because backend signature type is generic over the number of ios. + // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? match preouts_len { 0 => self.verify_gen::<0>(data, verifier), 1 => self.verify_gen::<1>(data, verifier), From 759406e06a9c8fbae3f8363ea85c483c47e906e0 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 18 Jul 2023 18:58:12 +0200 Subject: [PATCH 18/36] Rename bandersnatch testing app crypto id --- primitives/core/src/testing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index a1889d6779af6..25f5f9012c996 100644 --- a/primitives/core/src/testing.rs +++ b/primitives/core/src/testing.rs @@ -26,7 +26,7 @@ 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"); +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. From 3ccdd86a2973c5067b2e0c7ee4aab11b75eaff1b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sat, 22 Jul 2023 12:53:45 +0200 Subject: [PATCH 19/36] Support for seed recovery --- primitives/core/src/bandersnatch.rs | 69 +++++++++++++++++------------ 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 8e3cc36d273c5..6043255792806 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -215,7 +215,18 @@ type Seed = [u8; SEED_SERIALIZED_LEN]; /// Bandersnatch secret key. #[cfg(feature = "full_crypto")] #[derive(Clone)] -pub struct Pair(SecretKey); +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 { @@ -230,10 +241,10 @@ impl TraitPair for Pair { 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)) + 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. @@ -244,24 +255,24 @@ impl TraitPair for Pair { path: Iter, _seed: Option, ) -> Result<(Pair, Option), DeriveError> { - let derive_hard_junction = |secret_seed, cc| -> Seed { - ("bandersnatch-vrf-HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) + let derive_hard = |seed, cc| -> Seed { + ("bandersnatch-vrf-HDKD", seed, cc).using_encoded(sp_core_hashing::blake2_256) }; - // TODO @burdges : probably we need a serializable dleq_vrf::SecretKey to initialize acc - let mut acc = [0; 32]; - for j in path { - match j { - DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), - DeriveJunction::Hard(cc) => acc = derive_hard_junction(acc, cc), + 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(&acc), Some(acc))) + Ok((Self::from_seed(&seed), Some(seed))) } /// Get the public key. fn public(&self) -> Public { - let public = self.0.to_public(); + let public = self.secret.to_public(); let mut raw = [0; PUBLIC_SERIALIZED_LEN]; public .serialize_compressed(raw.as_mut_slice()) @@ -287,8 +298,7 @@ impl TraitPair for Pair { /// Return a vector filled with seed raw data. fn to_raw_vec(&self) -> Vec { - // TODO @burdges: for this we need a serializable dleq_vrfs::SecretKey - unimplemented!() + self.seed().to_vec() } } @@ -323,7 +333,7 @@ pub mod vrf { /// /// Each message tuple has the form: message_data := (sub-domain, data). pub fn new(domain: &'static [u8], message_data: &[(&[u8], &[u8])]) -> Self { - // ⚠️ TODO @davxy @burdges (temporary hack and probably needs to be fixed) + // TODO @burdges (temporary hack?) // In sassafras we want to construct a single `VrfInput` from multiple datas // E.g. the ticket score uses: epoch-randomness, attempt-index, epoch-index // Currently, `bandersnatch_vrfs::Message` has a single (domain, data) fields, @@ -474,7 +484,7 @@ pub mod vrf { } fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { - let output = self.0 .0.vrf_preout(&input.0); + let output = self.secret.0.vrf_preout(&input.0); VrfOutput(output) } } @@ -510,11 +520,11 @@ pub mod vrf { let ios: Vec<_> = data .vrf_inputs .iter() - .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) + .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) .collect(); let signature: ThinVrfSignature = - self.0.sign_thin_vrf(data.transcript.clone(), ios.as_slice()); + self.secret.sign_thin_vrf(data.transcript.clone(), ios.as_slice()); let mut sign_bytes = [0; SIGNATURE_SERIALIZED_LEN]; signature @@ -534,7 +544,7 @@ pub mod vrf { input: &VrfInput, ) -> [u8; N] { let transcript = Transcript::new_labeled(context); - let inout = self.0.clone().0.vrf_inout(input.0.clone()); + let inout = self.secret.clone().0.vrf_inout(input.0.clone()); inout.vrf_output_bytes(transcript) } } @@ -710,11 +720,11 @@ pub mod ring_vrf { let ios: Vec<_> = data .vrf_inputs .iter() - .map(|i| self.0.clone().0.vrf_inout(i.0.clone())) + .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) .collect(); let ring_signature: bandersnatch_vrfs::RingVrfSignature = - self.0.sign_ring_vrf(data.transcript.clone(), ios.as_slice(), prover); + 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); @@ -814,11 +824,14 @@ mod tests { } #[test] - fn derive_hard_known_pair() { - let pair = Pair::from_string(&format!("{}//Alice", DEV_PHRASE), None).unwrap(); - // known address of DEV_PHRASE - let known = h2b("646b261d8058ba8a3c4ebe152dc837fb2259433270dd5bdc79095874cc78b62f00"); + 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] From e8a40ab9dd6aa4c4e4f76730a5267774eab7d995 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 25 Jul 2023 18:03:24 +0200 Subject: [PATCH 20/36] Clarified domain size <-> key size relationship --- primitives/core/src/bandersnatch.rs | 55 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 6043255792806..8e0b76557ff91 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -42,8 +42,8 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); #[cfg(feature = "full_crypto")] pub const SIGNING_CTX: &[u8] = b"SigningContext"; -// Max ring domain size -const RING_MAX_SIZE: u32 = 1024; +// Max ring domain size. +const RING_DOMAIN_SIZE: usize = 1024; #[cfg(feature = "full_crypto")] const SEED_SERIALIZED_LEN: usize = 32; @@ -115,7 +115,7 @@ impl TryFrom<&[u8]> for Public { fn try_from(data: &[u8]) -> Result { if data.len() != PUBLIC_SERIALIZED_LEN { - return Err(()) + return Err(()); } let mut r = [0u8; PUBLIC_SERIALIZED_LEN]; r.copy_from_slice(data); @@ -179,7 +179,7 @@ impl TryFrom<&[u8]> for Signature { fn try_from(data: &[u8]) -> Result { if data.len() != SIGNATURE_SERIALIZED_LEN { - return Err(()) + return Err(()); } let mut r = [0u8; SIGNATURE_SERIALIZED_LEN]; r.copy_from_slice(data); @@ -239,7 +239,7 @@ impl TraitPair for Pair { /// 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) + return Err(SecretStringError::InvalidSeedLength); } let mut seed = [0; SEED_SERIALIZED_LEN]; seed.copy_from_slice(seed_slice); @@ -264,7 +264,7 @@ impl TraitPair for Pair { if let DeriveJunction::Hard(cc) = p { seed = derive_hard(seed, cc); } else { - return Err(DeriveError::SoftKeyInPath) + return Err(DeriveError::SoftKeyInPath); } } Ok((Self::from_seed(&seed), Some(seed))) @@ -500,7 +500,7 @@ pub mod vrf { 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 + return false; } // Hack used because backend signature type is generic over the number of ios // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? @@ -609,16 +609,15 @@ pub mod ring_vrf { /// Context used to produce ring signatures. #[derive(Clone)] - pub struct RingVrfContext(KZG); + pub struct RingContext(KZG); - impl RingVrfContext { + impl RingContext { /// Build an dummy instance used for testing purposes. pub fn new_testing() -> Self { - Self(KZG::testing_kzg_setup([0; 32], RING_MAX_SIZE)) + Self(KZG::testing_kzg_setup([0; 32], RING_DOMAIN_SIZE as u32)) } - /// Get the keyset size. - /// TODO @swasilyev: shouldn't this be equal to the domain_size used at init time? + /// Get the keyset max size. pub fn max_keyset_size(&self) -> usize { self.0.max_keyset_size() } @@ -650,7 +649,7 @@ pub mod ring_vrf { } } - impl Encode for RingVrfContext { + impl Encode for RingContext { fn encode(&self) -> Vec { let mut buf = Box::new([0; RING_CONTEXT_SERIALIZED_LEN]); self.0 @@ -660,22 +659,22 @@ pub mod ring_vrf { } } - impl Decode for RingVrfContext { + 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(RingVrfContext(kzg)) + Ok(RingContext(kzg)) } } - impl MaxEncodedLen for RingVrfContext { + impl MaxEncodedLen for RingContext { fn max_encoded_len() -> usize { <[u8; RING_CONTEXT_SERIALIZED_LEN]>::max_encoded_len() } } - impl TypeInfo for RingVrfContext { + impl TypeInfo for RingContext { type Identity = [u8; RING_CONTEXT_SERIALIZED_LEN]; fn type_info() -> scale_info::Type { @@ -753,7 +752,7 @@ pub mod ring_vrf { pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let preouts_len = self.outputs.len(); if preouts_len != data.vrf_inputs.len() { - return false + return false; } // Hack used because backend signature type is generic over the number of ios. // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? @@ -817,10 +816,12 @@ mod tests { #[test] fn backend_assumptions_check() { + 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); } #[test] @@ -931,7 +932,7 @@ mod tests { #[test] fn ring_vrf_sign_verify() { - let ring_ctx = RingVrfContext::new_testing(); + 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()); @@ -957,7 +958,7 @@ mod tests { #[test] fn encode_decode_ring_vrf_signature() { - let ring_ctx = RingVrfContext::new_testing(); + 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()); @@ -979,10 +980,10 @@ mod tests { let bytes = expected.encode(); - let expected_len = data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + - PEDERSEN_SIGNATURE_SERIALIZED_LEN + - RING_PROOF_SERIALIZED_LEN + - 1; + 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(); @@ -991,13 +992,13 @@ mod tests { #[test] fn encode_decode_ring_vrf_context() { - let ctx1 = RingVrfContext::new_testing(); + let ctx1 = RingContext::new_testing(); let enc1 = ctx1.encode(); println!("SIZE: {}", enc1.len()); - assert_eq!(enc1.len(), RingVrfContext::max_encoded_len()); + assert_eq!(enc1.len(), RingContext::max_encoded_len()); - let ctx2 = RingVrfContext::decode(&mut enc1.as_slice()).unwrap(); + let ctx2 = RingContext::decode(&mut enc1.as_slice()).unwrap(); let enc2 = ctx2.encode(); assert_eq!(enc1, enc2); From 84ad4ae89e80e269cb17e684dfa9687e08496567 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 25 Jul 2023 18:16:34 +0200 Subject: [PATCH 21/36] cargo fmt --- primitives/core/src/bandersnatch.rs | 20 ++++++++++---------- primitives/keystore/src/lib.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 8e0b76557ff91..cbd223a290335 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -115,7 +115,7 @@ impl TryFrom<&[u8]> for Public { fn try_from(data: &[u8]) -> Result { if data.len() != PUBLIC_SERIALIZED_LEN { - return Err(()); + return Err(()) } let mut r = [0u8; PUBLIC_SERIALIZED_LEN]; r.copy_from_slice(data); @@ -179,7 +179,7 @@ impl TryFrom<&[u8]> for Signature { fn try_from(data: &[u8]) -> Result { if data.len() != SIGNATURE_SERIALIZED_LEN { - return Err(()); + return Err(()) } let mut r = [0u8; SIGNATURE_SERIALIZED_LEN]; r.copy_from_slice(data); @@ -239,7 +239,7 @@ impl TraitPair for Pair { /// 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); + return Err(SecretStringError::InvalidSeedLength) } let mut seed = [0; SEED_SERIALIZED_LEN]; seed.copy_from_slice(seed_slice); @@ -264,7 +264,7 @@ impl TraitPair for Pair { if let DeriveJunction::Hard(cc) = p { seed = derive_hard(seed, cc); } else { - return Err(DeriveError::SoftKeyInPath); + return Err(DeriveError::SoftKeyInPath) } } Ok((Self::from_seed(&seed), Some(seed))) @@ -500,7 +500,7 @@ pub mod vrf { 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; + return false } // Hack used because backend signature type is generic over the number of ios // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? @@ -752,7 +752,7 @@ pub mod ring_vrf { pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { let preouts_len = self.outputs.len(); if preouts_len != data.vrf_inputs.len() { - return false; + return false } // Hack used because backend signature type is generic over the number of ios. // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? @@ -980,10 +980,10 @@ mod tests { let bytes = expected.encode(); - let expected_len = data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN - + PEDERSEN_SIGNATURE_SERIALIZED_LEN - + RING_PROOF_SERIALIZED_LEN - + 1; + 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(); diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 8de7e10ed7861..9e7dddf9756c9 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -243,7 +243,7 @@ pub trait Keystore: Send + Sync { /// 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::RingVrfContext`]. + /// a valid [`bandersnatch::ring_vrf::RingContext`]. /// /// The signature is valid if the signing key is part of the ring from which /// the `RingProver` has been derived. From ca11f8ddefd4352d582e295a09d2ff615b3e2a92 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 26 Jul 2023 10:25:11 +0200 Subject: [PATCH 22/36] Trigger CI From b27c61f2817853c16f79c1fea86f413e7dba2610 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Jul 2023 12:04:33 +0200 Subject: [PATCH 23/36] Some required tweaks to crypto types --- primitives/application-crypto/src/traits.rs | 40 +++++++++------------ primitives/core/src/crypto.rs | 2 +- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index d8869f19d0dab..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 + 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/src/crypto.rs b/primitives/core/src/crypto.rs index 5d6ae2f93d329..8fa3316b4d820 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -484,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)] From a5e4cd409ad44e34e19d1f5ab1b03d892b036241 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Jul 2023 12:14:43 +0200 Subject: [PATCH 24/36] Remove leftovers from Cargo.toml --- Cargo.toml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9440e8597678a..bbbb8563f5b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -382,17 +382,3 @@ 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 = { git = "https://github.com/davxy/ring-vrf", branch = "refactory-and-tests" } -# bandersnatch_vrfs = { path = "/mnt/ssd/develop/w3f/ring-vrf/bandersnatch_vrfs" } - -# [patch."https://github.com/w3f/fflonk"] -# fflonk = { git = "https://github.com/davxy/fflonk", branch = "working-fork" } -# fflonk = { path = "/mnt/ssd/develop/w3f/fflonk" } - -# [patch."https://github.com/w3f/ring-proof"] -# common = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -# ring = { git = "https://github.com/davxy/ring-proof", branch = "working-fork" } -# common = { path = "/mnt/ssd/develop/w3f/ring-proof/common" } -# ring = { path = "/mnt/ssd/develop/w3f/ring-proof/ring" } From 7bf3c7092d9db4cdf1bc0d15e8e01613a71e61d8 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Jul 2023 18:40:03 +0200 Subject: [PATCH 25/36] Remove some TODO notes --- client/keystore/src/local.rs | 3 -- primitives/core/src/bandersnatch.rs | 55 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index d9109bae9c3d5..97bc7c71a4a58 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -263,9 +263,6 @@ impl Keystore for LocalKeystore { self.sign::(key_type, public, msg) } - // TODO @davxy - // Maybe we can expose just this bandersnatch sign (the above one reduces to this with - // input len = 0) #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign( &self, diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index cbd223a290335..5771f363730b0 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -64,9 +64,8 @@ const RING_PROOF_SERIALIZED_LEN: usize = 592; // 512 → 74 KB // 1024 → 147 KB // 2048 → 295 KB -// TODO @swasilyev: This is quite big... can we do anything about it? -// Looks like the relationship is: -// serialized_size(ring_size) = 144·(ring_size + 2) + 4 +// 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. @@ -317,6 +316,8 @@ pub mod vrf { }; /// 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. @@ -329,15 +330,10 @@ pub mod vrf { pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); impl VrfInput { - /// Build a new VRF input. + /// Construct a new VRF input. /// - /// Each message tuple has the form: message_data := (sub-domain, data). + /// Each `message_data` tuple has the form (sub-domain, actual-data). pub fn new(domain: &'static [u8], message_data: &[(&[u8], &[u8])]) -> Self { - // TODO @burdges (temporary hack?) - // In sassafras we want to construct a single `VrfInput` from multiple datas - // E.g. the ticket score uses: epoch-randomness, attempt-index, epoch-index - // Currently, `bandersnatch_vrfs::Message` has a single (domain, data) fields, - // so we need a workaround here... let mut buf = Vec::new(); message_data.into_iter().for_each(|(sub_domain, data)| { buf.extend_from_slice(sub_domain); @@ -352,7 +348,7 @@ pub mod vrf { /// VRF (pre)output derived from [`VrfInput`] using a [`VrfSecret`]. /// - /// This is used to produce a verifiable arbitrary number of "random" bytes. + /// 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); @@ -389,7 +385,15 @@ pub mod vrf { } } - /// A Fiat-Shamir transcript and a sequence of [`VrfInput`]s ready to be signed. + /// 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. pub struct VrfSignData { /// VRF inputs to be signed. pub vrf_inputs: VrfIosVec, @@ -400,12 +404,9 @@ pub mod vrf { impl VrfSignData { /// Construct a new signable data instance. /// - /// The `transcript_data` will be used as messages for the Fiat-Shamir - /// transform part of the scheme. If unsure just give it a unique label - /// depending on the actual usage of the signing data - /// (TODO @burges: or leave it empty? There is already the `label` field for - /// contextualization). The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and - /// which contribute to the actual output bytes produced via the VRF. + /// The `transcript_data` is used to construct the *Fiat-Shamir* `Transcript`. + /// + /// Refer to the [`VrfSignData`] for more details. pub fn new>>( label: &'static [u8], transcript_data: &[&[u8]], @@ -442,11 +443,11 @@ pub mod vrf { self.vrf_inputs.try_push(vrf_input) } - /// Create challenge from input transcript within the signing data. + /// Create challenge from the transcript contained 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"); + let mut transcript = self.transcript.clone(); + let mut reader = transcript.challenge(b"Prehashed for bandersnatch"); reader.read_bytes(&mut output); output } @@ -472,8 +473,7 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfSecret for Pair { fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { - // Hack used because backend signature type is generic over the number of ios - // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // 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), @@ -502,8 +502,7 @@ pub mod vrf { if preouts_len != data.vrf_inputs.len() { return false } - // Hack used because backend signature type is generic over the number of ios - // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // 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), @@ -700,8 +699,7 @@ pub mod ring_vrf { /// The signature is valid if the signing [`Pair`] is part of the ring from which /// the [`RingProver`] has been derived. pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { - // Hack used because backend signature type is generic over the number of ios - // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // 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), @@ -754,8 +752,7 @@ pub mod ring_vrf { if preouts_len != data.vrf_inputs.len() { return false } - // Hack used because backend signature type is generic over the number of ios. - // TODO @burdges can we provide a Vec version in `bandersnatch_vrfs` crate? + // 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), From efd39bdad0be9cbcaeec2d77fba793a29df293e5 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 28 Jul 2023 20:01:15 +0200 Subject: [PATCH 26/36] Simplification of structs construction --- primitives/core/src/bandersnatch.rs | 137 +++++++++++++++------------- 1 file changed, 75 insertions(+), 62 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 5771f363730b0..3a3972f63765a 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -281,7 +281,7 @@ impl TraitPair for Pair { /// Sign raw data. fn sign(&self, data: &[u8]) -> Signature { - let data = vrf::VrfSignData::new(SIGNING_CTX, &[data], vrf::VrfIosVec::default()); + let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data], None); self.vrf_sign(&data).signature } @@ -289,7 +289,7 @@ impl TraitPair for Pair { /// /// 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 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) @@ -331,17 +331,8 @@ pub mod vrf { impl VrfInput { /// Construct a new VRF input. - /// - /// Each `message_data` tuple has the form (sub-domain, actual-data). - pub fn new(domain: &'static [u8], message_data: &[(&[u8], &[u8])]) -> Self { - let mut buf = Vec::new(); - message_data.into_iter().for_each(|(sub_domain, data)| { - buf.extend_from_slice(sub_domain); - buf.extend_from_slice(&sub_domain.len().to_le_bytes()); - buf.extend_from_slice(data); - buf.extend_from_slice(&data.len().to_le_bytes()); - }); - let msg = Message { domain, message: buf.as_slice() }; + 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()) } } @@ -394,6 +385,7 @@ pub mod vrf { /// /// 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, @@ -402,41 +394,52 @@ pub mod vrf { } impl VrfSignData { - /// Construct a new signable data instance. + /// 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. - pub fn new>>( + /// Refer to the [`VrfSignData`] for more details about the usage of + /// `transcript_data` and `vrf_inputs` + 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() } + 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 from an iterator of [`VrfInput`]s. + /// Construct a new data to be signed. /// - /// Fails if the `vrf_inputs` yields more elements than [`MAX_VRF_IOS`] - // TODO @davxy: maybe we can just provide one constructor... - pub fn from_iter>( + /// 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: &[&[u8]], - vrf_inputs: T, - ) -> Result { + transcript_data: impl IntoIterator>, + vrf_inputs: impl IntoIterator, + ) -> Self { 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)) + 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 } } - /// Appends a message to the transcript + /// Append a raw 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. + /// 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> { @@ -812,13 +815,25 @@ mod tests { } #[test] - fn backend_assumptions_check() { + 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] @@ -847,11 +862,11 @@ mod tests { 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 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::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1, i2, i3]); let signature = pair.vrf_sign(&data); @@ -863,18 +878,16 @@ mod tests { 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 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::from_iter(b"mydata", &[b"data"], [i1, i2.clone()]).unwrap(); + let data = VrfSignData::new_unchecked(b"mydata", &[b"bbb"], [i1, i2.clone()]); assert!(!public.vrf_verify(&data, &signature)); - let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], [i2]).unwrap(); + let data = VrfSignData::new_unchecked(b"mydata", &[b"aaa"], [i2]); assert!(!public.vrf_verify(&data, &signature)); } @@ -882,10 +895,10 @@ mod tests { 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 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); @@ -903,10 +916,10 @@ mod tests { // 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 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(); @@ -918,7 +931,7 @@ mod tests { let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); assert_eq!(expected, decoded); - let data = VrfSignData::from_iter(b"mydata", &[b"tdata"], []).unwrap(); + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], []); let expected = pair.vrf_sign(&data); let bytes = expected.encode(); @@ -940,11 +953,11 @@ mod tests { let prover_idx = 3; pks[prover_idx] = 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 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::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + 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); @@ -966,11 +979,11 @@ mod tests { let prover_idx = 3; pks[prover_idx] = 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 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::from_iter(b"mydata", &[b"tdata"], [i1, i2, i3]).unwrap(); + 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); From c56218131170318d3bf4f08c35cae3c8112989ef Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 2 Aug 2023 08:35:50 +0200 Subject: [PATCH 27/36] Trigger CI From b1f6c58aa057ca4cb49cfe8113e0257463850cca Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 08:48:51 +0200 Subject: [PATCH 28/36] Apply review suggestion Co-authored-by: Koute --- primitives/keyring/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 3122643f08ba6..8e9f793636066 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -21,6 +21,6 @@ sp-runtime = { version = "24.0.0", path = "../runtime" } [features] # This feature adds Bandersnatch crypto primitives. -# It shoud not be used in production since the implementation and interface may still -# be subject to significant change. +# 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"] From 934285638e134c6996aa7e98f96b9a46804d6f48 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 08:53:13 +0200 Subject: [PATCH 29/36] Docs typo --- client/keystore/Cargo.toml | 4 ++-- primitives/application-crypto/Cargo.toml | 4 ++-- primitives/core/Cargo.toml | 4 ++-- primitives/io/Cargo.toml | 2 +- primitives/keystore/Cargo.toml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 5ebb993849c28..b5af0bc90343a 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -28,7 +28,7 @@ tempfile = "3.1.0" [features] # 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. +# be subject to significant changes. bls-experimental = [ "sp-core/bls-experimental", "sp-keystore/bls-experimental", @@ -36,7 +36,7 @@ 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 change. +# be subject to significant changes. bandersnatch-experimental = [ "sp-core/bandersnatch-experimental", "sp-keystore/bandersnatch-experimental", diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index 5690da7ae3eed..9e271076f8a02 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -54,12 +54,12 @@ full_crypto = [ # 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. +# 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 change. +# be subject to significant changes. bandersnatch-experimental = [ "sp-core/bandersnatch-experimental", "sp-io/bandersnatch-experimental", diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index b0e231978349f..757bbcf38fa1d 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -152,10 +152,10 @@ full_crypto = [ # 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. +# 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 change. +# be subject to significant changes. bandersnatch-experimental = ["bandersnatch_vrfs"] diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index c25064f3e3b35..517d2e568b6bd 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -95,7 +95,7 @@ improved_panic_error_reporting = [] # This feature adds Bandersnatch crypto primitives. # It should not be used in production since the implementation and interface may still -# be subject to significant change. +# be subject to significant changes. bandersnatch-experimental = [ "sp-core/bandersnatch-experimental", "sp-keystore/bandersnatch-experimental", diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index cec0f8df285be..7e551b7cbf268 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -33,10 +33,10 @@ std = [ # 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. +# 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 change. +# be subject to significant changes. bandersnatch-experimental = ["sp-core/bandersnatch-experimental"] From ece4016214f333b7b1645e6a958374f1d7208fa3 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 09:27:53 +0200 Subject: [PATCH 30/36] Fix keystore tests --- primitives/core/src/sr25519.rs | 2 +- primitives/keystore/src/testing.rs | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index bcd64ed1cc3cf..c75c34cb3f146 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -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/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index c15e2770e10ad..db4c0bebcb76c 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -478,21 +478,12 @@ mod tests { let key_pair = bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); - let in1 = bandersnatch::vrf::VrfInput::new( - b"input1", - &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], - ); - let in2 = bandersnatch::vrf::VrfInput::new( - b"input2", - &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], - ); - let in3 = bandersnatch::vrf::VrfInput::new( - b"input3", - &[(b"dom1", b"foo"), (b"dom2", b"bar"), (b"dom3", b"baz")], - ); + let in1 = bandersnatch::vrf::VrfInput::new(b"in1", b"foo"); + let in2 = bandersnatch::vrf::VrfInput::new(b"in2", b"bar"); + let in3 = bandersnatch::vrf::VrfInput::new(b"in3", b"baz"); + let sign_data = - bandersnatch::vrf::VrfSignData::from_iter(b"Test", &[b"msg1"], [in1, in2, in3]) - .unwrap(); + bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", &[b"msg1"], [in1, in2, in3]); let result = store.bandersnatch_vrf_sign(BANDERSNATCH, &key_pair.public(), &sign_data); assert!(result.unwrap().is_none()); From 3b553b67e55f1cadbb1e52b91a68af874cc25ac1 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 10:50:58 +0200 Subject: [PATCH 31/36] Consistence --- primitives/core/src/bandersnatch.rs | 6 +++--- primitives/core/src/ecdsa.rs | 2 +- primitives/core/src/ed25519.rs | 2 +- primitives/core/src/sr25519.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index 3a3972f63765a..f353352f43f92 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -57,7 +57,8 @@ const RING_PROOF_SERIALIZED_LEN: usize = 592; // Max size of serialized ring-vrf context params. // -// This size is dependent on the ring size. +// 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 @@ -288,7 +289,7 @@ impl TraitPair for Pair { /// Verify a signature on a message. /// /// Returns `true` if the signature is good. - fn verify>(signature: &Self::Signature, data: M, public: &Self::Public) -> bool { + 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() }; @@ -1005,7 +1006,6 @@ mod tests { let ctx1 = RingContext::new_testing(); let enc1 = ctx1.encode(); - println!("SIZE: {}", enc1.len()); assert_eq!(enc1.len(), RingContext::max_encoded_len()); let ctx2 = RingContext::decode(&mut enc1.as_slice()).unwrap(); 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/sr25519.rs b/primitives/core/src/sr25519.rs index c75c34cb3f146..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() From c2b00b56f63569a319d8d971b3858e45d9268bb2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 14:33:55 +0200 Subject: [PATCH 32/36] Add ref to git rependency --- Cargo.lock | 8 ++++---- primitives/core/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ccdc3c059b85..7809c16b7dbe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ec", "ark-ff", @@ -496,7 +496,7 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ff", "ark-serialize", @@ -683,7 +683,7 @@ dependencies = [ [[package]] name = "bandersnatch_vrfs" version = "0.0.1" -source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1932,7 +1932,7 @@ checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" dependencies = [ "ark-ec", "ark-ff", diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index bcd3e652c3c56..ee4bf8924186c 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -58,7 +58,7 @@ sp-runtime-interface = { version = "17.0.0", default-features = false, path = ". # bls crypto w3f-bls = { version = "0.1.3", default-features = false, optional = true} # bandersnatch crypto -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", default-features = false, optional = true } +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "c86ebd4", default-features = false, optional = true } [dev-dependencies] criterion = "0.4.0" From b339b3a995676bc20710c9123220389ee0917959 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 14:43:55 +0200 Subject: [PATCH 33/36] Static check of MAX_VRF_IOS value --- primitives/core/src/bandersnatch.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index f353352f43f92..b259cb9d7e7a6 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -477,6 +477,7 @@ pub mod vrf { #[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), @@ -502,6 +503,7 @@ pub mod vrf { 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 @@ -703,6 +705,7 @@ pub mod ring_vrf { /// The signature is valid if the signing [`Pair`] is part of the ring from which /// the [`RingProver`] has been derived. 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), @@ -752,6 +755,7 @@ pub mod ring_vrf { /// The signature is valid if has been produced by a member of the ring from which /// the [`RingVerifier`] has been derived. 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 From 2b2676b14263d75c3c9412197dc0fb51b48dfffa Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 15:51:53 +0200 Subject: [PATCH 34/36] Clarify behavior for out of ring keys signatures --- primitives/core/src/bandersnatch.rs | 32 ++++++++++++++++++++++++----- primitives/keystore/src/lib.rs | 5 +++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/primitives/core/src/bandersnatch.rs b/primitives/core/src/bandersnatch.rs index b259cb9d7e7a6..c3ba7f41058e9 100644 --- a/primitives/core/src/bandersnatch.rs +++ b/primitives/core/src/bandersnatch.rs @@ -17,6 +17,8 @@ //! 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; @@ -702,8 +704,9 @@ pub mod ring_vrf { impl Pair { /// Produce a ring-vrf signature. /// - /// The signature is valid if the signing [`Pair`] is part of the ring from which - /// the [`RingProver`] has been derived. + /// 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. @@ -752,8 +755,8 @@ pub mod ring_vrf { impl RingVrfSignature { /// Verify a ring-vrf signature. /// - /// The signature is valid if has been produced by a member of the ring from which - /// the [`RingVerifier`] has been derived. + /// 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(); @@ -808,7 +811,7 @@ pub mod ring_vrf { mod tests { use super::{ring_vrf::*, vrf::*, *}; use crate::crypto::{VrfPublic, VrfSecret, DEV_PHRASE}; - const DEV_SEED: &[u8; SEED_SERIALIZED_LEN] = &[0; SEED_SERIALIZED_LEN]; + const DEV_SEED: &[u8; SEED_SERIALIZED_LEN] = &[0xcb; SEED_SERIALIZED_LEN]; #[allow(unused)] fn b2h(bytes: &[u8]) -> String { @@ -971,6 +974,25 @@ mod tests { 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(); diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 9e7dddf9756c9..cb7001139968f 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -245,8 +245,9 @@ pub trait Keystore: Send + Sync { /// Also takes a [`bandersnatch::ring_vrf::RingProver`] instance obtained from /// a valid [`bandersnatch::ring_vrf::RingContext`]. /// - /// The signature is valid if the signing key is part of the ring from which - /// the `RingProver` has been derived. + /// 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. /// /// Returns `None` if the given `key_type` and `public` combination doesn't /// exist in the keystore or an `Err` when something failed. From 0739e0f944c3c8b39828c0a6fe8eb1d69c9788b1 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 16:13:18 +0200 Subject: [PATCH 35/36] Add test for ring-vrf to the keystore --- primitives/keystore/src/testing.rs | 45 ++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index db4c0bebcb76c..efa35fd24bf46 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -478,12 +478,9 @@ mod tests { let key_pair = bandersnatch::Pair::from_string(secret_uri, None).expect("Generates key pair"); - let in1 = bandersnatch::vrf::VrfInput::new(b"in1", b"foo"); - let in2 = bandersnatch::vrf::VrfInput::new(b"in2", b"bar"); - let in3 = bandersnatch::vrf::VrfInput::new(b"in3", b"baz"); - + let in1 = bandersnatch::vrf::VrfInput::new("in", "foo"); let sign_data = - bandersnatch::vrf::VrfSignData::new_unchecked(b"Test", &[b"msg1"], [in1, in2, in3]); + 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()); @@ -496,4 +493,42 @@ mod tests { 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()); + } } From a457359b627229acbb2e7c4ef27b91c7eb9c55a5 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Aug 2023 16:23:19 +0200 Subject: [PATCH 36/36] Fix docs --- primitives/keystore/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index cb7001139968f..82062fe7b40a7 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -246,8 +246,9 @@ pub trait Keystore: Send + Sync { /// a valid [`bandersnatch::ring_vrf::RingContext`]. /// /// 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. + /// 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.