From 1b34571c0c423115813034783ddf524aac257bf5 Mon Sep 17 00:00:00 2001 From: drskalman <35698397+drskalman@users.noreply.github.com> Date: Sun, 15 Oct 2023 05:42:40 -0400 Subject: [PATCH] Paired-key Crypto Scheme (#1705) BEEFY needs two cryptographic keys at the same time. Validators should sign BEEFY payload using both ECDSA and BLS key. The network will gossip a payload which contains a valid ECDSA key. The prover nodes aggregate the BLS keys if aggregation fails to verifies the validator which provided a valid ECDSA signature but an invalid BLS signature is subject to slashing. As such BEEFY session should be initiated with both key. Currently there is no straight forward way of doing so, beside having a session with RuntimeApp corresponding to a crypto scheme contains both keys. This pull request implement a generic paired_crypto scheme as well as implementing it for (ECDSA, BLS) pair. --------- Co-authored-by: Davide Galassi Co-authored-by: Robert Hambrock --- substrate/primitives/core/src/bls.rs | 44 +- substrate/primitives/core/src/crypto.rs | 2 +- substrate/primitives/core/src/ecdsa.rs | 54 +- substrate/primitives/core/src/hexdisplay.rs | 2 +- substrate/primitives/core/src/lib.rs | 1 + .../primitives/core/src/paired_crypto.rs | 662 ++++++++++++++++++ 6 files changed, 723 insertions(+), 42 deletions(-) create mode 100644 substrate/primitives/core/src/paired_crypto.rs diff --git a/substrate/primitives/core/src/bls.rs b/substrate/primitives/core/src/bls.rs index 951aa1828ea5..8ce6eb166f86 100644 --- a/substrate/primitives/core/src/bls.rs +++ b/substrate/primitives/core/src/bls.rs @@ -17,7 +17,7 @@ //! Simple BLS (Boneh–Lynn–Shacham) Signature API. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use crate::crypto::Ss58Codec; use crate::crypto::{ByteArray, CryptoType, Derive, Public as TraitPublic, UncheckedFrom}; #[cfg(feature = "full_crypto")] @@ -28,8 +28,12 @@ use sp_std::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] + +#[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::{format, string::String}; + use w3f_bls::{DoublePublicKey, DoubleSignature, EngineBLS, SerializableToBytes, TinyBLS381}; #[cfg(feature = "full_crypto")] use w3f_bls::{DoublePublicKeyScheme, Keypair, Message, SecretKey}; @@ -39,6 +43,7 @@ use sp_std::{convert::TryFrom, marker::PhantomData, ops::Deref}; /// BLS-377 specialized types pub mod bls377 { + pub use super::{PUBLIC_KEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}; use crate::crypto::CryptoTypeId; use w3f_bls::TinyBLS377; @@ -60,6 +65,7 @@ pub mod bls377 { /// BLS-381 specialized types pub mod bls381 { + pub use super::{PUBLIC_KEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE}; use crate::crypto::CryptoTypeId; use w3f_bls::TinyBLS381; @@ -83,17 +89,17 @@ trait BlsBound: EngineBLS + HardJunctionId + Send + Sync + 'static {} impl BlsBound for T {} -// Secret key serialized size +/// Secret key serialized size #[cfg(feature = "full_crypto")] const SECRET_KEY_SERIALIZED_SIZE: usize = as SerializableToBytes>::SERIALIZED_BYTES_SIZE; -// Public key serialized size -const PUBLIC_KEY_SERIALIZED_SIZE: usize = +/// Public key serialized size +pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = as SerializableToBytes>::SERIALIZED_BYTES_SIZE; -// Signature serialized size -const SIGNATURE_SERIALIZED_SIZE: usize = +/// Signature serialized size +pub const SIGNATURE_SERIALIZED_SIZE: usize = as SerializableToBytes>::SERIALIZED_BYTES_SIZE; /// A secret seed. @@ -258,7 +264,7 @@ impl sp_std::fmt::Debug for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Public { fn serialize(&self, serializer: S) -> Result where @@ -268,7 +274,7 @@ impl Serialize for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de, T: BlsBound> Deserialize<'de> for Public { fn deserialize(deserializer: D) -> Result where @@ -317,6 +323,10 @@ impl sp_std::hash::Hash for Signature { } } +impl ByteArray for Signature { + const LEN: usize = SIGNATURE_SERIALIZED_SIZE; +} + impl TryFrom<&[u8]> for Signature { type Error = (); @@ -330,7 +340,7 @@ impl TryFrom<&[u8]> for Signature { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where @@ -340,7 +350,7 @@ impl Serialize for Signature { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de, T> Deserialize<'de> for Signature { fn deserialize(deserializer: D) -> Result where @@ -444,10 +454,9 @@ impl TraitPair for Pair { path: Iter, _seed: Option, ) -> Result<(Self, Option), DeriveError> { - let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = - self.0.secret.to_bytes().try_into().expect( - "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size", - ); + let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = self.0.secret.to_bytes().try_into().expect( + "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", + ); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -529,11 +538,10 @@ mod test { ); } - // Only passes if the seed = (seed mod ScalarField) #[test] fn seed_and_derive_should_work() { let seed = array_bytes::hex2array_unchecked( - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f00", + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed); // we are using hash to field so this is not going to work @@ -543,7 +551,7 @@ mod test { assert_eq!( derived.to_raw_vec(), array_bytes::hex2array_unchecked::<_, 32>( - "a4f2269333b3e87c577aa00c4a2cd650b3b30b2e8c286a47c251279ff3a26e0d" + "3a0626d095148813cd1642d38254f1cfff7eb8cc1a2fc83b2a135377c3554c12" ) ); } diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index e1bfb80046f7..ccb61c12f321 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -1213,7 +1213,7 @@ macro_rules! impl_from_entropy_base { [$type; 17], [$type; 18], [$type; 19], [$type; 20], [$type; 21], [$type; 22], [$type; 23], [$type; 24], [$type; 25], [$type; 26], [$type; 27], [$type; 28], [$type; 29], [$type; 30], [$type; 31], [$type; 32], [$type; 36], [$type; 40], [$type; 44], [$type; 48], [$type; 56], [$type; 64], [$type; 72], [$type; 80], - [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 192], [$type; 224], [$type; 256] + [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 177], [$type; 192], [$type; 224], [$type; 256] ); } } diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 05bc679386c3..603fa515a30e 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -50,6 +50,12 @@ use sp_std::vec::Vec; /// An identifier used to match public keys against ecdsa keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); +/// The byte length of public key +pub const PUBLIC_KEY_SERIALIZED_SIZE: usize = 33; + +/// The byte length of signature +pub const SIGNATURE_SERIALIZED_SIZE: usize = 65; + /// A secret seed (which is bytewise essentially equivalent to a SecretKey). /// /// We need it as a different type because `Seed` is expected to be AsRef<[u8]>. @@ -71,11 +77,11 @@ type Seed = [u8; 32]; PartialOrd, Ord, )] -pub struct Public(pub [u8; 33]); +pub struct Public(pub [u8; PUBLIC_KEY_SERIALIZED_SIZE]); impl crate::crypto::FromEntropy for Public { fn from_entropy(input: &mut impl codec::Input) -> Result { - let mut result = Self([0u8; 33]); + let mut result = Self([0u8; PUBLIC_KEY_SERIALIZED_SIZE]); input.read(&mut result.0[..])?; Ok(result) } @@ -86,7 +92,7 @@ impl Public { /// /// NOTE: No checking goes on to ensure this is a real public key. Only use it if /// you are certain that the array actually is a pubkey. GIGO! - pub fn from_raw(data: [u8; 33]) -> Self { + pub fn from_raw(data: [u8; PUBLIC_KEY_SERIALIZED_SIZE]) -> Self { Self(data) } @@ -109,7 +115,7 @@ impl Public { } impl ByteArray for Public { - const LEN: usize = 33; + const LEN: usize = PUBLIC_KEY_SERIALIZED_SIZE; } impl TraitPublic for Public {} @@ -148,8 +154,8 @@ impl From for Public { } } -impl UncheckedFrom<[u8; 33]> for Public { - fn unchecked_from(x: [u8; 33]) -> Self { +impl UncheckedFrom<[u8; PUBLIC_KEY_SERIALIZED_SIZE]> for Public { + fn unchecked_from(x: [u8; PUBLIC_KEY_SERIALIZED_SIZE]) -> Self { Public(x) } } @@ -198,14 +204,18 @@ impl<'de> Deserialize<'de> for Public { /// A signature (a 512-bit value, plus 8 bits for recovery ID). #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] -pub struct Signature(pub [u8; 65]); +pub struct Signature(pub [u8; SIGNATURE_SERIALIZED_SIZE]); + +impl ByteArray for Signature { + const LEN: usize = SIGNATURE_SERIALIZED_SIZE; +} impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { - if data.len() == 65 { - let mut inner = [0u8; 65]; + if data.len() == SIGNATURE_SERIALIZED_SIZE { + let mut inner = [0u8; SIGNATURE_SERIALIZED_SIZE]; inner.copy_from_slice(data); Ok(Signature(inner)) } else { @@ -239,7 +249,7 @@ impl<'de> Deserialize<'de> for Signature { impl Clone for Signature { fn clone(&self) -> Self { - let mut r = [0u8; 65]; + let mut r = [0u8; SIGNATURE_SERIALIZED_SIZE]; r.copy_from_slice(&self.0[..]); Signature(r) } @@ -247,18 +257,18 @@ impl Clone for Signature { impl Default for Signature { fn default() -> Self { - Signature([0u8; 65]) + Signature([0u8; SIGNATURE_SERIALIZED_SIZE]) } } -impl From for [u8; 65] { - fn from(v: Signature) -> [u8; 65] { +impl From for [u8; SIGNATURE_SERIALIZED_SIZE] { + fn from(v: Signature) -> [u8; SIGNATURE_SERIALIZED_SIZE] { v.0 } } -impl AsRef<[u8; 65]> for Signature { - fn as_ref(&self) -> &[u8; 65] { +impl AsRef<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature { + fn as_ref(&self) -> &[u8; SIGNATURE_SERIALIZED_SIZE] { &self.0 } } @@ -287,8 +297,8 @@ impl sp_std::fmt::Debug for Signature { } } -impl UncheckedFrom<[u8; 65]> for Signature { - fn unchecked_from(data: [u8; 65]) -> Signature { +impl UncheckedFrom<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature { + fn unchecked_from(data: [u8; SIGNATURE_SERIALIZED_SIZE]) -> Signature { Signature(data) } } @@ -298,7 +308,7 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_raw(data: [u8; 65]) -> Signature { + pub fn from_raw(data: [u8; SIGNATURE_SERIALIZED_SIZE]) -> Signature { Signature(data) } @@ -307,10 +317,10 @@ impl Signature { /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! pub fn from_slice(data: &[u8]) -> Option { - if data.len() != 65 { + if data.len() != SIGNATURE_SERIALIZED_SIZE { return None } - let mut r = [0u8; 65]; + let mut r = [0u8; SIGNATURE_SERIALIZED_SIZE]; r.copy_from_slice(data); Some(Signature(r)) } @@ -473,7 +483,7 @@ impl Pair { pub fn verify_deprecated>(sig: &Signature, message: M, pubkey: &Public) -> bool { let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let parse_signature_overflowing = |x: [u8; 65]| { + let parse_signature_overflowing = |x: [u8; SIGNATURE_SERIALIZED_SIZE]| { let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?; let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?; Some((sig, rid)) @@ -726,7 +736,7 @@ mod test { let signature = pair.sign(&message[..]); let serialized_signature = serde_json::to_string(&signature).unwrap(); // Signature is 65 bytes, so 130 chars + 2 quote chars - assert_eq!(serialized_signature.len(), 132); + assert_eq!(serialized_signature.len(), SIGNATURE_SERIALIZED_SIZE * 2 + 2); let signature = serde_json::from_str(&serialized_signature).unwrap(); assert!(Pair::verify(&signature, &message[..], &pair.public())); } diff --git a/substrate/primitives/core/src/hexdisplay.rs b/substrate/primitives/core/src/hexdisplay.rs index 30e045dfc52a..72bb24a186e5 100644 --- a/substrate/primitives/core/src/hexdisplay.rs +++ b/substrate/primitives/core/src/hexdisplay.rs @@ -96,7 +96,7 @@ macro_rules! impl_non_endians { impl_non_endians!( [u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], [u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], [u8; 48], [u8; 56], - [u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128], [u8; 144] + [u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128], [u8; 144], [u8; 177] ); /// Format into ASCII + # + hex, suitable for storage key preimages. diff --git a/substrate/primitives/core/src/lib.rs b/substrate/primitives/core/src/lib.rs index 3a0e1f33f16c..75b84c89ae68 100644 --- a/substrate/primitives/core/src/lib.rs +++ b/substrate/primitives/core/src/lib.rs @@ -66,6 +66,7 @@ pub mod hash; #[cfg(feature = "std")] mod hasher; pub mod offchain; +pub mod paired_crypto; pub mod sr25519; pub mod testing; #[cfg(feature = "std")] diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs new file mode 100644 index 000000000000..355fc690779f --- /dev/null +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -0,0 +1,662 @@ +// 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. + +//! API for using a pair of crypto schemes together. + +#[cfg(feature = "serde")] +use crate::crypto::Ss58Codec; +use crate::crypto::{ByteArray, CryptoType, Derive, Public as PublicT, UncheckedFrom}; +#[cfg(feature = "full_crypto")] +use crate::crypto::{DeriveError, DeriveJunction, Pair as PairT, SecretStringError}; + +#[cfg(feature = "full_crypto")] +use sp_std::vec::Vec; + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +#[cfg(feature = "serde")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::{format, string::String}; + +use sp_runtime_interface::pass_by::PassByInner; +use sp_std::convert::TryFrom; + +/// ECDSA and BLS12-377 paired crypto scheme +#[cfg(feature = "bls-experimental")] +pub mod ecdsa_n_bls377 { + use crate::{bls377, crypto::CryptoTypeId, ecdsa}; + + /// An identifier used to match public keys against BLS12-377 keys + pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecb7"); + + const PUBLIC_KEY_LEN: usize = + ecdsa::PUBLIC_KEY_SERIALIZED_SIZE + bls377::PUBLIC_KEY_SERIALIZED_SIZE; + const SIGNATURE_LEN: usize = + ecdsa::SIGNATURE_SERIALIZED_SIZE + bls377::SIGNATURE_SERIALIZED_SIZE; + + /// (ECDSA, BLS12-377) key-pair pair. + #[cfg(feature = "full_crypto")] + pub type Pair = super::Pair; + /// (ECDSA, BLS12-377) public key pair. + pub type Public = super::Public; + /// (ECDSA, BLS12-377) signature pair. + pub type Signature = super::Signature; + + impl super::CryptoType for Public { + #[cfg(feature = "full_crypto")] + type Pair = Pair; + } + + impl super::CryptoType for Signature { + #[cfg(feature = "full_crypto")] + type Pair = Pair; + } + + #[cfg(feature = "full_crypto")] + impl super::CryptoType for Pair { + type Pair = Pair; + } +} + +/// Secure seed length. +/// +/// Currently only supporting sub-schemes whose seed is a 32-bytes array. +#[cfg(feature = "full_crypto")] +const SECURE_SEED_LEN: usize = 32; + +/// A secret seed. +/// +/// It's not called a "secret key" because ring doesn't expose the secret keys +/// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we +/// will need it later (such as for HDKD). +#[cfg(feature = "full_crypto")] +type Seed = [u8; SECURE_SEED_LEN]; + +/// A public key. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq, PartialOrd, Ord)] +pub struct Public([u8; LEFT_PLUS_RIGHT_LEN]); + +#[cfg(feature = "full_crypto")] +impl sp_std::hash::Hash for Public { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl ByteArray for Public { + const LEN: usize = LEFT_PLUS_RIGHT_LEN; +} + +impl TryFrom<&[u8]> for Public { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != LEFT_PLUS_RIGHT_LEN { + return Err(()) + } + let mut inner = [0u8; LEFT_PLUS_RIGHT_LEN]; + inner.copy_from_slice(data); + Ok(Public(inner)) + } +} + +impl AsRef<[u8; LEFT_PLUS_RIGHT_LEN]> + for Public +{ + fn as_ref(&self) -> &[u8; LEFT_PLUS_RIGHT_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 PassByInner for Public { + type Inner = [u8; LEFT_PLUS_RIGHT_LEN]; + + fn into_inner(self) -> Self::Inner { + self.0 + } + + fn inner(&self) -> &Self::Inner { + &self.0 + } + + fn from_inner(inner: Self::Inner) -> Self { + Self(inner) + } +} + +#[cfg(feature = "full_crypto")] +impl< + LeftPair: PairT, + RightPair: PairT, + const LEFT_PLUS_RIGHT_PUBLIC_LEN: usize, + const SIGNATURE_LEN: usize, + > From> + for Public +where + Pair: + PairT>, +{ + fn from(x: Pair) -> Self { + x.public() + } +} + +impl UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]> + for Public +{ + fn unchecked_from(data: [u8; LEFT_PLUS_RIGHT_LEN]) -> Self { + Public(data) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for Public +where + Public: CryptoType, +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } +} + +impl sp_std::fmt::Debug for Public +where + Public: CryptoType, + [u8; LEFT_PLUS_RIGHT_LEN]: crate::hexdisplay::AsBytesRef, +{ + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + let s = self.to_ss58check(); + write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.0), &s[0..8]) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Public +where + Public: CryptoType, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_ss58check()) + } +} + +#[cfg(feature = "serde")] +impl<'de, const LEFT_PLUS_RIGHT_LEN: usize> Deserialize<'de> for Public +where + Public: CryptoType, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Public::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } +} + +impl PublicT for Public where + Public: CryptoType +{ +} + +impl Derive for Public {} + +/// Trait characterizing a signature which could be used as individual component of an +/// `paired_crypto:Signature` pair. +pub trait SignatureBound: ByteArray {} + +impl SignatureBound for T {} + +/// A pair of signatures of different types +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq)] +pub struct Signature([u8; LEFT_PLUS_RIGHT_LEN]); + +#[cfg(feature = "full_crypto")] +impl sp_std::hash::Hash for Signature { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl ByteArray for Signature { + const LEN: usize = LEFT_PLUS_RIGHT_LEN; +} + +impl TryFrom<&[u8]> for Signature { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != LEFT_PLUS_RIGHT_LEN { + return Err(()) + } + let mut inner = [0u8; LEFT_PLUS_RIGHT_LEN]; + inner.copy_from_slice(data); + Ok(Signature(inner)) + } +} + +impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl AsRef<[u8; LEFT_PLUS_RIGHT_LEN]> + for Signature +{ + fn as_ref(&self) -> &[u8; LEFT_PLUS_RIGHT_LEN] { + &self.0 + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +#[cfg(feature = "serde")] +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&array_bytes::bytes2hex("", self)) + } +} + +#[cfg(feature = "serde")] +impl<'de, const LEFT_PLUS_RIGHT_LEN: usize> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = array_bytes::hex2bytes(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e)))?; + Signature::::try_from(bytes.as_ref()).map_err(|e| { + de::Error::custom(format!("Error converting deserialized data into signature: {:?}", e)) + }) + } +} + +impl From> + for [u8; LEFT_PLUS_RIGHT_LEN] +{ + fn from(signature: Signature) -> [u8; LEFT_PLUS_RIGHT_LEN] { + signature.0 + } +} + +impl sp_std::fmt::Debug for Signature +where + [u8; LEFT_PLUS_RIGHT_LEN]: crate::hexdisplay::AsBytesRef, +{ + #[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(()) + } +} + +impl UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]> + for Signature +{ + fn unchecked_from(data: [u8; LEFT_PLUS_RIGHT_LEN]) -> Self { + Signature(data) + } +} + +/// A key pair. +#[cfg(feature = "full_crypto")] +#[derive(Clone)] +pub struct Pair< + LeftPair: PairT, + RightPair: PairT, + const PUBLIC_KEY_LEN: usize, + const SIGNATURE_LEN: usize, +> { + left: LeftPair, + right: RightPair, +} + +#[cfg(feature = "full_crypto")] +impl< + LeftPair: PairT, + RightPair: PairT, + const PUBLIC_KEY_LEN: usize, + const SIGNATURE_LEN: usize, + > PairT for Pair +where + Pair: CryptoType, + LeftPair::Signature: SignatureBound, + RightPair::Signature: SignatureBound, + Public: CryptoType, + LeftPair::Seed: From + Into, + RightPair::Seed: From + Into, +{ + type Seed = Seed; + type Public = Public; + type Signature = Signature; + + fn from_seed_slice(seed_slice: &[u8]) -> Result { + if seed_slice.len() != SECURE_SEED_LEN { + return Err(SecretStringError::InvalidSeedLength) + } + let left = LeftPair::from_seed_slice(&seed_slice)?; + let right = RightPair::from_seed_slice(&seed_slice)?; + Ok(Pair { left, right }) + } + + /// Derive a child key from a series of given junctions. + /// + /// Note: if the `LeftPair` and `RightPair` crypto schemes differ in + /// seed derivation, `derive` will drop the seed in the return. + fn derive>( + &self, + path: Iter, + seed: Option, + ) -> Result<(Self, Option), DeriveError> { + let path: Vec<_> = path.collect(); + + let left = self.left.derive(path.iter().cloned(), seed.map(|s| s.into()))?; + let right = self.right.derive(path.into_iter(), seed.map(|s| s.into()))?; + + let seed = match (left.1, right.1) { + (Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()), + _ => None, + }; + + Ok((Self { left: left.0, right: right.0 }, seed)) + } + + fn public(&self) -> Self::Public { + let mut raw = [0u8; PUBLIC_KEY_LEN]; + let left_pub = self.left.public(); + let right_pub = self.right.public(); + raw[..LeftPair::Public::LEN].copy_from_slice(left_pub.as_ref()); + raw[LeftPair::Public::LEN..].copy_from_slice(right_pub.as_ref()); + Self::Public::unchecked_from(raw) + } + + fn sign(&self, message: &[u8]) -> Self::Signature { + let mut raw: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN]; + raw[..LeftPair::Signature::LEN].copy_from_slice(self.left.sign(message).as_ref()); + raw[LeftPair::Signature::LEN..].copy_from_slice(self.right.sign(message).as_ref()); + Self::Signature::unchecked_from(raw) + } + + fn verify>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { + let Ok(left_pub) = public.0[..LeftPair::Public::LEN].try_into() else { return false }; + let Ok(left_sig) = sig.0[0..LeftPair::Signature::LEN].try_into() else { return false }; + if !LeftPair::verify(&left_sig, message.as_ref(), &left_pub) { + return false + } + + let Ok(right_pub) = public.0[LeftPair::Public::LEN..PUBLIC_KEY_LEN].try_into() else { + return false + }; + let Ok(right_sig) = sig.0[LeftPair::Signature::LEN..].try_into() else { return false }; + RightPair::verify(&right_sig, message.as_ref(), &right_pub) + } + + /// Get the seed/secret key for each key and then concatenate them. + fn to_raw_vec(&self) -> Vec { + let mut raw = self.left.to_raw_vec(); + raw.extend(self.right.to_raw_vec()); + raw + } +} + +// Test set exercising the (ECDSA, BLS12-377) implementation +#[cfg(all(test, feature = "bls-experimental"))] +mod test { + use super::*; + use crate::crypto::DEV_PHRASE; + use ecdsa_n_bls377::{Pair, Signature}; + + use crate::{bls377, ecdsa}; + #[test] + + fn test_length_of_paired_ecdsa_and_bls377_public_key_and_signature_is_correct() { + assert_eq!( + ::Public::LEN, + ::Public::LEN + ::Public::LEN + ); + assert_eq!( + ::Signature::LEN, + ::Signature::LEN + ::Signature::LEN + ); + } + + #[test] + fn default_phrase_should_be_used() { + assert_eq!( + Pair::from_string("//Alice///password", None).unwrap().public(), + Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")) + .unwrap() + .public(), + ); + } + + #[test] + fn seed_and_derive_should_work() { + let seed_for_right_and_left: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + ); + let pair = Pair::from_seed(&seed_for_right_and_left); + // we are using hash to field so this is not going to work + // assert_eq!(pair.seed(), seed); + let path = vec![DeriveJunction::Hard([0u8; 32])]; + let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; + assert_eq!( + derived.to_raw_vec(), + [ + array_bytes::hex2array_unchecked::<&str, SECURE_SEED_LEN>( + "b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61" + ), + array_bytes::hex2array_unchecked::<&str, SECURE_SEED_LEN>( + "3a0626d095148813cd1642d38254f1cfff7eb8cc1a2fc83b2a135377c3554c12" + ) + ] + .concat() + ); + } + + #[test] + fn test_vector_should_work() { + let seed_left_and_right: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + ); + let pair = Pair::from_seed(&([seed_left_and_right].concat()[..].try_into().unwrap())); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from( + array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400"), + ), + ); + let message = b""; + let signature = + array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" + ); + let signature = Signature::unchecked_from(signature); + assert!(pair.sign(&message[..]) == signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn test_vector_by_string_should_work() { + let pair = Pair::from_string( + "0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + None, + ) + .unwrap(); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from( + array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd916dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + ), + ), + ); + let message = b""; + let signature = + array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + ); + let signature = Signature::unchecked_from(signature); + assert!(pair.sign(&message[..]) == signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn generated_pair_should_work() { + let (pair, _) = Pair::generate(); + let public = pair.public(); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, b"Something else", &public)); + } + + #[test] + fn seeded_pair_should_work() { + let pair = + Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap())); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from( + array_bytes::hex2array_unchecked("035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9754d2f2bbfa67df54d7e0e951979a18a1e0f45948857752cc2bac6bbb0b1d05e8e48bcc453920bf0c4bbd5993212480112a1fb433f04d74af0a8b700d93dc957ab3207f8d071e948f5aca1a7632c00bdf6d06be05b43e2e6216dccc8a5d55a0071cb2313cfd60b7e9114619cd17c06843b352f0b607a99122f6651df8f02e1ad3697bd208e62af047ddd7b942ba80080") + ), + ); + let message = + array_bytes::hex2bytes_unchecked("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000" + ); + let signature = pair.sign(&message[..]); + println!("Correct signature: {:?}", signature); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, "Other message", &public)); + } + + #[test] + fn generate_with_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(None); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn generate_with_password_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn password_does_something() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_ne!(pair1.public(), pair2.public()); + } + + #[test] + fn ss58check_roundtrip_works() { + let pair = + Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap())); + let public = pair.public(); + let s = public.to_ss58check(); + println!("Correct: {}", s); + let cmp = Public::from_ss58check(&s).unwrap(); + assert_eq!(cmp, public); + } + + #[test] + fn signature_serialization_works() { + let pair = + Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap())); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + + let serialized_signature = serde_json::to_string(&signature).unwrap(); + println!("{:?} -- {:}", signature.0, serialized_signature); + // Signature is 177 bytes, hexify * 2 + 2 quote charsy + assert_eq!(serialized_signature.len(), 356); + let signature = serde_json::from_str(&serialized_signature).unwrap(); + assert!(Pair::verify(&signature, &message[..], &pair.public())); + } + + #[test] + fn signature_serialization_doesnt_panic() { + fn deserialize_signature(text: &str) -> Result { + serde_json::from_str(text) + } + assert!(deserialize_signature("Not valid json.").is_err()); + assert!(deserialize_signature("\"Not an actual signature.\"").is_err()); + // Poorly-sized + assert!(deserialize_signature("\"abc123\"").is_err()); + } + + #[test] + fn encode_and_decode_public_key_works() { + let pair = + Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap())); + let public = pair.public(); + let encoded_public = public.encode(); + let decoded_public = Public::decode(&mut encoded_public.as_slice()).unwrap(); + assert_eq!(public, decoded_public) + } + + #[test] + fn encode_and_decode_signature_works() { + let pair = + Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap())); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + let encoded_signature = signature.encode(); + let decoded_signature = Signature::decode(&mut encoded_signature.as_slice()).unwrap(); + assert_eq!(signature, decoded_signature) + } +}