miden-protocol 0.15.0

Core components of the Miden protocol
Documentation
use alloc::string::{String, ToString};
use core::fmt;

use super::v1;
use crate::Felt;
use crate::account::account_id::AccountIdPrefixV1;
use crate::account::{AccountIdVersion, AccountType};
use crate::errors::AccountIdError;
use crate::utils::serde::{
    ByteReader,
    ByteWriter,
    Deserializable,
    DeserializationError,
    Serializable,
};

// ACCOUNT ID PREFIX
// ================================================================================================

/// The prefix of an [`AccountId`][id], i.e. its first field element.
///
/// See the [`AccountId`][id] documentation for details.
///
/// The serialization formats of [`AccountIdPrefix`] and [`AccountId`][id] are compatible. In
/// particular, a prefix can be deserialized from the serialized bytes of a full id.
///
/// [id]: crate::account::AccountId
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AccountIdPrefix {
    V1(AccountIdPrefixV1),
}

impl AccountIdPrefix {
    // CONSTANTS
    // --------------------------------------------------------------------------------------------

    /// The serialized size of an [`AccountIdPrefix`] in bytes.
    pub const SERIALIZED_SIZE: usize = 8;

    // CONSTRUCTORS
    // --------------------------------------------------------------------------------------------

    /// Constructs a new [`AccountIdPrefix`] from the given `prefix` without checking its
    /// validity.
    ///
    /// # Warning
    ///
    /// Validity of the ID prefix must be ensured by the caller. An invalid ID may lead to panics.
    ///
    /// # Panics
    ///
    /// Panics if the prefix does not contain a known account ID version.
    ///
    /// If `debug_assertions` are enabled (e.g. in debug mode), this function panics if the given
    /// felt is invalid according to the constraints in the [`AccountId`](crate::account::AccountId)
    /// documentation.
    pub fn new_unchecked(prefix: Felt) -> Self {
        // The prefix contains the metadata.
        // If we add more versions in the future, we may need to generalize this.
        match v1::extract_version(prefix.as_canonical_u64())
            .expect("prefix should contain a valid account ID version")
        {
            AccountIdVersion::Version1 => Self::V1(AccountIdPrefixV1::new_unchecked(prefix)),
        }
    }

    /// Constructs a new [`AccountIdPrefix`] from the given `prefix` and checks its validity.
    ///
    /// # Errors
    ///
    /// Returns an error if any of the ID constraints are not met. See the [constraints
    /// documentation](super::AccountId#constraints) for details.
    pub fn new(prefix: Felt) -> Result<Self, AccountIdError> {
        // The prefix contains the metadata.
        // If we add more versions in the future, we may need to generalize this.
        match v1::extract_version(prefix.as_canonical_u64())? {
            AccountIdVersion::Version1 => AccountIdPrefixV1::new(prefix).map(Self::V1),
        }
    }

    // PUBLIC ACCESSORS
    // --------------------------------------------------------------------------------------------

    /// Returns the [`Felt`] that represents this prefix.
    pub const fn as_felt(&self) -> Felt {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.as_felt(),
        }
    }

    /// Returns the prefix as a [`u64`].
    pub fn as_u64(&self) -> u64 {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.as_u64(),
        }
    }

    /// Returns the account type of this account ID.
    pub fn account_type(&self) -> AccountType {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.account_type(),
        }
    }

    /// Returns `true` if the account type is [`AccountType::Public`], `false` otherwise.
    pub fn is_public(&self) -> bool {
        self.account_type().is_public()
    }

    /// Returns `true` if self is a private account, `false` otherwise.
    pub fn is_private(&self) -> bool {
        self.account_type().is_private()
    }

    /// Returns the version of this account ID.
    pub fn version(&self) -> AccountIdVersion {
        match self {
            AccountIdPrefix::V1(_) => AccountIdVersion::Version1,
        }
    }

    /// Returns the prefix as a big-endian, hex-encoded string.
    pub fn to_hex(self) -> String {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.to_hex(),
        }
    }
}

// CONVERSIONS FROM ACCOUNT ID PREFIX
// ================================================================================================

impl From<AccountIdPrefixV1> for AccountIdPrefix {
    fn from(id: AccountIdPrefixV1) -> Self {
        Self::V1(id)
    }
}

impl From<AccountIdPrefix> for Felt {
    fn from(id: AccountIdPrefix) -> Self {
        match id {
            AccountIdPrefix::V1(id_prefix) => id_prefix.into(),
        }
    }
}

impl From<AccountIdPrefix> for [u8; 8] {
    fn from(id: AccountIdPrefix) -> Self {
        match id {
            AccountIdPrefix::V1(id_prefix) => id_prefix.into(),
        }
    }
}

impl From<AccountIdPrefix> for u64 {
    fn from(id: AccountIdPrefix) -> Self {
        match id {
            AccountIdPrefix::V1(id_prefix) => id_prefix.into(),
        }
    }
}

// CONVERSIONS TO ACCOUNT ID PREFIX
// ================================================================================================

impl TryFrom<[u8; 8]> for AccountIdPrefix {
    type Error = AccountIdError;

    /// Tries to convert a byte array in big-endian order to an [`AccountIdPrefix`].
    ///
    /// # Errors
    ///
    /// Returns an error if any of the ID constraints are not met. See the [constraints
    /// documentation](super::AccountId#constraints) for details.
    fn try_from(value: [u8; 8]) -> Result<Self, Self::Error> {
        // The least significant byte of the ID prefix contains the metadata.
        let metadata_byte = value[7];
        // We only have one supported version for now, so we use the extractor from that version.
        // If we add more versions in the future, we may need to generalize this.
        let version = v1::extract_version(metadata_byte as u64)?;

        match version {
            AccountIdVersion::Version1 => AccountIdPrefixV1::try_from(value).map(Self::V1),
        }
    }
}

impl TryFrom<u64> for AccountIdPrefix {
    type Error = AccountIdError;

    /// Tries to convert a `u64` into an [`AccountIdPrefix`].
    ///
    /// # Errors
    ///
    /// Returns an error if any of the ID constraints are not met. See the [constraints
    /// documentation](super::AccountId#constraints) for details.
    fn try_from(value: u64) -> Result<Self, Self::Error> {
        let element = Felt::try_from(value).map_err(|err| {
            AccountIdError::AccountIdInvalidPrefixFieldElement(DeserializationError::InvalidValue(
                err.to_string(),
            ))
        })?;
        Self::new(element)
    }
}

impl TryFrom<Felt> for AccountIdPrefix {
    type Error = AccountIdError;

    /// Returns an [`AccountIdPrefix`] instantiated with the provided field element.
    ///
    /// # Errors
    ///
    /// Returns an error if any of the ID constraints are not met. See the [constraints
    /// documentation](super::AccountId#constraints) for details.
    fn try_from(element: Felt) -> Result<Self, Self::Error> {
        Self::new(element)
    }
}

// COMMON TRAIT IMPLS
// ================================================================================================

impl PartialOrd for AccountIdPrefix {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for AccountIdPrefix {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        u64::from(*self).cmp(&u64::from(*other))
    }
}

impl fmt::Display for AccountIdPrefix {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_hex())
    }
}

// SERIALIZATION
// ================================================================================================

impl Serializable for AccountIdPrefix {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.write_into(target),
        }
    }

    fn get_size_hint(&self) -> usize {
        match self {
            AccountIdPrefix::V1(id_prefix) => id_prefix.get_size_hint(),
        }
    }
}

impl Deserializable for AccountIdPrefix {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        <[u8; 8]>::read_from(source)?
            .try_into()
            .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
    }
}