Skip to main content

linera_base/
identifiers.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core identifiers used by the Linera protocol.
5
6use std::{
7    fmt,
8    hash::{Hash, Hasher},
9    marker::PhantomData,
10};
11
12use allocative::Allocative;
13#[cfg(with_revm)]
14use alloy_primitives::{Address, B256};
15use anyhow::{anyhow, Context};
16use async_graphql::{InputObject, SimpleObject};
17use custom_debug_derive::Debug;
18use derive_more::{Display, FromStr};
19use linera_witty::{WitLoad, WitStore, WitType};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21
22use crate::{
23    bcs_scalar,
24    crypto::{
25        AccountPublicKey, CryptoError, CryptoHash, Ed25519PublicKey, EvmPublicKey,
26        Secp256k1PublicKey,
27    },
28    data_types::{BlobContent, ChainDescription},
29    doc_scalar, hex_debug,
30    vm::VmRuntime,
31};
32
33/// An account owner.
34#[derive(
35    Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, WitLoad, WitStore, WitType, Allocative,
36)]
37#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
38// TODO(#5166) we can be more specific here
39#[cfg_attr(
40    web,
41    derive(tsify::Tsify),
42    tsify(from_wasm_abi, into_wasm_abi, type = "string")
43)]
44pub enum AccountOwner {
45    /// Short addresses reserved for the protocol.
46    Reserved(u8),
47    /// 32-byte account address.
48    Address32(CryptoHash),
49    /// 20-byte account EVM-compatible address.
50    Address20([u8; 20]),
51}
52
53impl fmt::Debug for AccountOwner {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::Reserved(byte) => f.debug_tuple("Reserved").field(byte).finish(),
57            Self::Address32(hash) => write!(f, "Address32({hash:?})"),
58            Self::Address20(bytes) => write!(f, "Address20({})", hex::encode(bytes)),
59        }
60    }
61}
62
63impl AccountOwner {
64    /// Returns the default chain address.
65    pub const CHAIN: AccountOwner = AccountOwner::Reserved(0);
66
67    /// Tests if the account is the chain address.
68    pub fn is_chain(&self) -> bool {
69        self == &AccountOwner::CHAIN
70    }
71
72    /// The size of the `AccountOwner`.
73    pub fn size(&self) -> u32 {
74        match self {
75            AccountOwner::Reserved(_) => 1,
76            AccountOwner::Address32(_) => 32,
77            AccountOwner::Address20(_) => 20,
78        }
79    }
80
81    /// Gets the EVM address if possible
82    #[cfg(with_revm)]
83    pub fn to_evm_address(&self) -> Option<Address> {
84        match self {
85            AccountOwner::Address20(address) => Some(Address::from(address)),
86            _ => None,
87        }
88    }
89}
90
91impl From<[u8; 32]> for AccountOwner {
92    /// Converts a 32-byte array to an `AccountOwner`.
93    ///
94    /// If the first 12 bytes are zero, the remaining 20 bytes are treated as an
95    /// EVM-compatible `Address20`. Otherwise, the full 32 bytes become an `Address32`.
96    fn from(bytes: [u8; 32]) -> Self {
97        if bytes[..12].iter().all(|&b| b == 0) {
98            let mut addr = [0u8; 20];
99            addr.copy_from_slice(&bytes[12..]);
100            AccountOwner::Address20(addr)
101        } else {
102            AccountOwner::Address32(CryptoHash::from(bytes))
103        }
104    }
105}
106
107#[cfg(with_testing)]
108impl From<CryptoHash> for AccountOwner {
109    fn from(address: CryptoHash) -> Self {
110        AccountOwner::Address32(address)
111    }
112}
113
114/// A system account.
115#[derive(
116    Allocative,
117    Debug,
118    PartialEq,
119    Eq,
120    Hash,
121    Copy,
122    Clone,
123    Serialize,
124    Deserialize,
125    WitLoad,
126    WitStore,
127    WitType,
128)]
129#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
130pub struct Account {
131    /// The chain of the account.
132    pub chain_id: ChainId,
133    /// The owner of the account, or `None` for the chain balance.
134    pub owner: AccountOwner,
135}
136
137impl Account {
138    /// Creates a new [`Account`] with the given chain ID and owner.
139    pub fn new(chain_id: ChainId, owner: AccountOwner) -> Self {
140        Self { chain_id, owner }
141    }
142
143    /// Creates an [`Account`] representing the balance shared by a chain's owners.
144    pub fn chain(chain_id: ChainId) -> Self {
145        Account {
146            chain_id,
147            owner: AccountOwner::CHAIN,
148        }
149    }
150
151    /// An address used exclusively for tests
152    #[cfg(with_testing)]
153    pub fn burn_address(chain_id: ChainId) -> Self {
154        let hash = CryptoHash::test_hash("burn");
155        Account {
156            chain_id,
157            owner: hash.into(),
158        }
159    }
160}
161
162impl fmt::Display for Account {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(f, "{}@{}", self.owner, self.chain_id)
165    }
166}
167
168impl std::str::FromStr for Account {
169    type Err = anyhow::Error;
170
171    fn from_str(string: &str) -> Result<Self, Self::Err> {
172        if let Some((owner_string, chain_string)) = string.rsplit_once('@') {
173            let owner = owner_string.parse::<AccountOwner>()?;
174            let chain_id = chain_string.parse()?;
175            Ok(Account::new(chain_id, owner))
176        } else {
177            let chain_id = string
178                .parse()
179                .context("Expecting an account formatted as `chain-id` or `owner@chain-id`")?;
180            Ok(Account::chain(chain_id))
181        }
182    }
183}
184
185/// The unique identifier (UID) of a chain. This is currently computed as the hash value
186/// of a [`ChainDescription`].
187#[derive(
188    Eq,
189    PartialEq,
190    Ord,
191    PartialOrd,
192    Copy,
193    Clone,
194    Hash,
195    Serialize,
196    Deserialize,
197    WitLoad,
198    WitStore,
199    WitType,
200    Allocative,
201)]
202#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
203#[cfg_attr(with_testing, derive(Default))]
204#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
205pub struct ChainId(pub CryptoHash);
206
207/// The type of the blob.
208/// Should be a 1:1 mapping of the types in `Blob`.
209#[derive(
210    Eq,
211    PartialEq,
212    Ord,
213    PartialOrd,
214    Clone,
215    Copy,
216    Hash,
217    Debug,
218    Serialize,
219    Deserialize,
220    WitType,
221    WitStore,
222    WitLoad,
223    Default,
224    Allocative,
225)]
226#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
227pub enum BlobType {
228    /// A generic data blob.
229    #[default]
230    Data,
231    /// A blob containing compressed contract Wasm bytecode.
232    ContractBytecode,
233    /// A blob containing compressed service Wasm bytecode.
234    ServiceBytecode,
235    /// A blob containing compressed EVM bytecode.
236    EvmBytecode,
237    /// A blob containing an application description.
238    ApplicationDescription,
239    /// A blob containing a committee of validators.
240    Committee,
241    /// A blob containing a chain description.
242    ChainDescription,
243}
244
245impl BlobType {
246    /// Returns whether the blob is of [`BlobType::Committee`] variant.
247    pub fn is_committee_blob(&self) -> bool {
248        match self {
249            BlobType::Data
250            | BlobType::ContractBytecode
251            | BlobType::ServiceBytecode
252            | BlobType::EvmBytecode
253            | BlobType::ApplicationDescription
254            | BlobType::ChainDescription => false,
255            BlobType::Committee => true,
256        }
257    }
258}
259
260impl fmt::Display for BlobType {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        write!(f, "{self:?}")
263    }
264}
265
266impl std::str::FromStr for BlobType {
267    type Err = anyhow::Error;
268
269    fn from_str(s: &str) -> Result<Self, Self::Err> {
270        serde_json::from_str(&format!("\"{s}\"")).with_context(|| format!("Invalid BlobType: {s}"))
271    }
272}
273
274/// A content-addressed blob ID i.e. the hash of the `BlobContent`.
275#[derive(
276    Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, WitType, WitStore, WitLoad, Allocative,
277)]
278#[cfg_attr(with_testing, derive(test_strategy::Arbitrary, Default))]
279pub struct BlobId {
280    /// The type of the blob.
281    pub blob_type: BlobType,
282    /// The hash of the blob.
283    pub hash: CryptoHash,
284}
285
286impl BlobId {
287    /// Creates a new `BlobId` from a `CryptoHash`. This must be a hash of the blob's bytes!
288    pub fn new(hash: CryptoHash, blob_type: BlobType) -> Self {
289        Self { hash, blob_type }
290    }
291}
292
293impl fmt::Display for BlobId {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        write!(f, "{}:{}", self.blob_type, self.hash)?;
296        Ok(())
297    }
298}
299
300impl std::str::FromStr for BlobId {
301    type Err = anyhow::Error;
302
303    fn from_str(s: &str) -> Result<Self, Self::Err> {
304        let parts = s.split(':').collect::<Vec<_>>();
305        if parts.len() == 2 {
306            let blob_type = BlobType::from_str(parts[0]).context("Invalid BlobType!")?;
307            Ok(BlobId {
308                hash: CryptoHash::from_str(parts[1]).context("Invalid hash!")?,
309                blob_type,
310            })
311        } else {
312            Err(anyhow!("Invalid blob ID: {s}"))
313        }
314    }
315}
316
317#[derive(Serialize, Deserialize)]
318#[serde(rename = "BlobId")]
319struct BlobIdHelper {
320    hash: CryptoHash,
321    blob_type: BlobType,
322}
323
324impl Serialize for BlobId {
325    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
326    where
327        S: Serializer,
328    {
329        if serializer.is_human_readable() {
330            serializer.serialize_str(&self.to_string())
331        } else {
332            let helper = BlobIdHelper {
333                hash: self.hash,
334                blob_type: self.blob_type,
335            };
336            helper.serialize(serializer)
337        }
338    }
339}
340
341impl<'a> Deserialize<'a> for BlobId {
342    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343    where
344        D: Deserializer<'a>,
345    {
346        if deserializer.is_human_readable() {
347            let s = String::deserialize(deserializer)?;
348            Self::from_str(&s).map_err(serde::de::Error::custom)
349        } else {
350            let helper = BlobIdHelper::deserialize(deserializer)?;
351            Ok(BlobId::new(helper.hash, helper.blob_type))
352        }
353    }
354}
355
356/// Hash of a data blob.
357#[derive(
358    Eq, Hash, PartialEq, Debug, Serialize, Deserialize, Clone, Copy, WitType, WitLoad, WitStore,
359)]
360pub struct DataBlobHash(pub CryptoHash);
361
362impl From<DataBlobHash> for BlobId {
363    fn from(hash: DataBlobHash) -> BlobId {
364        BlobId::new(hash.0, BlobType::Data)
365    }
366}
367
368// TODO(#5166) we can be more specific here (and also more generic)
369#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
370const _: &str = "export type ApplicationId = string;";
371
372/// A unique identifier for a user application from a blob.
373#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
374#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
375#[allocative(bound = "A")]
376pub struct ApplicationId<A = ()> {
377    /// The hash of the `ApplicationDescription` this refers to.
378    pub application_description_hash: CryptoHash,
379    #[witty(skip)]
380    #[debug(skip)]
381    #[allocative(skip)]
382    phantom: PhantomData<A>,
383}
384
385/// A unique identifier for an application.
386#[derive(
387    Eq,
388    PartialEq,
389    Ord,
390    PartialOrd,
391    Copy,
392    Clone,
393    Hash,
394    Debug,
395    Serialize,
396    Deserialize,
397    WitLoad,
398    WitStore,
399    WitType,
400    Allocative,
401)]
402#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
403pub enum GenericApplicationId {
404    /// The system application.
405    System,
406    /// A user application.
407    User(ApplicationId),
408}
409
410impl fmt::Display for GenericApplicationId {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        match self {
413            GenericApplicationId::System => Display::fmt("System", f),
414            GenericApplicationId::User(application_id) => {
415                Display::fmt("User:", f)?;
416                Display::fmt(&application_id, f)
417            }
418        }
419    }
420}
421
422impl std::str::FromStr for GenericApplicationId {
423    type Err = anyhow::Error;
424
425    fn from_str(s: &str) -> Result<Self, Self::Err> {
426        if s == "System" {
427            return Ok(GenericApplicationId::System);
428        }
429        if let Some(result) = s.strip_prefix("User:") {
430            let application_id = ApplicationId::from_str(result)?;
431            return Ok(GenericApplicationId::User(application_id));
432        }
433        Err(anyhow!("Invalid parsing of GenericApplicationId"))
434    }
435}
436
437impl<A> From<ApplicationId<A>> for AccountOwner {
438    fn from(app_id: ApplicationId<A>) -> Self {
439        AccountOwner::Address32(app_id.application_description_hash)
440    }
441}
442
443impl From<AccountPublicKey> for AccountOwner {
444    fn from(public_key: AccountPublicKey) -> Self {
445        match public_key {
446            AccountPublicKey::Ed25519(public_key) => public_key.into(),
447            AccountPublicKey::Secp256k1(public_key) => public_key.into(),
448            AccountPublicKey::EvmSecp256k1(public_key) => public_key.into(),
449        }
450    }
451}
452
453impl From<ApplicationId> for GenericApplicationId {
454    fn from(application_id: ApplicationId) -> Self {
455        GenericApplicationId::User(application_id)
456    }
457}
458
459impl From<Secp256k1PublicKey> for AccountOwner {
460    fn from(public_key: Secp256k1PublicKey) -> Self {
461        AccountOwner::Address32(CryptoHash::new(&public_key))
462    }
463}
464
465impl From<Ed25519PublicKey> for AccountOwner {
466    fn from(public_key: Ed25519PublicKey) -> Self {
467        AccountOwner::Address32(CryptoHash::new(&public_key))
468    }
469}
470
471impl From<EvmPublicKey> for AccountOwner {
472    fn from(public_key: EvmPublicKey) -> Self {
473        AccountOwner::Address20(alloy_primitives::Address::from_public_key(&public_key.0).into())
474    }
475}
476
477/// A unique identifier for a module.
478#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
479#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
480pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
481    /// The hash of the blob containing the contract bytecode.
482    pub contract_blob_hash: CryptoHash,
483    /// The hash of the blob containing the service bytecode.
484    pub service_blob_hash: CryptoHash,
485    /// The virtual machine being used.
486    pub vm_runtime: VmRuntime,
487    #[witty(skip)]
488    #[debug(skip)]
489    phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
490}
491
492/// The name of an event stream.
493// TODO(#5667): Enforce length limit for stream names.
494#[derive(
495    Clone,
496    Debug,
497    Eq,
498    Hash,
499    Ord,
500    PartialEq,
501    PartialOrd,
502    Serialize,
503    Deserialize,
504    WitLoad,
505    WitStore,
506    WitType,
507    Allocative,
508)]
509pub struct StreamName(
510    #[serde(with = "serde_bytes")]
511    #[debug(with = "hex_debug")]
512    pub Vec<u8>,
513);
514
515impl<T> From<T> for StreamName
516where
517    T: Into<Vec<u8>>,
518{
519    fn from(name: T) -> Self {
520        StreamName(name.into())
521    }
522}
523
524impl fmt::Display for StreamName {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        Display::fmt(&hex::encode(&self.0), f)
527    }
528}
529
530impl std::str::FromStr for StreamName {
531    type Err = anyhow::Error;
532
533    fn from_str(s: &str) -> Result<Self, Self::Err> {
534        let vec = hex::decode(s)?;
535        Ok(StreamName(vec))
536    }
537}
538
539/// An event stream ID.
540#[derive(
541    Clone,
542    Debug,
543    Eq,
544    Hash,
545    Ord,
546    PartialEq,
547    PartialOrd,
548    WitLoad,
549    WitStore,
550    WitType,
551    SimpleObject,
552    InputObject,
553    Allocative,
554)]
555#[graphql(input_name = "StreamIdInput")]
556pub struct StreamId {
557    /// The application that can add events to this stream.
558    pub application_id: GenericApplicationId,
559    /// The name of this stream: an application can have multiple streams with different names.
560    pub stream_name: StreamName,
561}
562
563impl serde::Serialize for StreamId {
564    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
565    where
566        S: serde::ser::Serializer,
567    {
568        if serializer.is_human_readable() {
569            serializer.serialize_str(&self.to_string())
570        } else {
571            use serde::ser::SerializeStruct;
572            let mut state = serializer.serialize_struct("StreamId", 2)?;
573            state.serialize_field("application_id", &self.application_id)?;
574            state.serialize_field("stream_name", &self.stream_name)?;
575            state.end()
576        }
577    }
578}
579
580impl<'de> serde::Deserialize<'de> for StreamId {
581    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
582    where
583        D: serde::de::Deserializer<'de>,
584    {
585        if deserializer.is_human_readable() {
586            let s = String::deserialize(deserializer)?;
587            Self::from_str(&s).map_err(serde::de::Error::custom)
588        } else {
589            #[derive(serde::Deserialize)]
590            #[serde(rename = "StreamId")]
591            struct StreamIdHelper {
592                application_id: GenericApplicationId,
593                stream_name: StreamName,
594            }
595            let helper = StreamIdHelper::deserialize(deserializer)?;
596            Ok(StreamId {
597                application_id: helper.application_id,
598                stream_name: helper.stream_name,
599            })
600        }
601    }
602}
603
604impl StreamId {
605    /// Creates a system stream ID with the given name.
606    pub fn system(name: impl Into<StreamName>) -> Self {
607        StreamId {
608            application_id: GenericApplicationId::System,
609            stream_name: name.into(),
610        }
611    }
612}
613
614/// The result of an `events_from_index`.
615#[derive(
616    Debug,
617    Eq,
618    PartialEq,
619    Ord,
620    PartialOrd,
621    Clone,
622    Hash,
623    Serialize,
624    Deserialize,
625    WitLoad,
626    WitStore,
627    WitType,
628    SimpleObject,
629)]
630pub struct IndexAndEvent {
631    /// The index of the found event.
632    pub index: u32,
633    /// The event being returned.
634    pub event: Vec<u8>,
635}
636
637impl fmt::Display for StreamId {
638    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639        Display::fmt(&self.application_id, f)?;
640        Display::fmt(":", f)?;
641        Display::fmt(&self.stream_name, f)
642    }
643}
644
645impl std::str::FromStr for StreamId {
646    type Err = anyhow::Error;
647
648    fn from_str(s: &str) -> Result<Self, Self::Err> {
649        let parts = s.rsplit_once(":");
650        if let Some((part0, part1)) = parts {
651            let application_id =
652                GenericApplicationId::from_str(part0).context("Invalid GenericApplicationId!")?;
653            let stream_name = StreamName::from_str(part1).context("Invalid StreamName!")?;
654            Ok(StreamId {
655                application_id,
656                stream_name,
657            })
658        } else {
659            Err(anyhow!("Invalid blob ID: {s}"))
660        }
661    }
662}
663
664/// An event identifier.
665#[derive(
666    Debug,
667    PartialEq,
668    Eq,
669    Hash,
670    Clone,
671    Serialize,
672    Deserialize,
673    WitLoad,
674    WitStore,
675    WitType,
676    SimpleObject,
677    Allocative,
678)]
679pub struct EventId {
680    /// The ID of the chain that generated this event.
681    pub chain_id: ChainId,
682    /// The ID of the stream this event belongs to.
683    pub stream_id: StreamId,
684    /// The event index, i.e. the number of events in the stream before this one.
685    pub index: u32,
686}
687
688impl StreamName {
689    /// Turns the stream name into bytes.
690    pub fn into_bytes(self) -> Vec<u8> {
691        self.0
692    }
693}
694
695// Cannot use #[derive(Clone)] because it requires `A: Clone`.
696impl<Abi, Parameters, InstantiationArgument> Clone
697    for ModuleId<Abi, Parameters, InstantiationArgument>
698{
699    fn clone(&self) -> Self {
700        *self
701    }
702}
703
704impl<Abi, Parameters, InstantiationArgument> Copy
705    for ModuleId<Abi, Parameters, InstantiationArgument>
706{
707}
708
709impl<Abi, Parameters, InstantiationArgument> PartialEq
710    for ModuleId<Abi, Parameters, InstantiationArgument>
711{
712    fn eq(&self, other: &Self) -> bool {
713        let ModuleId {
714            contract_blob_hash,
715            service_blob_hash,
716            vm_runtime,
717            phantom: _,
718        } = other;
719        self.contract_blob_hash == *contract_blob_hash
720            && self.service_blob_hash == *service_blob_hash
721            && self.vm_runtime == *vm_runtime
722    }
723}
724
725impl<Abi, Parameters, InstantiationArgument> Eq
726    for ModuleId<Abi, Parameters, InstantiationArgument>
727{
728}
729
730impl<Abi, Parameters, InstantiationArgument> PartialOrd
731    for ModuleId<Abi, Parameters, InstantiationArgument>
732{
733    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
734        Some(self.cmp(other))
735    }
736}
737
738impl<Abi, Parameters, InstantiationArgument> Ord
739    for ModuleId<Abi, Parameters, InstantiationArgument>
740{
741    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
742        let ModuleId {
743            contract_blob_hash,
744            service_blob_hash,
745            vm_runtime,
746            phantom: _,
747        } = other;
748        (
749            self.contract_blob_hash,
750            self.service_blob_hash,
751            self.vm_runtime,
752        )
753            .cmp(&(*contract_blob_hash, *service_blob_hash, *vm_runtime))
754    }
755}
756
757impl<Abi, Parameters, InstantiationArgument> Hash
758    for ModuleId<Abi, Parameters, InstantiationArgument>
759{
760    fn hash<H: Hasher>(&self, state: &mut H) {
761        let ModuleId {
762            contract_blob_hash: contract_blob_id,
763            service_blob_hash: service_blob_id,
764            vm_runtime: vm_runtime_id,
765            phantom: _,
766        } = self;
767        contract_blob_id.hash(state);
768        service_blob_id.hash(state);
769        vm_runtime_id.hash(state);
770    }
771}
772
773#[derive(Serialize, Deserialize)]
774#[serde(rename = "ModuleId")]
775struct SerializableModuleId {
776    contract_blob_hash: CryptoHash,
777    service_blob_hash: CryptoHash,
778    vm_runtime: VmRuntime,
779}
780
781impl<Abi, Parameters, InstantiationArgument> Serialize
782    for ModuleId<Abi, Parameters, InstantiationArgument>
783{
784    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
785    where
786        S: serde::ser::Serializer,
787    {
788        let serializable_module_id = SerializableModuleId {
789            contract_blob_hash: self.contract_blob_hash,
790            service_blob_hash: self.service_blob_hash,
791            vm_runtime: self.vm_runtime,
792        };
793        if serializer.is_human_readable() {
794            let bytes =
795                bcs::to_bytes(&serializable_module_id).map_err(serde::ser::Error::custom)?;
796            serializer.serialize_str(&hex::encode(bytes))
797        } else {
798            SerializableModuleId::serialize(&serializable_module_id, serializer)
799        }
800    }
801}
802
803impl<'de, Abi, Parameters, InstantiationArgument> Deserialize<'de>
804    for ModuleId<Abi, Parameters, InstantiationArgument>
805{
806    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
807    where
808        D: serde::de::Deserializer<'de>,
809    {
810        if deserializer.is_human_readable() {
811            let s = String::deserialize(deserializer)?;
812            let module_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
813            let serializable_module_id: SerializableModuleId =
814                bcs::from_bytes(&module_id_bytes).map_err(serde::de::Error::custom)?;
815            Ok(ModuleId {
816                contract_blob_hash: serializable_module_id.contract_blob_hash,
817                service_blob_hash: serializable_module_id.service_blob_hash,
818                vm_runtime: serializable_module_id.vm_runtime,
819                phantom: PhantomData,
820            })
821        } else {
822            let serializable_module_id = SerializableModuleId::deserialize(deserializer)?;
823            Ok(ModuleId {
824                contract_blob_hash: serializable_module_id.contract_blob_hash,
825                service_blob_hash: serializable_module_id.service_blob_hash,
826                vm_runtime: serializable_module_id.vm_runtime,
827                phantom: PhantomData,
828            })
829        }
830    }
831}
832
833impl ModuleId {
834    /// Creates a module ID from contract/service hashes and the VM runtime to use.
835    pub fn new(
836        contract_blob_hash: CryptoHash,
837        service_blob_hash: CryptoHash,
838        vm_runtime: VmRuntime,
839    ) -> Self {
840        ModuleId {
841            contract_blob_hash,
842            service_blob_hash,
843            vm_runtime,
844            phantom: PhantomData,
845        }
846    }
847
848    /// Specializes a module ID for a given ABI.
849    pub fn with_abi<Abi, Parameters, InstantiationArgument>(
850        self,
851    ) -> ModuleId<Abi, Parameters, InstantiationArgument> {
852        ModuleId {
853            contract_blob_hash: self.contract_blob_hash,
854            service_blob_hash: self.service_blob_hash,
855            vm_runtime: self.vm_runtime,
856            phantom: PhantomData,
857        }
858    }
859
860    /// Gets the `BlobId` of the contract
861    pub fn contract_bytecode_blob_id(&self) -> BlobId {
862        match self.vm_runtime {
863            VmRuntime::Wasm => BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
864            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
865        }
866    }
867
868    /// Gets the `BlobId` of the service
869    pub fn service_bytecode_blob_id(&self) -> BlobId {
870        match self.vm_runtime {
871            VmRuntime::Wasm => BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
872            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
873        }
874    }
875
876    /// Gets all bytecode `BlobId`s of the module
877    pub fn bytecode_blob_ids(&self) -> Vec<BlobId> {
878        match self.vm_runtime {
879            VmRuntime::Wasm => vec![
880                BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
881                BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
882            ],
883            VmRuntime::Evm => vec![BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode)],
884        }
885    }
886}
887
888impl<Abi, Parameters, InstantiationArgument> ModuleId<Abi, Parameters, InstantiationArgument> {
889    /// Forgets the ABI of a module ID (if any).
890    pub fn forget_abi(self) -> ModuleId {
891        ModuleId {
892            contract_blob_hash: self.contract_blob_hash,
893            service_blob_hash: self.service_blob_hash,
894            vm_runtime: self.vm_runtime,
895            phantom: PhantomData,
896        }
897    }
898}
899
900// Cannot use #[derive(Clone)] because it requires `A: Clone`.
901impl<A> Clone for ApplicationId<A> {
902    fn clone(&self) -> Self {
903        *self
904    }
905}
906
907impl<A> Copy for ApplicationId<A> {}
908
909impl<A: PartialEq> PartialEq for ApplicationId<A> {
910    fn eq(&self, other: &Self) -> bool {
911        self.application_description_hash == other.application_description_hash
912    }
913}
914
915impl<A: Eq> Eq for ApplicationId<A> {}
916
917impl<A: PartialOrd> PartialOrd for ApplicationId<A> {
918    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
919        self.application_description_hash
920            .partial_cmp(&other.application_description_hash)
921    }
922}
923
924impl<A: Ord> Ord for ApplicationId<A> {
925    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
926        self.application_description_hash
927            .cmp(&other.application_description_hash)
928    }
929}
930
931impl<A> Hash for ApplicationId<A> {
932    fn hash<H: Hasher>(&self, state: &mut H) {
933        self.application_description_hash.hash(state);
934    }
935}
936
937#[derive(Serialize, Deserialize)]
938#[serde(rename = "ApplicationId")]
939struct SerializableApplicationId {
940    pub application_description_hash: CryptoHash,
941}
942
943impl<A> Serialize for ApplicationId<A> {
944    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
945    where
946        S: serde::ser::Serializer,
947    {
948        if serializer.is_human_readable() {
949            let bytes = bcs::to_bytes(&SerializableApplicationId {
950                application_description_hash: self.application_description_hash,
951            })
952            .map_err(serde::ser::Error::custom)?;
953            serializer.serialize_str(&hex::encode(bytes))
954        } else {
955            SerializableApplicationId::serialize(
956                &SerializableApplicationId {
957                    application_description_hash: self.application_description_hash,
958                },
959                serializer,
960            )
961        }
962    }
963}
964
965impl<'de, A> Deserialize<'de> for ApplicationId<A> {
966    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
967    where
968        D: serde::de::Deserializer<'de>,
969    {
970        if deserializer.is_human_readable() {
971            let s = String::deserialize(deserializer)?;
972            let application_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
973            let application_id: SerializableApplicationId =
974                bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?;
975            Ok(ApplicationId {
976                application_description_hash: application_id.application_description_hash,
977                phantom: PhantomData,
978            })
979        } else {
980            let value = SerializableApplicationId::deserialize(deserializer)?;
981            Ok(ApplicationId {
982                application_description_hash: value.application_description_hash,
983                phantom: PhantomData,
984            })
985        }
986    }
987}
988
989impl ApplicationId {
990    /// Creates an application ID from the application description hash.
991    pub fn new(application_description_hash: CryptoHash) -> Self {
992        ApplicationId {
993            application_description_hash,
994            phantom: PhantomData,
995        }
996    }
997
998    /// Converts the application ID to the ID of the blob containing the
999    /// `ApplicationDescription`.
1000    pub fn description_blob_id(self) -> BlobId {
1001        BlobId::new(
1002            self.application_description_hash,
1003            BlobType::ApplicationDescription,
1004        )
1005    }
1006
1007    /// Specializes an application ID for a given ABI.
1008    pub fn with_abi<A>(self) -> ApplicationId<A> {
1009        ApplicationId {
1010            application_description_hash: self.application_description_hash,
1011            phantom: PhantomData,
1012        }
1013    }
1014}
1015
1016impl<A> ApplicationId<A> {
1017    /// Forgets the ABI of an application ID (if any).
1018    pub fn forget_abi(self) -> ApplicationId {
1019        ApplicationId {
1020            application_description_hash: self.application_description_hash,
1021            phantom: PhantomData,
1022        }
1023    }
1024}
1025
1026#[cfg(with_revm)]
1027impl<A> ApplicationId<A> {
1028    /// Converts the `ApplicationId` into an Ethereum Address.
1029    pub fn evm_address(&self) -> Address {
1030        let bytes = self.application_description_hash.as_bytes();
1031        let bytes = bytes.0.as_ref();
1032        Address::from_slice(&bytes[0..20])
1033    }
1034
1035    /// Converts the `ApplicationId` into an Ethereum-compatible 32-byte array.
1036    pub fn bytes32(&self) -> B256 {
1037        *self.application_description_hash.as_bytes()
1038    }
1039
1040    /// Returns whether the `ApplicationId` is the one of an EVM application.
1041    pub fn is_evm(&self) -> bool {
1042        let bytes = self.application_description_hash.as_bytes();
1043        let bytes = bytes.0.as_ref();
1044        for byte in &bytes[20..] {
1045            if byte != &0 {
1046                return false;
1047            }
1048        }
1049        true
1050    }
1051}
1052
1053#[derive(Serialize, Deserialize)]
1054#[serde(rename = "AccountOwner")]
1055enum SerializableAccountOwner {
1056    Reserved(u8),
1057    Address32(CryptoHash),
1058    Address20([u8; 20]),
1059}
1060
1061impl Serialize for AccountOwner {
1062    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1063        if serializer.is_human_readable() {
1064            serializer.serialize_str(&self.to_string())
1065        } else {
1066            match self {
1067                AccountOwner::Reserved(value) => SerializableAccountOwner::Reserved(*value),
1068                AccountOwner::Address32(value) => SerializableAccountOwner::Address32(*value),
1069                AccountOwner::Address20(value) => SerializableAccountOwner::Address20(*value),
1070            }
1071            .serialize(serializer)
1072        }
1073    }
1074}
1075
1076impl<'de> Deserialize<'de> for AccountOwner {
1077    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1078        if deserializer.is_human_readable() {
1079            let s = String::deserialize(deserializer)?;
1080            let value = Self::from_str(&s).map_err(serde::de::Error::custom)?;
1081            Ok(value)
1082        } else {
1083            let value = SerializableAccountOwner::deserialize(deserializer)?;
1084            match value {
1085                SerializableAccountOwner::Reserved(value) => Ok(AccountOwner::Reserved(value)),
1086                SerializableAccountOwner::Address32(value) => Ok(AccountOwner::Address32(value)),
1087                SerializableAccountOwner::Address20(value) => Ok(AccountOwner::Address20(value)),
1088            }
1089        }
1090    }
1091}
1092
1093impl fmt::Display for AccountOwner {
1094    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095        match self {
1096            AccountOwner::Reserved(value) => {
1097                write!(f, "0x{}", hex::encode(&value.to_be_bytes()[..]))?
1098            }
1099            AccountOwner::Address32(value) => write!(f, "0x{value}")?,
1100            AccountOwner::Address20(value) => write!(f, "0x{}", hex::encode(&value[..]))?,
1101        };
1102
1103        Ok(())
1104    }
1105}
1106
1107impl std::str::FromStr for AccountOwner {
1108    type Err = anyhow::Error;
1109
1110    fn from_str(s: &str) -> Result<Self, Self::Err> {
1111        if let Some(s) = s.strip_prefix("0x") {
1112            if s.len() == 64 {
1113                if let Ok(hash) = CryptoHash::from_str(s) {
1114                    return Ok(AccountOwner::Address32(hash));
1115                }
1116            } else if s.len() == 40 {
1117                let address = hex::decode(s)?;
1118                if address.len() != 20 {
1119                    anyhow::bail!("Invalid address length: {s}");
1120                }
1121                let address = <[u8; 20]>::try_from(address.as_slice()).unwrap();
1122                return Ok(AccountOwner::Address20(address));
1123            }
1124            if s.len() == 2 {
1125                let bytes = hex::decode(s)?;
1126                if bytes.len() == 1 {
1127                    let value = u8::from_be_bytes(bytes.try_into().expect("one byte"));
1128                    return Ok(AccountOwner::Reserved(value));
1129                }
1130            }
1131        }
1132        anyhow::bail!("Invalid address value: {s}");
1133    }
1134}
1135
1136impl fmt::Display for ChainId {
1137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1138        Display::fmt(&self.0, f)
1139    }
1140}
1141
1142impl std::str::FromStr for ChainId {
1143    type Err = CryptoError;
1144
1145    fn from_str(s: &str) -> Result<Self, Self::Err> {
1146        Ok(ChainId(CryptoHash::from_str(s)?))
1147    }
1148}
1149
1150impl TryFrom<&[u8]> for ChainId {
1151    type Error = CryptoError;
1152
1153    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
1154        Ok(ChainId(CryptoHash::try_from(value)?))
1155    }
1156}
1157
1158impl fmt::Debug for ChainId {
1159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1160        write!(f, "{:?}", self.0)
1161    }
1162}
1163
1164impl<'a> From<&'a ChainDescription> for ChainId {
1165    fn from(description: &'a ChainDescription) -> Self {
1166        Self(CryptoHash::new(&BlobContent::new_chain_description(
1167            description,
1168        )))
1169    }
1170}
1171
1172impl From<ChainDescription> for ChainId {
1173    fn from(description: ChainDescription) -> Self {
1174        From::from(&description)
1175    }
1176}
1177
1178bcs_scalar!(ApplicationId, "A unique identifier for a user application");
1179doc_scalar!(DataBlobHash, "Hash of a Data Blob");
1180doc_scalar!(
1181    GenericApplicationId,
1182    "A unique identifier for a user application or for the system application"
1183);
1184bcs_scalar!(ModuleId, "A unique identifier for an application module");
1185doc_scalar!(
1186    ChainId,
1187    "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \
1188    ChainDescription."
1189);
1190doc_scalar!(StreamName, "The name of an event stream");
1191
1192doc_scalar!(
1193    AccountOwner,
1194    "A unique identifier for a user or an application."
1195);
1196doc_scalar!(Account, "An account");
1197doc_scalar!(
1198    BlobId,
1199    "A content-addressed blob ID i.e. the hash of the `BlobContent`"
1200);
1201
1202#[cfg(test)]
1203mod tests {
1204    use std::str::FromStr as _;
1205
1206    use assert_matches::assert_matches;
1207
1208    use super::{AccountOwner, BlobType};
1209    use crate::{
1210        data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp},
1211        identifiers::{ApplicationId, CryptoHash, GenericApplicationId, StreamId, StreamName},
1212        ownership::ChainOwnership,
1213    };
1214
1215    /// Verifies that the way of computing chain IDs doesn't change.
1216    #[test]
1217    fn chain_id_computing() {
1218        let example_chain_origin = ChainOrigin::Root(0);
1219        let example_chain_config = InitialChainConfig {
1220            epoch: Epoch::ZERO,
1221            ownership: ChainOwnership::single(AccountOwner::Reserved(0)),
1222            balance: Amount::ZERO,
1223            min_active_epoch: Epoch::ZERO,
1224            max_active_epoch: Epoch::ZERO,
1225            application_permissions: Default::default(),
1226        };
1227        let description = ChainDescription::new(
1228            example_chain_origin,
1229            example_chain_config,
1230            Timestamp::from(0),
1231        );
1232        assert_eq!(
1233            description.id().to_string(),
1234            "0b87a7cba23cf0d634a1f8eace084acb8fa110b8017db71a7f1bb159e4c752dd"
1235        );
1236    }
1237
1238    #[test]
1239    fn blob_types() {
1240        assert_eq!("ContractBytecode", BlobType::ContractBytecode.to_string());
1241        assert_eq!(
1242            BlobType::ContractBytecode,
1243            BlobType::from_str("ContractBytecode").unwrap()
1244        );
1245    }
1246
1247    #[test]
1248    fn addresses() {
1249        assert_eq!(&AccountOwner::Reserved(0).to_string(), "0x00");
1250        assert_eq!(AccountOwner::from_str("0x00").unwrap(), AccountOwner::CHAIN);
1251
1252        let address = AccountOwner::from_str("0x10").unwrap();
1253        assert_eq!(address, AccountOwner::Reserved(16));
1254        assert_eq!(address.to_string(), "0x10");
1255
1256        let address = AccountOwner::from_str(
1257            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844",
1258        )
1259        .unwrap();
1260        assert_matches!(address, AccountOwner::Address32(_));
1261        assert_eq!(
1262            address.to_string(),
1263            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1264        );
1265
1266        let address = AccountOwner::from_str("0x6E0ab7F37b667b7228D3a03116Ca21Be83213823").unwrap();
1267        assert_matches!(address, AccountOwner::Address20(_));
1268        assert_eq!(
1269            address.to_string(),
1270            "0x6e0ab7f37b667b7228d3a03116ca21be83213823"
1271        );
1272
1273        assert!(AccountOwner::from_str("0x5487b7").is_err());
1274        assert!(AccountOwner::from_str("0").is_err());
1275        assert!(AccountOwner::from_str(
1276            "5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1277        )
1278        .is_err());
1279    }
1280
1281    #[test]
1282    fn accounts() {
1283        use super::{Account, ChainId};
1284
1285        const CHAIN: &str = "76e3a8c7b2449e6bc238642ac68b4311a809cb57328bea0a1ef9122f08a0053d";
1286        const OWNER: &str = "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844";
1287
1288        let chain_id = ChainId::from_str(CHAIN).unwrap();
1289        let owner = AccountOwner::from_str(OWNER).unwrap();
1290
1291        // Chain-only account.
1292        let account = Account::from_str(CHAIN).unwrap();
1293        assert_eq!(
1294            account,
1295            Account::from_str(&format!("0x00@{CHAIN}")).unwrap()
1296        );
1297        assert_eq!(account, Account::chain(chain_id));
1298        assert_eq!(account.to_string(), format!("0x00@{CHAIN}"));
1299
1300        // Account with owner.
1301        let account = Account::from_str(&format!("{OWNER}@{CHAIN}")).unwrap();
1302        assert_eq!(account, Account::new(chain_id, owner));
1303        assert_eq!(account.to_string(), format!("{OWNER}@{CHAIN}"));
1304    }
1305
1306    #[test]
1307    fn stream_name() {
1308        let vec = vec![32, 54, 120, 234];
1309        let stream_name1 = StreamName(vec);
1310        let stream_name2 = StreamName::from_str(&format!("{stream_name1}")).unwrap();
1311        assert_eq!(stream_name1, stream_name2);
1312    }
1313
1314    fn test_generic_application_id(application_id: GenericApplicationId) {
1315        let application_id2 = GenericApplicationId::from_str(&format!("{application_id}")).unwrap();
1316        assert_eq!(application_id, application_id2);
1317    }
1318
1319    #[test]
1320    fn generic_application_id() {
1321        test_generic_application_id(GenericApplicationId::System);
1322        let hash = CryptoHash::test_hash("test case");
1323        let application_id = ApplicationId::new(hash);
1324        test_generic_application_id(GenericApplicationId::User(application_id));
1325    }
1326
1327    #[test]
1328    fn stream_id() {
1329        let hash = CryptoHash::test_hash("test case");
1330        let application_id = ApplicationId::new(hash);
1331        let application_id = GenericApplicationId::User(application_id);
1332        let vec = vec![32, 54, 120, 234];
1333        let stream_name = StreamName(vec);
1334
1335        let stream_id1 = StreamId {
1336            application_id,
1337            stream_name,
1338        };
1339        let stream_id2 = StreamId::from_str(&format!("{stream_id1}")).unwrap();
1340        assert_eq!(stream_id1, stream_id2);
1341    }
1342
1343    #[test]
1344    fn ed25519_public_key_to_account_owner_known_vector() {
1345        use crate::crypto::Ed25519PublicKey;
1346        // Pins the entire derivation pipeline against silent drift, not just BCS.
1347        // The chain executed:
1348        //
1349        //   [u8; 32]
1350        //     -> Ed25519PublicKey                       (newtype wrap)
1351        //     -> AccountOwner::from(public_key)         (impl From, this file)
1352        //          -> CryptoHash::new(&public_key)
1353        //               -> Hashable::write into a Keccak256 hasher
1354        //                    -> BcsHashable blanket impl writes:
1355        //                         * type-name discriminator prefix
1356        //                         * BCS body (32 raw bytes for [u8; 32])
1357        //               -> Keccak256 finalize -> 32-byte hash
1358        //     -> AccountOwner::Address32(hash)
1359        //     -> Display: "0x" + lowercase hex
1360        //
1361        // Any change in any link breaks this test: BCS format, the
1362        // `BcsHashable` type-name discriminator, the hash function, the
1363        // `From<Ed25519PublicKey>` impl, the `Address32` carrier, or the
1364        // `Display` formatting.
1365        //
1366        // Fixed 32-byte public key (0x01..0x20).
1367        let pubkey_bytes: [u8; 32] = [
1368            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1369            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1370            0x1d, 0x1e, 0x1f, 0x20,
1371        ];
1372        let pubkey = Ed25519PublicKey(pubkey_bytes);
1373        let owner = AccountOwner::from(pubkey);
1374        // The expected hex is the pinned output of `Keccak256(BCS(Ed25519PublicKey))`.
1375        // Do not update it without understanding why the derivation changed — the JS
1376        // test in `@linera/client` cross-checks this exact value.
1377        assert_eq!(
1378            owner.to_string(),
1379            "0xeacee5344cbec9569e836f95029d476c700f4f5bc007c71c0752c73fba149043",
1380            "Ed25519 owner derivation drifted; verify intentional before updating"
1381        );
1382    }
1383}