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
@@ -0,0 +1,244 @@
// 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.
//! Generic byte array which can be specialized with a marker type.
use crate::{
crypto::{FromEntropy, UncheckedFrom},
hash::{H256, H512},
};
use codec::{Decode, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use scale_info::TypeInfo;
use sp_runtime_interface::pass_by::{self, PassBy, PassByInner};
/// Generic byte array holding some crypto-related raw data.
///
/// The type is generic over a constant length `N` and a "tag" `T` which
/// can be used to specialize the byte array without requiring newtypes.
///
/// The tag `T` is held in a `PhantomData<fn() ->T>`, a trick allowing
/// `CryptoBytes` to be `Send` and `Sync` regardless of `T` properties
/// ([ref](https://doc.rust-lang.org/nomicon/phantom-data.html#table-of-phantomdata-patterns)).
#[derive(Encode, Decode, MaxEncodedLen)]
#[repr(transparent)]
pub struct CryptoBytes<const N: usize, T = ()>(pub [u8; N], PhantomData<fn() -> T>);
impl<const N: usize, T> Copy for CryptoBytes<N, T> {}
impl<const N: usize, T> Clone for CryptoBytes<N, T> {
fn clone(&self) -> Self {
Self(self.0, PhantomData)
}
}
impl<const N: usize, T> TypeInfo for CryptoBytes<N, T> {
type Identity = [u8; N];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
impl<const N: usize, T> PartialOrd for CryptoBytes<N, T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<const N: usize, T> Ord for CryptoBytes<N, T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<const N: usize, T> PartialEq for CryptoBytes<N, T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<const N: usize, T> core::hash::Hash for CryptoBytes<N, T> {
fn hash<H: scale_info::prelude::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<const N: usize, T> Eq for CryptoBytes<N, T> {}
impl<const N: usize, T> Default for CryptoBytes<N, T> {
fn default() -> Self {
Self([0_u8; N], PhantomData)
}
}
impl<const N: usize, T> PassByInner for CryptoBytes<N, T> {
type Inner = [u8; N];
fn into_inner(self) -> Self::Inner {
self.0
}
fn inner(&self) -> &Self::Inner {
&self.0
}
fn from_inner(inner: Self::Inner) -> Self {
Self(inner, PhantomData)
}
}
impl<const N: usize, T> PassBy for CryptoBytes<N, T> {
type PassBy = pass_by::Inner<Self, [u8; N]>;
}
impl<const N: usize, T> AsRef<[u8]> for CryptoBytes<N, T> {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl<const N: usize, T> AsMut<[u8]> for CryptoBytes<N, T> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
impl<const N: usize, T> From<CryptoBytes<N, T>> for [u8; N] {
fn from(v: CryptoBytes<N, T>) -> [u8; N] {
v.0
}
}
impl<const N: usize, T> AsRef<[u8; N]> for CryptoBytes<N, T> {
fn as_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize, T> AsMut<[u8; N]> for CryptoBytes<N, T> {
fn as_mut(&mut self) -> &mut [u8; N] {
&mut self.0
}
}
impl<const N: usize, T> From<[u8; N]> for CryptoBytes<N, T> {
fn from(value: [u8; N]) -> Self {
Self::from_raw(value)
}
}
impl<const N: usize, T> TryFrom<&[u8]> for CryptoBytes<N, T> {
type Error = ();
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
if data.len() != N {
return Err(())
}
let mut r = [0u8; N];
r.copy_from_slice(data);
Ok(Self::from_raw(r))
}
}
impl<const N: usize, T> UncheckedFrom<[u8; N]> for CryptoBytes<N, T> {
fn unchecked_from(data: [u8; N]) -> Self {
Self::from_raw(data)
}
}
impl<const N: usize, T> core::ops::Deref for CryptoBytes<N, T> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize, T> CryptoBytes<N, T> {
/// Construct from raw array.
pub fn from_raw(inner: [u8; N]) -> Self {
Self(inner, PhantomData)
}
/// Construct from raw array.
pub fn to_raw(self) -> [u8; N] {
self.0
}
/// Return a slice filled with raw data.
pub fn as_array_ref(&self) -> &[u8; N] {
&self.0
}
}
impl<const N: usize, T> crate::ByteArray for CryptoBytes<N, T> {
const LEN: usize = N;
}
impl<const N: usize, T> FromEntropy for CryptoBytes<N, T> {
fn from_entropy(input: &mut impl codec::Input) -> Result<Self, codec::Error> {
let mut result = Self::default();
input.read(result.as_mut())?;
Ok(result)
}
}
impl<T> From<CryptoBytes<32, T>> for H256 {
fn from(x: CryptoBytes<32, T>) -> H256 {
H256::from(x.0)
}
}
impl<T> From<CryptoBytes<64, T>> for H512 {
fn from(x: CryptoBytes<64, T>) -> H512 {
H512::from(x.0)
}
}
impl<T> UncheckedFrom<H256> for CryptoBytes<32, T> {
fn unchecked_from(x: H256) -> Self {
Self::from_h256(x)
}
}
impl<T> CryptoBytes<32, T> {
/// A new instance from an H256.
pub fn from_h256(x: H256) -> Self {
Self::from_raw(x.into())
}
}
impl<T> CryptoBytes<64, T> {
/// A new instance from an H512.
pub fn from_h512(x: H512) -> Self {
Self::from_raw(x.into())
}
}
/// Tag used for generic public key bytes.
pub struct PublicTag;
/// Generic encoded public key.
pub type PublicBytes<const N: usize, SubTag> = CryptoBytes<N, (PublicTag, SubTag)>;
/// Tag used for generic signature bytes.
pub struct SignatureTag;
/// Generic encoded signature.
pub type SignatureBytes<const N: usize, SubTag> = CryptoBytes<N, (SignatureTag, SubTag)>;