diem_types/transaction/
authenticator.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::account_address::AccountAddress;
9use anyhow::{bail, ensure, Error, Result};
10use diem_crypto::{
11    bls::{
12        BLSPublicKey, BLSPublicKeyUnchecked, BLSSignature,
13        BLSSignatureUnchecked,
14    },
15    hash::CryptoHash,
16    multi_bls::MultiBLSSignature,
17    traits::Signature,
18    CryptoMaterialError, HashValue, ValidCryptoMaterial,
19    ValidCryptoMaterialStringExt,
20};
21use diem_crypto_derive::{CryptoHasher, DeserializeKey, SerializeKey};
22#[cfg(any(test, feature = "fuzzing"))]
23use proptest_derive::Arbitrary;
24use rand::{rngs::OsRng, Rng};
25use serde::{Deserialize, Serialize};
26use std::{convert::TryFrom, fmt, str::FromStr};
27
28/// A `TransactionAuthenticator` is an abstraction of a signature scheme. It
29/// must know: (1) How to check its signature against a message and public key
30/// (2) How to convert its public key into an `AuthenticationKeyPreimage`
31/// structured as (public_key | signaure_scheme_id).
32/// Each on-chain `DiemAccount` must store an `AuthenticationKey` (computed via
33/// a sha3 hash of an `AuthenticationKeyPreimage`).
34/// Each transaction submitted to the Diem blockchain contains a
35/// `TransactionAuthenticator`. During transaction execution, the executor will
36/// check if the `TransactionAuthenticator`'s signature on the transaction hash
37/// is well-formed (1) and whether the sha3 hash of the
38/// `TransactionAuthenticator`'s `AuthenticationKeyPreimage` matches the
39/// `AuthenticationKey` stored under the transaction's sender account address
40/// (2).
41
42// TODO: in the future, can tie these to the TransactionAuthenticator enum directly with https://github.com/rust-lang/rust/issues/60553
43//
44// Discriminants must mirror the matching `TransactionAuthenticator` BCS
45// tag — `AuthenticationKeyPreimage::new` appends `scheme as u8`.
46#[derive(Debug)]
47#[repr(u8)]
48#[non_exhaustive]
49pub enum Scheme {
50    /// BCS tag 0 (ex-Ed25519, never used in Conflux PoS).
51    ReservedEd25519 = 0,
52    /// BCS tag 1 (ex-MultiEd25519, never used in Conflux PoS).
53    ReservedMultiEd25519 = 1,
54    BLS = 2,
55    MultiBLS = 3,
56    // ... add more schemes here
57}
58
59impl fmt::Display for Scheme {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        let display = match self {
62            Scheme::ReservedEd25519 => "reserved_ed25519",
63            Scheme::ReservedMultiEd25519 => "reserved_multi_ed25519",
64            Scheme::BLS => "bls",
65            Scheme::MultiBLS => "multi_bls",
66        };
67        write!(f, "Scheme::{}", display)
68    }
69}
70
71#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
72pub enum TransactionAuthenticator {
73    /// Reserved (was Ed25519, never used in Conflux PoS). Kept for BCS index
74    /// compat.
75    _ReservedEd25519,
76    /// Reserved (was MultiEd25519, never used in Conflux PoS). Kept for BCS
77    /// index compat.
78    _ReservedMultiEd25519,
79    /// BLS signature
80    BLS {
81        public_key: BLSPublicKey,
82        signature: BLSSignature,
83    },
84    MultiBLS {
85        signature: MultiBLSSignature,
86    }, // ... add more schemes here
87}
88
89#[derive(Deserialize)]
90pub enum TransactionAuthenticatorUnchecked {
91    /// Reserved (was Ed25519, never used in Conflux PoS). Kept for BCS index
92    /// compat.
93    _ReservedEd25519,
94    /// Reserved (was MultiEd25519, never used in Conflux PoS). Kept for BCS
95    /// index compat.
96    _ReservedMultiEd25519,
97    BLS {
98        public_key: BLSPublicKeyUnchecked,
99        signature: BLSSignatureUnchecked,
100    },
101    MultiBLS {
102        signature: MultiBLSSignature,
103    },
104}
105
106impl From<TransactionAuthenticatorUnchecked> for TransactionAuthenticator {
107    fn from(t: TransactionAuthenticatorUnchecked) -> Self {
108        match t {
109            TransactionAuthenticatorUnchecked::_ReservedEd25519 => {
110                Self::_ReservedEd25519
111            }
112            TransactionAuthenticatorUnchecked::_ReservedMultiEd25519 => {
113                Self::_ReservedMultiEd25519
114            }
115            TransactionAuthenticatorUnchecked::BLS {
116                public_key,
117                signature,
118            } => Self::BLS {
119                public_key: public_key.into(),
120                signature: signature.into(),
121            },
122            TransactionAuthenticatorUnchecked::MultiBLS { signature } => {
123                Self::MultiBLS { signature }
124            }
125        }
126    }
127}
128
129impl TransactionAuthenticator {
130    /// Unique identifier for the signature scheme.
131    pub fn scheme(&self) -> Scheme {
132        match self {
133            Self::_ReservedEd25519 => Scheme::ReservedEd25519,
134            Self::_ReservedMultiEd25519 => Scheme::ReservedMultiEd25519,
135            Self::BLS { .. } => Scheme::BLS,
136            Self::MultiBLS { .. } => Scheme::MultiBLS,
137        }
138    }
139
140    pub fn bls(public_key: BLSPublicKey, signature: BLSSignature) -> Self {
141        Self::BLS {
142            public_key,
143            signature,
144        }
145    }
146
147    pub fn multi_bls(signature: MultiBLSSignature) -> Self {
148        Self::MultiBLS { signature }
149    }
150
151    /// Return Ok if the authenticator's public key matches its signature, Err
152    /// otherwise. Reserved variants always return Err (Byzantine input
153    /// must not crash the executor).
154    pub fn verify<T: Serialize + CryptoHash>(&self, message: &T) -> Result<()> {
155        match self {
156            Self::_ReservedEd25519 | Self::_ReservedMultiEd25519 => {
157                bail!("reserved authenticator variant has no signature")
158            }
159            Self::BLS {
160                public_key,
161                signature,
162            } => signature.verify(message, public_key),
163            Self::MultiBLS { .. } => {
164                // we will verify this case in pos state
165                Ok(())
166            }
167        }
168    }
169
170    /// Return the raw bytes of `self.public_key`. MultiBLS has no per-tx
171    /// pubkey (the verifying set lives on the committee); Reserved
172    /// variants have no signature material at all.
173    pub fn public_key_bytes(&self) -> Vec<u8> {
174        match self {
175            Self::_ReservedEd25519 | Self::_ReservedMultiEd25519 => Vec::new(),
176            Self::BLS { public_key, .. } => public_key.to_bytes().to_vec(),
177            Self::MultiBLS { .. } => Vec::new(),
178        }
179    }
180
181    /// Return the raw bytes of `self.signature`
182    pub fn signature_bytes(&self) -> Vec<u8> {
183        match self {
184            Self::_ReservedEd25519 | Self::_ReservedMultiEd25519 => Vec::new(),
185            Self::BLS { signature, .. } => signature.to_bytes().to_vec(),
186            Self::MultiBLS { signature } => signature.to_bytes(),
187        }
188    }
189
190    /// Return an authentication key preimage derived from `self`'s public key
191    /// and scheme id
192    pub fn authentication_key_preimage(&self) -> AuthenticationKeyPreimage {
193        AuthenticationKeyPreimage::new(self.public_key_bytes(), self.scheme())
194    }
195
196    /// Return an authentication key derived from `self`'s public key and scheme
197    /// id
198    pub fn authentication_key(&self) -> AuthenticationKey {
199        AuthenticationKey::from_preimage(&self.authentication_key_preimage())
200    }
201}
202
203/// A struct that represents an account authentication key. An account's address
204/// is the last 16 bytes of authentication key used to create it
205#[derive(
206    Clone,
207    Copy,
208    CryptoHasher,
209    Debug,
210    DeserializeKey,
211    Eq,
212    Hash,
213    Ord,
214    PartialEq,
215    PartialOrd,
216    SerializeKey,
217)]
218#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
219pub struct AuthenticationKey([u8; AuthenticationKey::LENGTH]);
220
221impl AuthenticationKey {
222    /// The number of bytes in an authentication key.
223    pub const LENGTH: usize = 32;
224
225    /// Create an authentication key from `bytes`
226    pub const fn new(bytes: [u8; Self::LENGTH]) -> Self { Self(bytes) }
227
228    /// Create an authentication key from a preimage by taking its sha3 hash
229    pub fn from_preimage(
230        preimage: &AuthenticationKeyPreimage,
231    ) -> AuthenticationKey {
232        AuthenticationKey::new(*HashValue::sha3_256_of(&preimage.0).as_ref())
233    }
234
235    /// Return an address derived from the last `AccountAddress::LENGTH` bytes
236    /// of this authentication key.
237    pub fn derived_address(&self) -> AccountAddress {
238        // keep only last 16 bytes
239        let mut array = [0u8; AccountAddress::LENGTH];
240        array.copy_from_slice(&self.0[Self::LENGTH - AccountAddress::LENGTH..]);
241        AccountAddress::new(array)
242    }
243
244    /// Return the first AccountAddress::LENGTH bytes of this authentication key
245    pub fn prefix(&self) -> [u8; AccountAddress::LENGTH] {
246        let mut array = [0u8; AccountAddress::LENGTH];
247        array.copy_from_slice(&self.0[..AccountAddress::LENGTH]);
248        array
249    }
250
251    /// Construct a vector from this authentication key
252    pub fn to_vec(&self) -> Vec<u8> { self.0.to_vec() }
253
254    /// Create a random authentication key. For testing only
255    pub fn random() -> Self {
256        let mut rng = OsRng;
257        let buf: [u8; Self::LENGTH] = rng.gen();
258        AuthenticationKey::new(buf)
259    }
260}
261
262impl ValidCryptoMaterial for AuthenticationKey {
263    fn to_bytes(&self) -> Vec<u8> { self.to_vec() }
264}
265
266/// A value that can be hashed to produce an authentication key
267pub struct AuthenticationKeyPreimage(Vec<u8>);
268
269impl AuthenticationKeyPreimage {
270    /// Return bytes for (public_key | scheme_id)
271    fn new(mut public_key_bytes: Vec<u8>, scheme: Scheme) -> Self {
272        public_key_bytes.push(scheme as u8);
273        Self(public_key_bytes)
274    }
275
276    /// Construct a vector from this authentication key
277    pub fn into_vec(self) -> Vec<u8> { self.0 }
278}
279
280impl fmt::Display for TransactionAuthenticator {
281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282        write!(
283            f,
284            "TransactionAuthenticator[scheme id: {:?}, public key: {}, signature: {}]",
285            self.scheme(),
286            hex::encode(&self.public_key_bytes()),
287            hex::encode(&self.signature_bytes())
288        )
289    }
290}
291
292impl TryFrom<&[u8]> for AuthenticationKey {
293    type Error = CryptoMaterialError;
294
295    fn try_from(
296        bytes: &[u8],
297    ) -> std::result::Result<AuthenticationKey, CryptoMaterialError> {
298        if bytes.len() != Self::LENGTH {
299            return Err(CryptoMaterialError::WrongLengthError);
300        }
301        let mut addr = [0u8; Self::LENGTH];
302        addr.copy_from_slice(bytes);
303        Ok(AuthenticationKey(addr))
304    }
305}
306
307impl TryFrom<Vec<u8>> for AuthenticationKey {
308    type Error = CryptoMaterialError;
309
310    fn try_from(
311        bytes: Vec<u8>,
312    ) -> std::result::Result<AuthenticationKey, CryptoMaterialError> {
313        AuthenticationKey::try_from(&bytes[..])
314    }
315}
316
317impl FromStr for AuthenticationKey {
318    type Err = Error;
319
320    fn from_str(s: &str) -> Result<Self> {
321        ensure!(
322            !s.is_empty(),
323            "authentication key string should not be empty.",
324        );
325        let bytes_out = ::hex::decode(s)?;
326        let key = AuthenticationKey::try_from(bytes_out.as_slice())?;
327        Ok(key)
328    }
329}
330
331impl AsRef<[u8]> for AuthenticationKey {
332    fn as_ref(&self) -> &[u8] { &self.0 }
333}
334
335impl fmt::LowerHex for AuthenticationKey {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        write!(f, "{}", hex::encode(&self.0))
338    }
339}
340
341impl fmt::Display for AuthenticationKey {
342    fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
343        // Forward to the LowerHex impl with a "0x" prepended (the # flag).
344        write!(f, "{:#x}", self)
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::{Scheme, TransactionAuthenticator};
351    use crate::{
352        account_address::AccountAddress,
353        transaction::{
354            authenticator::AuthenticationKey, RawTransaction, RetirePayload,
355        },
356    };
357    use std::str::FromStr;
358
359    #[test]
360    fn test_from_str_should_not_panic_by_given_empty_string() {
361        assert!(AuthenticationKey::from_str("").is_err());
362    }
363
364    // Regression: a Byzantine PoS proposer can BCS-decode a block
365    // payload into a `_Reserved*` variant; `verify()` must Err, not
366    // panic.
367    #[test]
368    fn reserved_authenticator_verify_returns_err_not_panic() {
369        let raw_txn = RawTransaction::new_retire(
370            AccountAddress::ZERO,
371            RetirePayload {
372                node_id: AccountAddress::ZERO,
373                votes: 0,
374            },
375        );
376
377        for auth in [
378            TransactionAuthenticator::_ReservedEd25519,
379            TransactionAuthenticator::_ReservedMultiEd25519,
380        ] {
381            let err = auth.verify(&raw_txn).unwrap_err();
382            assert!(
383                err.to_string().contains("reserved"),
384                "expected 'reserved' in error, got: {}",
385                err,
386            );
387        }
388    }
389
390    #[test]
391    fn reserved_authenticator_accessors_are_panic_free() {
392        let auth_a = TransactionAuthenticator::_ReservedEd25519;
393        let auth_b = TransactionAuthenticator::_ReservedMultiEd25519;
394
395        // Distinct scheme bytes keep preimages distinct across variants.
396        assert!(matches!(auth_a.scheme(), Scheme::ReservedEd25519));
397        assert!(matches!(auth_b.scheme(), Scheme::ReservedMultiEd25519));
398        assert_ne!(auth_a.scheme() as u8, auth_b.scheme() as u8);
399        assert!(auth_a.public_key_bytes().is_empty());
400        assert!(auth_a.signature_bytes().is_empty());
401        assert!(auth_b.public_key_bytes().is_empty());
402        assert!(auth_b.signature_bytes().is_empty());
403    }
404}