Implement crypto byte array newtypes in term of a shared type (#3684)

Introduces `CryptoBytes` type defined as:

```rust
pub struct CryptoBytes<const N: usize, Tag = ()>(pub [u8; N], PhantomData<fn() -> Tag>);
```

The type implements a bunch of methods and traits which are typically
expected from a byte array newtype
(NOTE: some of the methods and trait implementations IMO are a bit
redundant, but I decided to maintain them all to not change too much
stuff in this PR)

It also introduces two (generic) typical consumers of `CryptoBytes`:
`PublicBytes` and `SignatureBytes`.

```rust
pub struct PublicTag;
pub PublicBytes<const N: usize, CryptoTag> = CryptoBytes<N, (PublicTag, CryptoTag)>;

pub struct SignatureTag;
pub SignatureBytes<const N: usize, CryptoTag> = CryptoBytes<N, (SignatureTag, CryptoTag)>;
```

Both of them use a tag to differentiate the two types at a higher level.
Downstream specializations will further specialize using a dedicated
crypto tag. For example in ECDSA:


```rust
pub struct EcdsaTag;

pub type Public = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, EcdsaTag>;
pub type Signature = PublicBytes<PUBLIC_KEY_SERIALIZED_SIZE, EcdsaTag>;
```

Overall we have a cleaner and most importantly **consistent** code for
all the types involved

All these details are opaque to the end user which can use `Public` and
`Signature` for the cryptos as before
This commit is contained in:
Davide Galassi
2024-03-19 16:47:42 +01:00
committed by GitHub
parent 5fd72a1f5e
commit 1e9fd23776
29 changed files with 492 additions and 1163 deletions
+91 -176
View File
@@ -17,25 +17,22 @@
//! API for using a pair of crypto schemes together.
use core::marker::PhantomData;
#[cfg(feature = "serde")]
use crate::crypto::Ss58Codec;
use crate::crypto::{
ByteArray, CryptoType, Derive, DeriveError, DeriveJunction, Pair as PairT, Public as PublicT,
SecretStringError, UncheckedFrom,
PublicBytes, SecretStringError, SignatureBytes, UncheckedFrom,
};
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::{self, PassBy, PassByInner};
use sp_std::convert::TryFrom;
/// ECDSA and BLS12-377 paired crypto scheme
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls377 {
@@ -54,12 +51,20 @@ pub mod ecdsa_bls377 {
const SIGNATURE_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls377::SIGNATURE_SERIALIZED_SIZE;
#[doc(hidden)]
pub struct EcdsaBls377Tag(ecdsa::EcdsaTag, bls377::Bls377Tag);
impl super::PairedCryptoSubTagBound for EcdsaBls377Tag {}
/// (ECDSA,BLS12-377) key-pair pair.
pub type Pair = super::Pair<ecdsa::Pair, bls377::Pair, PUBLIC_KEY_LEN, SIGNATURE_LEN>;
pub type Pair =
super::Pair<ecdsa::Pair, bls377::Pair, PUBLIC_KEY_LEN, SIGNATURE_LEN, EcdsaBls377Tag>;
/// (ECDSA,BLS12-377) public key pair.
pub type Public = super::Public<PUBLIC_KEY_LEN>;
pub type Public = super::Public<PUBLIC_KEY_LEN, EcdsaBls377Tag>;
/// (ECDSA,BLS12-377) signature pair.
pub type Signature = super::Signature<SIGNATURE_LEN>;
pub type Signature = super::Signature<SIGNATURE_LEN, EcdsaBls377Tag>;
impl super::CryptoType for Public {
type Pair = Pair;
@@ -110,7 +115,7 @@ pub mod ecdsa_bls377 {
let Ok(left_pub) = public.0[..ecdsa::PUBLIC_KEY_SERIALIZED_SIZE].try_into() else {
return false
};
let Ok(left_sig) = sig.0[0..ecdsa::SIGNATURE_SERIALIZED_SIZE].try_into() else {
let Ok(left_sig) = sig.0[..ecdsa::SIGNATURE_SERIALIZED_SIZE].try_into() else {
return false
};
if !ecdsa::Pair::verify_prehashed(&left_sig, &msg_hash, &left_pub) {
@@ -140,110 +145,49 @@ const SECURE_SEED_LEN: usize = 32;
/// will need it later (such as for HDKD).
type Seed = [u8; SECURE_SEED_LEN];
#[doc(hidden)]
pub trait PairedCryptoSubTagBound {}
#[doc(hidden)]
pub struct PairedCryptoTag;
/// A public key.
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq, PartialOrd, Ord)]
pub struct Public<const LEFT_PLUS_RIGHT_LEN: usize>([u8; LEFT_PLUS_RIGHT_LEN]);
impl<const LEFT_PLUS_RIGHT_LEN: usize> sp_std::hash::Hash for Public<LEFT_PLUS_RIGHT_LEN> {
fn hash<H: sp_std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> ByteArray for Public<LEFT_PLUS_RIGHT_LEN> {
const LEN: usize = LEFT_PLUS_RIGHT_LEN;
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> TryFrom<&[u8]> for Public<LEFT_PLUS_RIGHT_LEN> {
type Error = ();
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
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<const LEFT_PLUS_RIGHT_LEN: usize> AsRef<[u8; LEFT_PLUS_RIGHT_LEN]>
for Public<LEFT_PLUS_RIGHT_LEN>
{
fn as_ref(&self) -> &[u8; LEFT_PLUS_RIGHT_LEN] {
&self.0
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> AsRef<[u8]> for Public<LEFT_PLUS_RIGHT_LEN> {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> AsMut<[u8]> for Public<LEFT_PLUS_RIGHT_LEN> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> PassByInner for Public<LEFT_PLUS_RIGHT_LEN> {
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)
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> PassBy for Public<LEFT_PLUS_RIGHT_LEN> {
type PassBy = pass_by::Inner<Self, [u8; LEFT_PLUS_RIGHT_LEN]>;
}
pub type Public<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> =
PublicBytes<LEFT_PLUS_RIGHT_LEN, (PairedCryptoTag, SubTag)>;
impl<
LeftPair: PairT,
RightPair: PairT,
const LEFT_PLUS_RIGHT_PUBLIC_LEN: usize,
const SIGNATURE_LEN: usize,
> From<Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN>>
for Public<LEFT_PLUS_RIGHT_PUBLIC_LEN>
SubTag: PairedCryptoSubTagBound,
> From<Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, SubTag>>
for Public<LEFT_PLUS_RIGHT_PUBLIC_LEN, SubTag>
where
Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN>:
PairT<Public = Public<LEFT_PLUS_RIGHT_PUBLIC_LEN>>,
Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, SubTag>:
PairT<Public = Public<LEFT_PLUS_RIGHT_PUBLIC_LEN, SubTag>>,
{
fn from(x: Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN>) -> Self {
fn from(
x: Pair<LeftPair, RightPair, LEFT_PLUS_RIGHT_PUBLIC_LEN, SIGNATURE_LEN, SubTag>,
) -> Self {
x.public()
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]>
for Public<LEFT_PLUS_RIGHT_LEN>
{
fn unchecked_from(data: [u8; LEFT_PLUS_RIGHT_LEN]) -> Self {
Public(data)
}
}
#[cfg(feature = "std")]
impl<const LEFT_PLUS_RIGHT_LEN: usize> std::fmt::Display for Public<LEFT_PLUS_RIGHT_LEN>
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> std::fmt::Display
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
where
Public<LEFT_PLUS_RIGHT_LEN>: CryptoType,
Public<LEFT_PLUS_RIGHT_LEN, SubTag>: CryptoType,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_ss58check())
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> sp_std::fmt::Debug for Public<LEFT_PLUS_RIGHT_LEN>
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> sp_std::fmt::Debug
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
where
Public<LEFT_PLUS_RIGHT_LEN>: CryptoType,
Public<LEFT_PLUS_RIGHT_LEN, SubTag>: CryptoType,
[u8; LEFT_PLUS_RIGHT_LEN]: crate::hexdisplay::AsBytesRef,
{
#[cfg(feature = "std")]
@@ -259,9 +203,10 @@ where
}
#[cfg(feature = "serde")]
impl<const LEFT_PLUS_RIGHT_LEN: usize> Serialize for Public<LEFT_PLUS_RIGHT_LEN>
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> Serialize
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
where
Public<LEFT_PLUS_RIGHT_LEN>: CryptoType,
Public<LEFT_PLUS_RIGHT_LEN, SubTag>: CryptoType,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@@ -272,9 +217,10 @@ where
}
#[cfg(feature = "serde")]
impl<'de, const LEFT_PLUS_RIGHT_LEN: usize> Deserialize<'de> for Public<LEFT_PLUS_RIGHT_LEN>
impl<'de, const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> Deserialize<'de>
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
where
Public<LEFT_PLUS_RIGHT_LEN>: CryptoType,
Public<LEFT_PLUS_RIGHT_LEN, SubTag>: CryptoType,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -285,12 +231,17 @@ where
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> PublicT for Public<LEFT_PLUS_RIGHT_LEN> where
Public<LEFT_PLUS_RIGHT_LEN>: CryptoType
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> PublicT
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
where
Public<LEFT_PLUS_RIGHT_LEN, SubTag>: CryptoType,
{
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> Derive for Public<LEFT_PLUS_RIGHT_LEN> {}
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag: PairedCryptoSubTagBound> Derive
for Public<LEFT_PLUS_RIGHT_LEN, SubTag>
{
}
/// Trait characterizing a signature which could be used as individual component of an
/// `paired_crypto:Signature` pair.
@@ -299,54 +250,13 @@ pub trait SignatureBound: ByteArray {}
impl<T: ByteArray> SignatureBound for T {}
/// A pair of signatures of different types
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
pub struct Signature<const LEFT_PLUS_RIGHT_LEN: usize>([u8; LEFT_PLUS_RIGHT_LEN]);
impl<const LEFT_PLUS_RIGHT_LEN: usize> sp_std::hash::Hash for Signature<LEFT_PLUS_RIGHT_LEN> {
fn hash<H: sp_std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> ByteArray for Signature<LEFT_PLUS_RIGHT_LEN> {
const LEN: usize = LEFT_PLUS_RIGHT_LEN;
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> TryFrom<&[u8]> for Signature<LEFT_PLUS_RIGHT_LEN> {
type Error = ();
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
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<const LEFT_PLUS_RIGHT_LEN: usize> AsMut<[u8]> for Signature<LEFT_PLUS_RIGHT_LEN> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> AsRef<[u8; LEFT_PLUS_RIGHT_LEN]>
for Signature<LEFT_PLUS_RIGHT_LEN>
{
fn as_ref(&self) -> &[u8; LEFT_PLUS_RIGHT_LEN] {
&self.0
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> AsRef<[u8]> for Signature<LEFT_PLUS_RIGHT_LEN> {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
pub type Signature<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> =
SignatureBytes<LEFT_PLUS_RIGHT_LEN, (PairedCryptoTag, SubTag)>;
#[cfg(feature = "serde")]
impl<const LEFT_PLUS_RIGHT_LEN: usize> Serialize for Signature<LEFT_PLUS_RIGHT_LEN> {
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> Serialize
for Signature<LEFT_PLUS_RIGHT_LEN, SubTag>
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -356,28 +266,23 @@ impl<const LEFT_PLUS_RIGHT_LEN: usize> Serialize for Signature<LEFT_PLUS_RIGHT_L
}
#[cfg(feature = "serde")]
impl<'de, const LEFT_PLUS_RIGHT_LEN: usize> Deserialize<'de> for Signature<LEFT_PLUS_RIGHT_LEN> {
impl<'de, const LEFT_PLUS_RIGHT_LEN: usize, SubTag> Deserialize<'de>
for Signature<LEFT_PLUS_RIGHT_LEN, SubTag>
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = array_bytes::hex2bytes(&String::deserialize(deserializer)?)
.map_err(|e| de::Error::custom(format!("{:?}", e)))?;
Signature::<LEFT_PLUS_RIGHT_LEN>::try_from(bytes.as_ref()).map_err(|e| {
Signature::<LEFT_PLUS_RIGHT_LEN, SubTag>::try_from(bytes.as_ref()).map_err(|e| {
de::Error::custom(format!("Error converting deserialized data into signature: {:?}", e))
})
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> From<Signature<LEFT_PLUS_RIGHT_LEN>>
for [u8; LEFT_PLUS_RIGHT_LEN]
{
fn from(signature: Signature<LEFT_PLUS_RIGHT_LEN>) -> [u8; LEFT_PLUS_RIGHT_LEN] {
signature.0
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> sp_std::fmt::Debug for Signature<LEFT_PLUS_RIGHT_LEN>
impl<const LEFT_PLUS_RIGHT_LEN: usize, SubTag> sp_std::fmt::Debug
for Signature<LEFT_PLUS_RIGHT_LEN, SubTag>
where
[u8; LEFT_PLUS_RIGHT_LEN]: crate::hexdisplay::AsBytesRef,
{
@@ -392,24 +297,30 @@ where
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> UncheckedFrom<[u8; LEFT_PLUS_RIGHT_LEN]>
for Signature<LEFT_PLUS_RIGHT_LEN>
{
fn unchecked_from(data: [u8; LEFT_PLUS_RIGHT_LEN]) -> Self {
Signature(data)
}
}
/// A key pair.
#[derive(Clone)]
pub struct Pair<
LeftPair: PairT,
RightPair: PairT,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
SubTag,
> {
left: LeftPair,
right: RightPair,
_phantom: PhantomData<fn() -> SubTag>,
}
impl<
LeftPair: PairT + Clone,
RightPair: PairT + Clone,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
SubTag,
> Clone for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, SubTag>
{
fn clone(&self) -> Self {
Self { left: self.left.clone(), right: self.right.clone(), _phantom: PhantomData }
}
}
impl<
@@ -417,18 +328,19 @@ impl<
RightPair: PairT,
const PUBLIC_KEY_LEN: usize,
const SIGNATURE_LEN: usize,
> PairT for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN>
SubTag: PairedCryptoSubTagBound,
> PairT for Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, SubTag>
where
Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN>: CryptoType,
Pair<LeftPair, RightPair, PUBLIC_KEY_LEN, SIGNATURE_LEN, SubTag>: CryptoType,
LeftPair::Signature: SignatureBound,
RightPair::Signature: SignatureBound,
Public<PUBLIC_KEY_LEN>: CryptoType,
Public<PUBLIC_KEY_LEN, SubTag>: CryptoType,
LeftPair::Seed: From<Seed> + Into<Seed>,
RightPair::Seed: From<Seed> + Into<Seed>,
{
type Seed = Seed;
type Public = Public<PUBLIC_KEY_LEN>;
type Signature = Signature<SIGNATURE_LEN>;
type Public = Public<PUBLIC_KEY_LEN, SubTag>;
type Signature = Signature<SIGNATURE_LEN, SubTag>;
fn from_seed_slice(seed_slice: &[u8]) -> Result<Self, SecretStringError> {
if seed_slice.len() != SECURE_SEED_LEN {
@@ -436,7 +348,7 @@ where
}
let left = LeftPair::from_seed_slice(&seed_slice)?;
let right = RightPair::from_seed_slice(&seed_slice)?;
Ok(Pair { left, right })
Ok(Pair { left, right, _phantom: PhantomData })
}
/// Derive a child key from a series of given junctions.
@@ -459,7 +371,7 @@ where
_ => None,
};
Ok((Self { left: left.0, right: right.0 }, seed))
Ok((Self { left: left.0, right: right.0, _phantom: PhantomData }, seed))
}
fn public(&self) -> Self::Public {
@@ -479,16 +391,18 @@ where
Self::Signature::unchecked_from(raw)
}
fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool {
fn verify<Msg: AsRef<[u8]>>(
sig: &Self::Signature,
message: Msg,
public: &Self::Public,
) -> bool {
let Ok(left_pub) = public.0[..LeftPair::Public::LEN].try_into() else { return false };
let Ok(left_sig) = sig.0[0..LeftPair::Signature::LEN].try_into() else { return false };
if !LeftPair::verify(&left_sig, message.as_ref(), &left_pub) {
return false
}
let Ok(right_pub) = public.0[LeftPair::Public::LEN..PUBLIC_KEY_LEN].try_into() else {
return false
};
let Ok(right_pub) = public.0[LeftPair::Public::LEN..].try_into() else { return false };
let Ok(right_sig) = sig.0[LeftPair::Signature::LEN..].try_into() else { return false };
RightPair::verify(&right_sig, message.as_ref(), &right_pub)
}
@@ -506,6 +420,7 @@ where
mod test {
use super::*;
use crate::{crypto::DEV_PHRASE, KeccakHasher};
use codec::{Decode, Encode};
use ecdsa_bls377::{Pair, Signature};
use crate::{bls377, ecdsa};