iota_sdk/client/
utils.rs

1// Copyright 2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Utility functions for IOTA
5
6use core::borrow::Borrow;
7use std::collections::HashMap;
8
9use crypto::{
10    hashes::{blake2b::Blake2b256, Digest},
11    keys::bip39::{wordlist, Mnemonic, MnemonicRef, Passphrase, Seed},
12    utils,
13};
14use serde::{Deserialize, Serialize};
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17use super::{Client, ClientInner};
18use crate::{
19    client::{Error, Result},
20    types::block::{
21        address::{Address, Bech32Address, Ed25519Address, Hrp, ToBech32Ext},
22        output::{AliasId, NftId},
23        payload::TaggedDataPayload,
24        ConvertTo,
25    },
26};
27
28/// Transforms bech32 to hex
29pub fn bech32_to_hex(bech32: impl ConvertTo<Bech32Address>) -> Result<String> {
30    Ok(match bech32.convert()?.inner() {
31        Address::Ed25519(ed) => ed.to_string(),
32        Address::Alias(alias) => alias.to_string(),
33        Address::Nft(nft) => nft.to_string(),
34    })
35}
36
37/// Transforms a hex encoded address to a bech32 encoded address
38pub fn hex_to_bech32(hex: &str, bech32_hrp: impl ConvertTo<Hrp>) -> Result<Bech32Address> {
39    let address: Ed25519Address = hex.parse::<Ed25519Address>()?;
40    Ok(Address::Ed25519(address).try_to_bech32(bech32_hrp)?)
41}
42
43/// Transforms a prefix hex encoded public key to a bech32 encoded address
44pub fn hex_public_key_to_bech32_address(hex: &str, bech32_hrp: impl ConvertTo<Hrp>) -> Result<Bech32Address> {
45    let public_key: [u8; Ed25519Address::LENGTH] = prefix_hex::decode(hex)?;
46    let address = Ed25519Address::new(Blake2b256::digest(public_key).into());
47
48    Ok(Address::Ed25519(address).try_to_bech32(bech32_hrp)?)
49}
50
51/// Generates a new mnemonic.
52pub fn generate_mnemonic() -> Result<Mnemonic> {
53    let mut entropy = [0u8; 32];
54    utils::rand::fill(&mut entropy)?;
55    let mnemonic = wordlist::encode(&entropy, &crypto::keys::bip39::wordlist::ENGLISH)
56        .map_err(|e| crate::client::Error::InvalidMnemonic(format!("{e:?}")))?;
57    entropy.zeroize();
58    Ok(mnemonic)
59}
60
61/// Returns a hex encoded seed for a mnemonic.
62pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow<MnemonicRef>) -> Result<String> {
63    Ok(prefix_hex::encode(mnemonic_to_seed(mnemonic)?.as_ref()))
64}
65
66/// Returns a seed for a mnemonic.
67pub fn mnemonic_to_seed(mnemonic: impl Borrow<MnemonicRef>) -> Result<Seed> {
68    // first we check if the mnemonic is valid to give meaningful errors
69    verify_mnemonic(mnemonic.borrow())?;
70    Ok(crypto::keys::bip39::mnemonic_to_seed(
71        mnemonic.borrow(),
72        &Passphrase::default(),
73    ))
74}
75
76/// Verifies that a &str is a valid mnemonic.
77pub fn verify_mnemonic(mnemonic: impl Borrow<MnemonicRef>) -> Result<()> {
78    crypto::keys::bip39::wordlist::verify(mnemonic.borrow(), &crypto::keys::bip39::wordlist::ENGLISH)
79        .map_err(|e| crate::client::Error::InvalidMnemonic(format!("{e:?}")))?;
80    Ok(())
81}
82
83/// Requests funds from a faucet
84pub async fn request_funds_from_faucet(url: &str, bech32_address: &Bech32Address) -> Result<String> {
85    let mut map = HashMap::new();
86    map.insert("address", bech32_address.to_string());
87
88    let client = reqwest::Client::new();
89    let faucet_response = client
90        .post(url)
91        .json(&map)
92        .send()
93        .await
94        .map_err(|err| Error::Node(err.into()))?
95        .text()
96        .await
97        .map_err(|err| Error::Node(err.into()))?;
98    Ok(faucet_response)
99}
100
101impl ClientInner {
102    /// Transforms a hex encoded address to a bech32 encoded address
103    pub async fn hex_to_bech32(
104        &self,
105        hex: &str,
106        bech32_hrp: Option<impl ConvertTo<Hrp>>,
107    ) -> crate::client::Result<Bech32Address> {
108        match bech32_hrp {
109            Some(hrp) => Ok(hex_to_bech32(hex, hrp)?),
110            None => Ok(hex_to_bech32(hex, self.get_bech32_hrp().await?)?),
111        }
112    }
113
114    /// Transforms an alias id to a bech32 encoded address
115    pub async fn alias_id_to_bech32(
116        &self,
117        alias_id: AliasId,
118        bech32_hrp: Option<impl ConvertTo<Hrp>>,
119    ) -> crate::client::Result<Bech32Address> {
120        match bech32_hrp {
121            Some(hrp) => Ok(alias_id.to_bech32(hrp.convert()?)),
122            None => Ok(alias_id.to_bech32(self.get_bech32_hrp().await?)),
123        }
124    }
125
126    /// Transforms an nft id to a bech32 encoded address
127    pub async fn nft_id_to_bech32(
128        &self,
129        nft_id: NftId,
130        bech32_hrp: Option<impl ConvertTo<Hrp>>,
131    ) -> crate::client::Result<Bech32Address> {
132        match bech32_hrp {
133            Some(hrp) => Ok(nft_id.to_bech32(hrp.convert()?)),
134            None => Ok(nft_id.to_bech32(self.get_bech32_hrp().await?)),
135        }
136    }
137
138    /// Transforms a hex encoded public key to a bech32 encoded address
139    pub async fn hex_public_key_to_bech32_address(
140        &self,
141        hex: &str,
142        bech32_hrp: Option<impl ConvertTo<Hrp>>,
143    ) -> crate::client::Result<Bech32Address> {
144        match bech32_hrp {
145            Some(hrp) => Ok(hex_public_key_to_bech32_address(hex, hrp)?),
146            None => Ok(hex_public_key_to_bech32_address(hex, self.get_bech32_hrp().await?)?),
147        }
148    }
149}
150
151impl Client {
152    /// Transforms bech32 to hex
153    pub fn bech32_to_hex(bech32: impl ConvertTo<Bech32Address>) -> crate::client::Result<String> {
154        bech32_to_hex(bech32)
155    }
156
157    /// Generates a new mnemonic.
158    pub fn generate_mnemonic() -> Result<Mnemonic> {
159        generate_mnemonic()
160    }
161
162    /// Returns a seed for a mnemonic.
163    pub fn mnemonic_to_seed(mnemonic: impl Borrow<MnemonicRef>) -> Result<Seed> {
164        mnemonic_to_seed(mnemonic)
165    }
166
167    /// Returns a hex encoded seed for a mnemonic.
168    pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow<MnemonicRef>) -> Result<String> {
169        mnemonic_to_hex_seed(mnemonic)
170    }
171
172    /// UTF-8 encodes the `tag` of a given TaggedDataPayload.
173    pub fn tag_to_utf8(payload: &TaggedDataPayload) -> Result<String> {
174        String::from_utf8(payload.tag().to_vec()).map_err(|_| Error::TaggedData("found invalid UTF-8".to_string()))
175    }
176
177    /// UTF-8 encodes the `data` of a given TaggedDataPayload.
178    pub fn data_to_utf8(payload: &TaggedDataPayload) -> Result<String> {
179        String::from_utf8(payload.data().to_vec()).map_err(|_| Error::TaggedData("found invalid UTF-8".to_string()))
180    }
181
182    /// UTF-8 encodes both the `tag` and `data` of a given TaggedDataPayload.
183    pub fn tagged_data_to_utf8(payload: &TaggedDataPayload) -> Result<(String, String)> {
184        Ok((Self::tag_to_utf8(payload)?, Self::data_to_utf8(payload)?))
185    }
186}
187
188/// A password wrapper that takes care of zeroing the memory when being dropped.
189#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop, derive_more::From)]
190pub struct Password(String);
191
192impl Password {
193    pub fn as_bytes(&self) -> &[u8] {
194        self.0.as_bytes()
195    }
196}