From b7bf966c9210c80810419bde86a2df7f132298d0 Mon Sep 17 00:00:00 2001 From: Emma Turner Date: Tue, 19 Dec 2023 09:09:07 +0000 Subject: [PATCH] crypto: align Signature with octez crypto impl --- CHANGELOG.md | 2 + crypto/src/hash.rs | 46 ++++++++---- crypto/src/lib.rs | 1 + crypto/src/signature.rs | 130 +++++++++++++++++++++++++++++++++ tezos-encoding/src/enc.rs | 8 +- tezos-encoding/src/encoding.rs | 11 ++- tezos-encoding/src/nom.rs | 17 ++++- 7 files changed, 196 insertions(+), 19 deletions(-) create mode 100644 crypto/src/signature.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a67bc2166..967da3f6b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add `FromBase58CheckError::IncorrectBase58Prefix` variant. - Add `NomReader`, `BinWriter` support for `Ed25519Signature`. +- Add `signature::Signature` enum representing possible types of signature used in Tezos. ### Changed - `tezos_data_encoding`: The `NomReader` trait is now explicitly parameterized by the lifetime of the input byte slice. - Altered hashes to implement `AsRef<[u8]>` instead of `AsRef>`. +- Renamed `hash::Signature` to `hash::UnknownSignature`. ### Deprecated diff --git a/crypto/src/hash.rs b/crypto/src/hash.rs index a6b42b5723..7680fefcf1 100644 --- a/crypto/src/hash.rs +++ b/crypto/src/hash.rs @@ -7,6 +7,7 @@ use std::convert::{TryFrom, TryInto}; use crate::{ base58::{FromBase58Check, FromBase58CheckError, ToBase58Check}, blake2b::{self, Blake2bError}, + signature::Signature, CryptoError, PublicKeySignatureVerifier, PublicKeyWithHash, }; use serde::{Deserialize, Serialize}; @@ -75,7 +76,7 @@ pub enum FromBytesError { /// Invalid data size #[error("invalid hash size")] InvalidSize, - /// Ed25519 decrompression + /// Ed25519 decompression #[error("From bytes ed25519: {0:?}")] Ed25519(ed25519_dalek::SignatureError), } @@ -308,7 +309,7 @@ define_hash!(PublicKeyBls); define_hash!(SeedEd25519); define_hash!(SecretKeyEd25519); define_hash!(SecretKeyBls); -define_hash!(Signature); +define_hash!(UnknownSignature); define_hash!(Ed25519Signature); define_hash!(Secp256k1Signature); define_hash!(P256Signature); @@ -367,7 +368,7 @@ pub enum HashType { // "\003\150\192\040" (* BLsk(54) *) SecretKeyBls, // "\004\130\043" (* sig(96) *) - Signature, + UnknownSignature, // "\009\245\205\134\018" (* edsig(99) *) Ed25519Signature, // "\013\115\101\019\063" (* spsig1(99) *) @@ -412,7 +413,7 @@ impl HashType { HashType::SeedEd25519 => &SEED_ED25519, HashType::SecretKeyEd25519 => &SECRET_KEY_ED25519, HashType::SecretKeyBls => &SECRET_KEY_BLS, - HashType::Signature => &GENERIC_SIGNATURE_HASH, + HashType::UnknownSignature => &GENERIC_SIGNATURE_HASH, HashType::Ed25519Signature => &ED22519_SIGNATURE_HASH, HashType::Secp256k1Signature => &SECP256K1_SIGNATURE_HASH, HashType::P256Signature => &P256_SIGNATURE_HASH, @@ -453,7 +454,7 @@ impl HashType { | HashType::Ed25519Signature | HashType::Secp256k1Signature | HashType::P256Signature - | HashType::Signature => 64, + | HashType::UnknownSignature => 64, HashType::BlsSignature => 96, } } @@ -464,7 +465,8 @@ impl HashType { Err(FromBytesError::InvalidSize) } else { let mut hash = Vec::with_capacity(self.base58check_prefix().len() + data.len()); - if matches!(self, Self::Signature) && data == [0; Self::Ed25519Signature.size()] { + if matches!(self, Self::UnknownSignature) && data == [0; Self::Ed25519Signature.size()] + { hash.extend(Self::Ed25519Signature.base58check_prefix()); } else { hash.extend(self.base58check_prefix()); @@ -482,7 +484,7 @@ impl HashType { /// Convert string representation of the hash to bytes form. pub fn b58check_to_hash(&self, data: &str) -> Result { let mut hash = data.from_base58check()?; - if let HashType::Signature = self { + if let HashType::UnknownSignature = self { // zero signature is represented as Ed25519 signature if hash.len() == HashType::Ed25519Signature.size() @@ -665,7 +667,9 @@ impl SecretKeyEd25519 { let payload = crate::blake2b::digest_256(data.as_ref()) .map_err(|e| CryptoError::AlgorithmError(e.to_string()))?; let signature = sk.sign(&payload); - Ok(Signature(signature.to_bytes().to_vec())) + Ok(Signature::Ed25519(Ed25519Signature( + signature.to_bytes().to_vec(), + ))) } } @@ -676,8 +680,7 @@ impl PublicKeySignatureVerifier for PublicKeyEd25519 { /// Verifies the correctness of `bytes` signed by Ed25519 as the `signature`. fn verify_signature(&self, signature: &Signature, bytes: &[u8]) -> Result { let signature = signature - .0 - .as_slice() + .as_ref() .try_into() .map(ed25519_dalek::Signature::from_bytes) .map_err(|_| CryptoError::InvalidSignature)?; @@ -706,7 +709,7 @@ impl PublicKeySignatureVerifier for PublicKeySecp256k1 { Some(libsecp256k1::PublicKeyFormat::Compressed), ) .map_err(|_| CryptoError::InvalidPublicKey)?; - let sig = libsecp256k1::Signature::parse_standard_slice(&signature.0) + let sig = libsecp256k1::Signature::parse_standard_slice(signature.as_ref()) .map_err(|_| CryptoError::InvalidSignature)?; let msg = libsecp256k1::Message::parse_slice(bytes).map_err(|_| CryptoError::InvalidMessage)?; @@ -767,10 +770,10 @@ impl PublicKeySignatureVerifier for PublicKeyP256 { let pk = p256::ecdsa::VerifyingKey::from_sec1_bytes(&self.0) .map_err(|_| CryptoError::InvalidPublicKey)?; - let r: [u8; 32] = signature.0[..32] + let r: [u8; 32] = signature.as_ref()[..32] .try_into() .map_err(|_| CryptoError::InvalidSignature)?; - let s: [u8; 32] = signature.0[32..] + let s: [u8; 32] = signature.as_ref()[32..] .try_into() .map_err(|_| CryptoError::InvalidSignature)?; let sig = p256::ecdsa::Signature::from_scalars(r, s) @@ -1097,13 +1100,13 @@ mod tests { #[test] fn test_b85_to_signature_hash() -> Result<(), anyhow::Error> { let encoded = "sigbQ5ZNvkjvGssJgoAnUAfY4Wvvg3QZqawBYB1j1VDBNTMBAALnCzRHWzer34bnfmzgHg3EvwdzQKdxgSghB897cono6gbQ"; - let decoded = hex::encode(HashType::Signature.b58check_to_hash(encoded)?); + let decoded = hex::encode(HashType::UnknownSignature.b58check_to_hash(encoded)?); let expected = "66804fe735e06e97e26da8236b6341b91c625d5e82b3524ec0a88cc982365e70f8a5b9bc65df2ea6d21ee244cc3a96fb33031c394c78b1179ff1b8a44237740c"; assert_eq!(expected, decoded); assert_eq!( encoded, - HashType::Signature.hash_to_b58check(&hex::decode(decoded)?)? + HashType::UnknownSignature.hash_to_b58check(&hex::decode(decoded)?)? ); Ok(()) } @@ -1299,7 +1302,18 @@ mod tests { ["p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRscDZJrrApbRh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v"] ); - test!(generic_sig, Signature, ["sigNCaj9CnmD94eZH9C7aPPqBbVCJF72fYmCFAXqEbWfqE633WNFWYQJFnDUFgRUQXR8fQ5tKSfJeTe6UAi75eTzzQf7AEc1"]); + test!(generic_sig, UnknownSignature, ["sigNCaj9CnmD94eZH9C7aPPqBbVCJF72fYmCFAXqEbWfqE633WNFWYQJFnDUFgRUQXR8fQ5tKSfJeTe6UAi75eTzzQf7AEc1"]); + + use crate::signature::Signature; + test!( + composite_sig, + Signature, + [ + "BLsigAmLKnuw12tethjMmotFPaQ6u4XCKrVk6c15dkRXKkjDDjHywbhS3nd4rBT31yrCvvQrS2HntWhDRu7sX8Vvek53zBUwQHqfcHRiVKVj1ehq8CBYs1Z7XW2rkL2XkVNHua4cnvxY7F", + "edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q", + "spsig1PPUFZucuAQybs5wsqsNQ68QNgFaBnVKMFaoZZfi1BtNnuCAWnmL9wVy5HfHkR6AeodjVGxpBVVSYcJKyMURn6K1yknYLm", + "p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRscDZJrrApbRh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v" + ]); test!( smart_rollup_hash, diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 7368d7d3d9..3ea7bdfe0e 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -14,6 +14,7 @@ pub mod base58; pub mod bls; #[macro_use] pub mod hash; +pub mod signature; #[derive(Debug, Error)] pub enum CryptoError { diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs new file mode 100644 index 0000000000..4866e662c9 --- /dev/null +++ b/crypto/src/signature.rs @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2023 TriliTech +// Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors +// +// Ported from octez: lib_crypto/signature_v1.ml +// +// Copyright (c) 2018 Dynamic Ledger Solutions, Inc. +// Copyright (c) 2020 Metastate AG +// Copyright (c) 2022 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +use crate::base58::FromBase58CheckError; +use crate::hash::{ + BlsSignature, Ed25519Signature, FromBytesError, HashTrait, HashType, P256Signature, + Secp256k1Signature, UnknownSignature, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Signature { + Ed25519(Ed25519Signature), + Secp256k1(Secp256k1Signature), + P256(P256Signature), + Bls(BlsSignature), + Unknown(UnknownSignature), +} + +impl Signature { + pub fn from_base58_check(data: &str) -> Result { + if data.starts_with("edsig") { + Ok(Signature::Ed25519(Ed25519Signature::from_b58check(data)?)) + } else if data.starts_with("spsig1") { + Ok(Signature::Secp256k1(Secp256k1Signature::from_b58check( + data, + )?)) + } else if data.starts_with("p2sig") { + Ok(Signature::P256(P256Signature::from_b58check(data)?)) + } else if data.starts_with("BLsig") { + Ok(Signature::Bls(BlsSignature::from_b58check(data)?)) + } else { + Ok(Signature::Unknown(UnknownSignature::from_b58check(data)?)) + } + } + + pub fn to_base58_check(&self) -> String { + match self { + Self::Ed25519(s) => s.to_b58check(), + Self::Secp256k1(s) => s.to_b58check(), + Self::P256(s) => s.to_b58check(), + Self::Bls(s) => s.to_b58check(), + Self::Unknown(s) => s.to_b58check(), + } + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + match self { + Self::Ed25519(s) => s.0.as_ref(), + Self::Secp256k1(s) => s.0.as_ref(), + Self::P256(s) => s.0.as_ref(), + Self::Bls(s) => s.0.as_ref(), + Self::Unknown(s) => s.0.as_ref(), + } + } +} + +impl From for Vec { + fn from(s: Signature) -> Self { + match s { + Signature::Ed25519(s) => s.0, + Signature::Secp256k1(s) => s.0, + Signature::P256(s) => s.0, + Signature::Bls(s) => s.0, + Signature::Unknown(s) => s.0, + } + } +} + +impl TryFrom> for Signature { + type Error = FromBytesError; + + fn try_from(hash: Vec) -> Result { + if hash.len() == HashType::BlsSignature.size() { + Ok(Signature::Bls(BlsSignature(hash))) + } else if hash.len() == HashType::UnknownSignature.size() { + Ok(Signature::Unknown(UnknownSignature(hash))) + } else { + Err(FromBytesError::InvalidSize) + } + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = FromBytesError; + + fn try_from(hash: &[u8]) -> Result { + if hash.len() == HashType::BlsSignature.size() { + Ok(Signature::Bls(BlsSignature(hash.to_vec()))) + } else if hash.len() == HashType::UnknownSignature.size() { + Ok(Signature::Unknown(UnknownSignature(hash.to_vec()))) + } else { + Err(FromBytesError::InvalidSize) + } + } +} + +impl TryFrom<&str> for Signature { + type Error = FromBase58CheckError; + + fn try_from(s: &str) -> Result { + Signature::from_base58_check(s) + } +} + +impl ::core::str::FromStr for Signature { + type Err = FromBase58CheckError; + + fn from_str(s: &str) -> Result { + Signature::from_base58_check(s) + } +} + +impl ::std::fmt::Display for Signature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO - this could be done without the need to perform a heap allocation. + write!(f, "{}", self.to_base58_check()) + } +} diff --git a/tezos-encoding/src/enc.rs b/tezos-encoding/src/enc.rs index 4f92c44523..8bcfc73cea 100644 --- a/tezos-encoding/src/enc.rs +++ b/tezos-encoding/src/enc.rs @@ -351,7 +351,7 @@ encode_hash!(crypto::hash::PublicKeyP256); encode_hash!(crypto::hash::PublicKeyBls); encode_hash!(crypto::hash::SecretKeyEd25519); encode_hash!(crypto::hash::SecretKeyBls); -encode_hash!(crypto::hash::Signature); +encode_hash!(crypto::hash::UnknownSignature); encode_hash!(crypto::hash::Ed25519Signature); encode_hash!(crypto::hash::Secp256k1Signature); encode_hash!(crypto::hash::P256Signature); @@ -359,6 +359,12 @@ encode_hash!(crypto::hash::BlsSignature); encode_hash!(crypto::hash::NonceHash); encode_hash!(crypto::hash::SmartRollupHash); +impl BinWriter for crypto::signature::Signature { + fn bin_write(&self, out: &mut Vec) -> BinResult { + dynamic(bytes)(self, out) + } +} + impl BinWriter for Mutez { fn bin_write(&self, out: &mut Vec) -> BinResult { n_bignum(self.0.magnitude(), out) diff --git a/tezos-encoding/src/encoding.rs b/tezos-encoding/src/encoding.rs index 342e106f58..5ff8e68004 100644 --- a/tezos-encoding/src/encoding.rs +++ b/tezos-encoding/src/encoding.rs @@ -338,11 +338,20 @@ hash_has_encoding!(PublicKeyP256, PUBLIC_KEY_P256); hash_has_encoding!(PublicKeyBls, PUBLIC_KEY_BLS); hash_has_encoding!(SecretKeyEd25519, SECRET_KEY_ED25519); hash_has_encoding!(SecretKeyBls, SECRET_KEY_BLS); -hash_has_encoding!(Signature, SIGNATURE); +hash_has_encoding!(UnknownSignature, UNKNOWN_SIGNATURE); +hash_has_encoding!(Ed25519Signature, ED25519_SIGNATURE_HASH); +hash_has_encoding!(Secp256k1Signature, SECP256K1_SIGNATURE_HASH); +hash_has_encoding!(P256Signature, P256_SIGNATURE_HASH); hash_has_encoding!(BlsSignature, BLS_SIGNATURE_HASH); hash_has_encoding!(NonceHash, NONCE_HASH); hash_has_encoding!(SmartRollupHash, SMART_ROLLUP_HASH); +impl HasEncoding for crypto::signature::Signature { + fn encoding() -> Encoding { + Encoding::Custom + } +} + /// Creates impl HasEncoding for given struct backed by lazy_static ref instance with encoding. #[macro_export] macro_rules! has_encoding { diff --git a/tezos-encoding/src/nom.rs b/tezos-encoding/src/nom.rs index 15784e9a1a..7288f532e6 100644 --- a/tezos-encoding/src/nom.rs +++ b/tezos-encoding/src/nom.rs @@ -76,6 +76,7 @@ pub mod error { List, Dynamic, Bounded, + Signature, } impl<'a> DecodeError> { @@ -265,7 +266,7 @@ hash_nom_reader!(PublicKeyP256); hash_nom_reader!(PublicKeyBls); hash_nom_reader!(SecretKeyEd25519); hash_nom_reader!(SecretKeyBls); -hash_nom_reader!(Signature); +hash_nom_reader!(UnknownSignature); hash_nom_reader!(Ed25519Signature); hash_nom_reader!(Secp256k1Signature); hash_nom_reader!(P256Signature); @@ -273,6 +274,20 @@ hash_nom_reader!(BlsSignature); hash_nom_reader!(NonceHash); hash_nom_reader!(SmartRollupHash); +impl<'a> NomReader<'a> for crypto::signature::Signature { + fn nom_read(input: &'a [u8]) -> NomResult<'a, Self> { + let (rest, v) = dynamic(bytes)(input)?; + if let Ok(v) = Self::try_from(v) { + Ok((rest, v)) + } else { + Err(Err::Error(DecodeError::limit( + input, + BoundedEncodingKind::Signature, + ))) + } + } +} + impl<'a> NomReader<'a> for Zarith { fn nom_read(bytes: &[u8]) -> NomResult { map(z_bignum, |big_int| big_int.into())(bytes)