Skip to main content

celestia_types/state/
auth.rs

1//! types related to accounts
2
3use celestia_proto::cosmos::crypto::ed25519::PubKey as Ed25519PubKey;
4use celestia_proto::cosmos::crypto::secp256k1::PubKey as Secp256k1PubKey;
5use prost::Message;
6use tendermint::public_key::PublicKey;
7use tendermint_proto::Protobuf;
8use tendermint_proto::google::protobuf::Any;
9
10use crate::Error;
11use crate::state::AccAddress;
12use crate::validation_error;
13
14pub use celestia_proto::cosmos::auth::v1beta1::BaseAccount as RawBaseAccount;
15pub use celestia_proto::cosmos::auth::v1beta1::ModuleAccount as RawModuleAccount;
16pub use celestia_proto::cosmos::auth::v1beta1::Params as AuthParams;
17
18const COSMOS_ED25519_PUBKEY: &str = "/cosmos.crypto.ed25519.PubKey";
19const COSMOS_SECP256K1_PUBKEY: &str = "/cosmos.crypto.secp256k1.PubKey";
20
21// TODO: add vesting accounts
22/// Enum representing different types of account
23#[derive(Debug, PartialEq)]
24#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
25pub enum Account {
26    /// Base account type
27    Base(BaseAccount),
28    /// Account for modules that holds coins on a pool
29    Module(ModuleAccount),
30}
31
32impl std::ops::Deref for Account {
33    type Target = BaseAccount;
34
35    fn deref(&self) -> &Self::Target {
36        match self {
37            Account::Base(base) => base,
38            Account::Module(module) => &module.base_account,
39        }
40    }
41}
42
43impl std::ops::DerefMut for Account {
44    fn deref_mut(&mut self) -> &mut Self::Target {
45        match self {
46            Account::Base(base) => base,
47            Account::Module(module) => &mut module.base_account,
48        }
49    }
50}
51
52impl From<Account> for BaseAccount {
53    fn from(value: Account) -> Self {
54        match value {
55            Account::Base(acc) => acc,
56            Account::Module(acc) => acc.base_account,
57        }
58    }
59}
60
61/// [`BaseAccount`] defines a base account type.
62///
63/// It contains all the necessary fields for basic account functionality.
64///
65/// Any custom account type should extend this type for additional functionality
66/// (e.g. vesting).
67#[derive(Debug, Clone, PartialEq)]
68pub struct BaseAccount {
69    /// Bech32 `AccountId` of this account.
70    pub address: AccAddress,
71    /// Optional `PublicKey` associated with this account.
72    pub pub_key: Option<PublicKey>,
73    /// `account_number` is the account number of the account in state
74    pub account_number: u64,
75    /// Sequence of the account, which describes the number of committed transactions signed by a
76    /// given address.
77    pub sequence: u64,
78}
79
80/// [`ModuleAccount`] defines an account for modules that holds coins on a pool.
81#[derive(Debug, Clone, PartialEq)]
82#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
83pub struct ModuleAccount {
84    /// [`BaseAccount`] specification of this module account.
85    pub base_account: BaseAccount,
86    /// Name of the module.
87    pub name: String,
88    /// Permissions associated with this module account.
89    pub permissions: Vec<String>,
90}
91
92impl From<BaseAccount> for RawBaseAccount {
93    fn from(account: BaseAccount) -> Self {
94        RawBaseAccount {
95            address: account.address.to_string(),
96            pub_key: account.pub_key.map(any_from_public_key),
97            account_number: account.account_number,
98            sequence: account.sequence,
99        }
100    }
101}
102
103impl TryFrom<RawBaseAccount> for BaseAccount {
104    type Error = Error;
105
106    fn try_from(account: RawBaseAccount) -> Result<Self, Self::Error> {
107        let pub_key = account.pub_key.map(public_key_from_any).transpose()?;
108        Ok(BaseAccount {
109            address: account.address.parse()?,
110            pub_key,
111            account_number: account.account_number,
112            sequence: account.sequence,
113        })
114    }
115}
116
117impl From<ModuleAccount> for RawModuleAccount {
118    fn from(account: ModuleAccount) -> Self {
119        let base_account = Some(account.base_account.into());
120        RawModuleAccount {
121            base_account,
122            name: account.name,
123            permissions: account.permissions,
124        }
125    }
126}
127
128impl TryFrom<RawModuleAccount> for ModuleAccount {
129    type Error = Error;
130
131    fn try_from(account: RawModuleAccount) -> Result<Self, Self::Error> {
132        let base_account = account
133            .base_account
134            .ok_or_else(|| validation_error!("base account missing"))?
135            .try_into()?;
136        Ok(ModuleAccount {
137            base_account,
138            name: account.name,
139            permissions: account.permissions,
140        })
141    }
142}
143
144fn public_key_from_any(any: Any) -> Result<PublicKey, Error> {
145    match any.type_url.as_ref() {
146        COSMOS_ED25519_PUBKEY => {
147            PublicKey::from_raw_ed25519(&Ed25519PubKey::decode(&*any.value)?.key)
148        }
149        COSMOS_SECP256K1_PUBKEY => {
150            PublicKey::from_raw_secp256k1(&Secp256k1PubKey::decode(&*any.value)?.key)
151        }
152        other => return Err(Error::InvalidPublicKeyType(other.to_string())),
153    }
154    .ok_or(Error::InvalidPublicKey)
155}
156
157fn any_from_public_key(key: PublicKey) -> Any {
158    match key {
159        key @ PublicKey::Ed25519(_) => Any {
160            type_url: COSMOS_ED25519_PUBKEY.to_string(),
161            value: Ed25519PubKey {
162                key: key.to_bytes(),
163            }
164            .encode_to_vec(),
165        },
166        key @ PublicKey::Secp256k1(_) => Any {
167            type_url: COSMOS_SECP256K1_PUBKEY.to_string(),
168            value: Secp256k1PubKey {
169                key: key.to_bytes(),
170            }
171            .encode_to_vec(),
172        },
173        _ => unimplemented!("unexpected key type"),
174    }
175}
176
177impl Protobuf<RawBaseAccount> for BaseAccount {}
178
179impl Protobuf<RawModuleAccount> for ModuleAccount {}
180
181#[cfg(feature = "uniffi")]
182mod uniffi_types {
183    use std::str::FromStr;
184
185    use super::{BaseAccount as RustBaseAccount, PublicKey as TendermintPublicKey};
186
187    use tendermint::public_key::{Ed25519, Secp256k1};
188    use uniffi::{Enum, Record};
189
190    use crate::{error::UniffiConversionError, state::Address};
191
192    #[derive(Enum)]
193    pub enum PublicKey {
194        Ed25519 { bytes: Vec<u8> },
195        Secp256k1 { sec1_bytes: Vec<u8> },
196    }
197
198    impl TryFrom<PublicKey> for TendermintPublicKey {
199        type Error = UniffiConversionError;
200
201        fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
202            Ok(match value {
203                PublicKey::Ed25519 { bytes } => TendermintPublicKey::Ed25519(
204                    Ed25519::try_from(bytes.as_ref())
205                        .map_err(|_| UniffiConversionError::InvalidPublicKey)?,
206                ),
207                PublicKey::Secp256k1 { sec1_bytes } => TendermintPublicKey::Secp256k1(
208                    Secp256k1::from_sec1_bytes(&sec1_bytes)
209                        .map_err(|_| UniffiConversionError::InvalidPublicKey)?,
210                ),
211            })
212        }
213    }
214
215    impl From<TendermintPublicKey> for PublicKey {
216        fn from(value: TendermintPublicKey) -> Self {
217            match value {
218                TendermintPublicKey::Ed25519(k) => PublicKey::Ed25519 {
219                    bytes: k.as_bytes().to_vec(),
220                },
221                TendermintPublicKey::Secp256k1(k) => PublicKey::Secp256k1 {
222                    sec1_bytes: k.to_sec1_bytes().to_vec(),
223                },
224                _ => unimplemented!("unexpected key type"),
225            }
226        }
227    }
228
229    uniffi::custom_type!(TendermintPublicKey, PublicKey, {
230        remote,
231        try_lift: |value| Ok(value.try_into()?),
232        lower: |value| value.into()
233    });
234
235    #[derive(Record)]
236    pub struct BaseAccount {
237        /// Bech32 `AccountId` of this account.
238        pub address: String,
239        /// Optional `PublicKey` associated with this account.
240        pub pub_key: Option<PublicKey>,
241        /// `account_number` is the account number of the account in state
242        pub account_number: u64,
243        /// Sequence of the account, which describes the number of committed transactions signed by a
244        /// given address.
245        pub sequence: u64,
246    }
247
248    impl TryFrom<BaseAccount> for RustBaseAccount {
249        type Error = UniffiConversionError;
250
251        fn try_from(value: BaseAccount) -> Result<Self, Self::Error> {
252            let address = Address::from_str(&value.address)
253                .map_err(|e| UniffiConversionError::InvalidAddress { msg: e.to_string() })?;
254            let Address::AccAddress(account_address) = address else {
255                return Err(UniffiConversionError::InvalidAddress {
256                    msg: "Invalid address type, expected account address".to_string(),
257                });
258            };
259
260            Ok(RustBaseAccount {
261                address: account_address,
262                pub_key: value.pub_key.map(TryInto::try_into).transpose()?,
263                account_number: value.account_number,
264                sequence: value.sequence,
265            })
266        }
267    }
268
269    impl From<RustBaseAccount> for BaseAccount {
270        fn from(value: RustBaseAccount) -> Self {
271            BaseAccount {
272                address: value.address.to_string(),
273                pub_key: value.pub_key.map(Into::into),
274                account_number: value.account_number,
275                sequence: value.sequence,
276            }
277        }
278    }
279
280    uniffi::custom_type!(RustBaseAccount, BaseAccount);
281}
282
283#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
284pub use wbg::*;
285
286#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
287mod wbg {
288    use js_sys::{BigInt, Uint8Array};
289    use lumina_utils::make_object;
290    use tendermint::PublicKey;
291    use wasm_bindgen::{JsCast, prelude::*};
292
293    use super::{Account, AuthParams};
294
295    #[wasm_bindgen(typescript_custom_section)]
296    const _: &str = r#"
297    /**
298     * Public key
299     */
300    export interface PublicKey {
301      type: "ed25519" | "secp256k1",
302      value: Uint8Array
303    }
304
305    /**
306     * Common data of all account types
307     */
308    export interface BaseAccount {
309      address: string,
310      pubkey?: PublicKey,
311      accountNumber: bigint,
312      sequence: bigint
313    }
314
315    /**
316     * Auth module parameters
317     */
318    export interface AuthParams {
319      maxMemoCharacters: bigint,
320      txSigLimit: bigint,
321      txSizeCostPerByte: bigint,
322      sigVerifyCostEd25519: bigint,
323      sigVerifyCostSecp256k1: bigint
324    }
325    "#;
326
327    #[wasm_bindgen]
328    extern "C" {
329        /// Public key exposed to javascript.
330        #[derive(Clone, Debug)]
331        #[wasm_bindgen(typescript_type = "PublicKey")]
332        pub type JsPublicKey;
333
334        /// AuthParams exposed to JS
335        #[wasm_bindgen(typescript_type = "AuthParams")]
336        pub type JsAuthParams;
337
338        /// BaseAccount exposed to javascript.
339        #[wasm_bindgen(typescript_type = "BaseAccount")]
340        pub type JsBaseAccount;
341    }
342
343    impl From<AuthParams> for JsAuthParams {
344        fn from(value: AuthParams) -> JsAuthParams {
345            let obj = make_object!(
346                "maxMemoCharacters" => BigInt::from(value.max_memo_characters),
347                "txSigLimit" => BigInt::from(value.tx_sig_limit),
348                "txSizeCostPerByte" => BigInt::from(value.tx_size_cost_per_byte),
349                "sigVerifyCostEd25519" => BigInt::from(value.sig_verify_cost_ed25519),
350                "sigVerifyCostSecp256k1" => BigInt::from(value.sig_verify_cost_secp256k1)
351            );
352
353            obj.unchecked_into()
354        }
355    }
356
357    impl From<PublicKey> for JsPublicKey {
358        fn from(value: PublicKey) -> JsPublicKey {
359            let algo = match value {
360                PublicKey::Ed25519(..) => "ed25519",
361                PublicKey::Secp256k1(..) => "secp256k1",
362                _ => unreachable!("unsupported pubkey algo found"),
363            };
364            let obj = make_object!(
365                "type" => algo.into(),
366                "value" => Uint8Array::from(value.to_bytes().as_ref())
367            );
368
369            obj.unchecked_into()
370        }
371    }
372
373    impl From<Account> for JsBaseAccount {
374        fn from(value: Account) -> JsBaseAccount {
375            let obj = make_object!(
376                "address" => value.address.to_string().into(),
377                "pubkey" => value.pub_key.map(JsPublicKey::from).into(),
378                "accountNumber" => BigInt::from(value.account_number),
379                "sequence" => BigInt::from(value.sequence)
380            );
381
382            obj.unchecked_into()
383        }
384    }
385}