iota_sdk_types/
hash.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use blake2::Digest as DigestTrait;
6
7use crate::{Address, Digest, PublicKeyExt};
8
9type Blake2b256 = blake2::Blake2b<blake2::digest::consts::U32>;
10
11/// A Blake2b256 Hasher
12#[derive(Debug, Default)]
13pub struct Hasher(Blake2b256);
14
15impl Hasher {
16    /// Initialize a new Blake2b256 Hasher instance.
17    pub fn new() -> Self {
18        Self(Blake2b256::new())
19    }
20
21    /// Process the provided data, updating internal state.
22    pub fn update<T: AsRef<[u8]>>(&mut self, data: T) {
23        self.0.update(data)
24    }
25
26    /// Finalize hashing, consuming the Hasher instance and returning the
27    /// resultant hash or `Digest`.
28    pub fn finalize(self) -> Digest {
29        let mut buf = [0; Digest::LENGTH];
30        let result = self.0.finalize();
31
32        buf.copy_from_slice(&result);
33
34        Digest::new(buf)
35    }
36
37    /// Convenience function for creating a new Hasher instance, hashing the
38    /// provided data, and returning the resultant `Digest`
39    pub fn digest<T: AsRef<[u8]>>(data: T) -> Digest {
40        let mut hasher = Self::new();
41        hasher.update(data);
42        hasher.finalize()
43    }
44}
45
46impl std::io::Write for Hasher {
47    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
48        self.0.write(buf)
49    }
50
51    fn flush(&mut self) -> std::io::Result<()> {
52        self.0.flush()
53    }
54}
55
56impl crate::Ed25519PublicKey {
57    /// Derive an `Address` from this Public Key
58    ///
59    /// An `Address` can be derived from an `Ed25519PublicKey` by hashing the
60    /// bytes of the public key with no prefix flag.
61    ///
62    /// `hash(32-byte ed25519 public key)`
63    ///
64    /// ```
65    /// use iota_sdk_types::{Address, Ed25519PublicKey, hash::Hasher};
66    ///
67    /// let public_key_bytes = [0; 32];
68    /// let mut hasher = Hasher::new();
69    /// hasher.update(public_key_bytes);
70    /// let address = Address::new(hasher.finalize().into_inner());
71    /// println!("Address: {}", address);
72    ///
73    /// let public_key = Ed25519PublicKey::new(public_key_bytes);
74    /// assert_eq!(address, public_key.derive_address());
75    /// ```
76    pub fn derive_address(&self) -> Address {
77        let mut hasher = Hasher::new();
78        self.write_into_hasher(&mut hasher);
79        let digest = hasher.finalize();
80        Address::new(digest.into_inner())
81    }
82
83    fn write_into_hasher(&self, hasher: &mut Hasher) {
84        hasher.update(self.inner());
85    }
86}
87
88impl crate::Secp256k1PublicKey {
89    /// Derive an `Address` from this Public Key
90    ///
91    /// An `Address` can be derived from a `Secp256k1PublicKey` by hashing the
92    /// bytes of the public key prefixed with the Secp256k1
93    /// `SignatureScheme` flag (`0x01`).
94    ///
95    /// `hash( 0x01 || 33-byte secp256k1 public key)`
96    ///
97    /// ```
98    /// use iota_sdk_types::{Address, Secp256k1PublicKey, hash::Hasher};
99    ///
100    /// let public_key_bytes = [0; 33];
101    /// let mut hasher = Hasher::new();
102    /// hasher.update([0x01]); // The SignatureScheme flag for Secp256k1 is `1`
103    /// hasher.update(public_key_bytes);
104    /// let address = Address::new(hasher.finalize().into_inner());
105    /// println!("Address: {}", address);
106    ///
107    /// let public_key = Secp256k1PublicKey::new(public_key_bytes);
108    /// assert_eq!(address, public_key.derive_address());
109    /// ```
110    pub fn derive_address(&self) -> Address {
111        let mut hasher = Hasher::new();
112        self.write_into_hasher(&mut hasher);
113        let digest = hasher.finalize();
114        Address::new(digest.into_inner())
115    }
116
117    fn write_into_hasher(&self, hasher: &mut Hasher) {
118        hasher.update([self.scheme().to_u8()]);
119        hasher.update(self.inner());
120    }
121}
122
123impl crate::Secp256r1PublicKey {
124    /// Derive an `Address` from this Public Key
125    ///
126    /// An `Address` can be derived from a `Secp256r1PublicKey` by hashing the
127    /// bytes of the public key prefixed with the Secp256r1
128    /// `SignatureScheme` flag (`0x02`).
129    ///
130    /// `hash( 0x02 || 33-byte secp256r1 public key)`
131    ///
132    /// ```
133    /// use iota_sdk_types::{Address, Secp256r1PublicKey, hash::Hasher};
134    ///
135    /// let public_key_bytes = [0; 33];
136    /// let mut hasher = Hasher::new();
137    /// hasher.update([0x02]); // The SignatureScheme flag for Secp256r1 is `2`
138    /// hasher.update(public_key_bytes);
139    /// let address = Address::new(hasher.finalize().into_inner());
140    /// println!("Address: {}", address);
141    ///
142    /// let public_key = Secp256r1PublicKey::new(public_key_bytes);
143    /// assert_eq!(address, public_key.derive_address());
144    /// ```
145    pub fn derive_address(&self) -> Address {
146        let mut hasher = Hasher::new();
147        self.write_into_hasher(&mut hasher);
148        let digest = hasher.finalize();
149        Address::new(digest.into_inner())
150    }
151
152    fn write_into_hasher(&self, hasher: &mut Hasher) {
153        hasher.update([self.scheme().to_u8()]);
154        hasher.update(self.inner());
155    }
156}
157
158impl crate::ZkLoginPublicIdentifier {
159    /// Derive an `Address` from this `ZkLoginPublicIdentifier` by hashing the
160    /// byte length of the `iss` followed by the `iss` bytes themselves and
161    /// the full 32 byte `address_seed` value, all prefixed with the zklogin
162    /// `SignatureScheme` flag (`0x05`).
163    ///
164    /// `hash( 0x05 || iss_bytes_len || iss_bytes || 32_byte_address_seed )`
165    pub fn derive_address_padded(&self) -> Address {
166        let mut hasher = Hasher::new();
167        self.write_into_hasher_padded(&mut hasher);
168        let digest = hasher.finalize();
169        Address::new(digest.into_inner())
170    }
171
172    fn write_into_hasher_padded(&self, hasher: &mut Hasher) {
173        hasher.update([self.scheme().to_u8()]);
174        hasher.update([self.iss().len() as u8]); // TODO enforce iss is less than 255 bytes
175        hasher.update(self.iss());
176        hasher.update(self.address_seed().padded());
177    }
178
179    /// Derive an `Address` from this `ZkLoginPublicIdentifier` by hashing the
180    /// byte length of the `iss` followed by the `iss` bytes themselves and
181    /// the `address_seed` bytes with any leading zero-bytes stripped, all
182    /// prefixed with the zklogin `SignatureScheme` flag (`0x05`).
183    ///
184    /// `hash( 0x05 || iss_bytes_len || iss_bytes ||
185    /// unpadded_32_byte_address_seed )`
186    pub fn derive_address_unpadded(&self) -> Address {
187        let mut hasher = Hasher::new();
188        hasher.update([self.scheme().to_u8()]);
189        hasher.update([self.iss().len() as u8]); // TODO enforce iss is less than 255 bytes
190        hasher.update(self.iss());
191        hasher.update(self.address_seed().unpadded());
192        let digest = hasher.finalize();
193        Address::new(digest.into_inner())
194    }
195
196    /// Provides an iterator over the addresses that correspond to this zklogin
197    /// authenticator.
198    ///
199    /// In the majority of instances this will only yield a single address,
200    /// except for the instances where the `address_seed` value has a
201    /// leading zero-byte, in such cases the returned iterator will yield
202    /// two addresses.
203    pub fn derive_address(&self) -> impl Iterator<Item = Address> {
204        let main_address = self.derive_address_padded();
205        let mut addresses = [Some(main_address), None];
206        // If address_seed starts with a zero byte then we know that this zklogin
207        // authenticator has two addresses
208        if self.address_seed().padded()[0] == 0 {
209            let secondary_address = self.derive_address_unpadded();
210
211            addresses[1] = Some(secondary_address);
212        }
213
214        addresses.into_iter().flatten()
215    }
216}
217
218impl crate::PasskeyPublicKey {
219    /// Derive an `Address` from this Passkey Public Key
220    ///
221    /// An `Address` can be derived from a `PasskeyPublicKey` by hashing the
222    /// bytes of the `Secp256r1PublicKey` that corresponds to this passkey
223    /// prefixed with the Passkey `SignatureScheme` flag (`0x06`).
224    ///
225    /// `hash( 0x06 || 33-byte secp256r1 public key)`
226    pub fn derive_address(&self) -> Address {
227        let mut hasher = Hasher::new();
228        self.write_into_hasher(&mut hasher);
229        let digest = hasher.finalize();
230        Address::new(digest.into_inner())
231    }
232
233    fn write_into_hasher(&self, hasher: &mut Hasher) {
234        hasher.update([self.scheme().to_u8()]);
235        hasher.update(self.inner().inner());
236    }
237}
238
239impl crate::MultisigCommittee {
240    /// Derive an `Address` from this MultisigCommittee.
241    ///
242    /// A MultiSig address
243    /// is defined as the 32-byte Blake2b hash of serializing the
244    /// `SignatureScheme` flag (0x03), the threshold (in little endian), and
245    /// the concatenation of all n flag, public keys and its weight.
246    ///
247    /// `hash(0x03 || threshold || flag_1 || pk_1 || weight_1
248    /// || ... || flag_n || pk_n || weight_n)`.
249    ///
250    /// When flag_i is ZkLogin, the pk_i for the [`ZkLoginPublicIdentifier`]
251    /// refers to the same input used when deriving the address using the
252    /// [`ZkLoginPublicIdentifier::derive_address_padded`] method (using the
253    /// full 32-byte `address_seed` value).
254    ///
255    /// [`ZkLoginPublicIdentifier`]: crate::ZkLoginPublicIdentifier
256    /// [`ZkLoginPublicIdentifier::derive_address_padded`]: crate::ZkLoginPublicIdentifier::derive_address_padded
257    pub fn derive_address(&self) -> Address {
258        use crate::MultisigMemberPublicKey::*;
259
260        let mut hasher = Hasher::new();
261        hasher.update([self.scheme().to_u8()]);
262        hasher.update(self.threshold().to_le_bytes());
263
264        for member in self.members() {
265            match member.public_key() {
266                Ed25519(p) => p.write_into_hasher(&mut hasher),
267                Secp256k1(p) => p.write_into_hasher(&mut hasher),
268                Secp256r1(p) => p.write_into_hasher(&mut hasher),
269                ZkLogin(p) => p.write_into_hasher_padded(&mut hasher),
270            }
271
272            hasher.update(member.weight().to_le_bytes());
273        }
274
275        let digest = hasher.finalize();
276        Address::new(digest.into_inner())
277    }
278}
279
280#[cfg(feature = "serde")]
281#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
282mod type_digest {
283    use base64ct::Encoding;
284
285    use super::Hasher;
286    use crate::Digest;
287
288    impl crate::Object {
289        /// Calculate the digest of this `Object`
290        ///
291        /// This is done by hashing the BCS bytes of this `Object` prefixed
292        /// with a salt.
293        pub fn digest(&self) -> Digest {
294            const SALT: &str = "Object::";
295            type_digest(SALT, self)
296        }
297    }
298
299    impl crate::CheckpointSummary {
300        pub fn digest(&self) -> Digest {
301            const SALT: &str = "CheckpointSummary::";
302            type_digest(SALT, self)
303        }
304    }
305
306    impl crate::CheckpointContents {
307        pub fn digest(&self) -> Digest {
308            const SALT: &str = "CheckpointContents::";
309            type_digest(SALT, self)
310        }
311    }
312
313    impl crate::Transaction {
314        pub fn digest(&self) -> Digest {
315            const SALT: &str = "TransactionData::";
316            type_digest(SALT, &self)
317        }
318
319        /// Serialize the transaction as a `Vec<u8>` of BCS bytes.
320        pub fn to_bcs(&self) -> Vec<u8> {
321            bcs::to_bytes(self).expect("bcs serialization failed")
322        }
323
324        /// Serialize the transaction as a base64-encoded string.
325        pub fn to_base64(&self) -> String {
326            base64ct::Base64::encode_string(&self.to_bcs())
327        }
328
329        /// Deserialize a transaction from a `Vec<u8>` of BCS bytes.
330        pub fn from_bcs(bytes: &[u8]) -> Result<Self, bcs::Error> {
331            bcs::from_bytes::<Self>(bytes)
332        }
333
334        /// Deserialize a transaction from a base64-encoded string.
335        pub fn from_base64(bytes: &str) -> Result<Self, bcs::Error> {
336            let decoded = base64ct::Base64::decode_vec(bytes)
337                .map_err(|e| bcs::Error::Custom(e.to_string()))?;
338            Self::from_bcs(&decoded)
339        }
340    }
341
342    impl crate::TransactionV1 {
343        pub fn digest(&self) -> Digest {
344            const SALT: &str = "TransactionData::";
345            type_digest(SALT, &crate::Transaction::V1(self.clone()))
346        }
347
348        /// Serialize the transaction as a `Vec<u8>` of BCS bytes.
349        pub fn to_bcs(&self) -> Vec<u8> {
350            bcs::to_bytes(self).expect("bcs serialization failed")
351        }
352
353        /// Serialize the transaction as a base64-encoded string.
354        pub fn to_base64(&self) -> String {
355            base64ct::Base64::encode_string(&self.to_bcs())
356        }
357
358        /// Deserialize a transaction from a `Vec<u8>` of BCS bytes.
359        pub fn from_bcs(bytes: &[u8]) -> Result<Self, bcs::Error> {
360            bcs::from_bytes::<Self>(bytes)
361        }
362
363        /// Deserialize a transaction from a base64-encoded string.
364        pub fn from_base64(bytes: &str) -> Result<Self, bcs::Error> {
365            let decoded = base64ct::Base64::decode_vec(bytes)
366                .map_err(|e| bcs::Error::Custom(e.to_string()))?;
367            Self::from_bcs(&decoded)
368        }
369    }
370
371    impl crate::TransactionEffects {
372        pub fn digest(&self) -> Digest {
373            const SALT: &str = "TransactionEffects::";
374            type_digest(SALT, self)
375        }
376    }
377
378    impl crate::TransactionEvents {
379        pub fn digest(&self) -> Digest {
380            const SALT: &str = "TransactionEvents::";
381            type_digest(SALT, self)
382        }
383    }
384
385    fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
386        let mut hasher = Hasher::new();
387        hasher.update(salt);
388        bcs::serialize_into(&mut hasher, ty).unwrap();
389        hasher.finalize()
390    }
391}
392
393#[cfg(feature = "serde")]
394#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
395mod signing_message {
396    use crate::{
397        Digest, Intent, IntentAppId, IntentScope, IntentVersion, PersonalMessage, SigningDigest,
398        Transaction, TransactionV1, hash::Hasher,
399    };
400
401    impl Transaction {
402        pub fn signing_digest(&self) -> SigningDigest {
403            const INTENT: Intent = Intent {
404                scope: IntentScope::TransactionData,
405                version: IntentVersion::V0,
406                app_id: IntentAppId::Iota,
407            };
408            let digest = signing_digest(INTENT, self);
409            digest.into_inner()
410        }
411
412        pub fn signing_digest_hex(&self) -> String {
413            hex::encode(self.signing_digest())
414        }
415    }
416
417    impl TransactionV1 {
418        pub fn signing_digest(&self) -> SigningDigest {
419            const INTENT: Intent = Intent {
420                scope: IntentScope::TransactionData,
421                version: IntentVersion::V0,
422                app_id: IntentAppId::Iota,
423            };
424            let digest = signing_digest(INTENT, &Transaction::V1(self.clone()));
425            digest.into_inner()
426        }
427
428        pub fn signing_digest_hex(&self) -> String {
429            hex::encode(self.signing_digest())
430        }
431    }
432
433    fn signing_digest<T: serde::Serialize + ?Sized>(intent: Intent, ty: &T) -> Digest {
434        let mut hasher = Hasher::new();
435        hasher.update(intent.to_bytes());
436        bcs::serialize_into(&mut hasher, ty).unwrap();
437        hasher.finalize()
438    }
439
440    impl PersonalMessage<'_> {
441        pub fn signing_digest(&self) -> SigningDigest {
442            const INTENT: Intent = Intent {
443                scope: IntentScope::PersonalMessage,
444                version: IntentVersion::V0,
445                app_id: IntentAppId::Iota,
446            };
447            let digest = signing_digest(INTENT, &self.0);
448            digest.into_inner()
449        }
450
451        pub fn signing_digest_hex(&self) -> String {
452            hex::encode(self.signing_digest())
453        }
454    }
455
456    impl crate::CheckpointSummary {
457        pub fn signing_message(&self) -> Vec<u8> {
458            const INTENT: Intent = Intent {
459                scope: IntentScope::CheckpointSummary,
460                version: IntentVersion::V0,
461                app_id: IntentAppId::Iota,
462            };
463            let mut message = Vec::new();
464            message.extend(INTENT.to_bytes());
465            bcs::serialize_into(&mut message, self).unwrap();
466            bcs::serialize_into(&mut message, &self.epoch).unwrap();
467            message
468        }
469
470        pub fn signing_message_hex(&self) -> String {
471            hex::encode(self.signing_message())
472        }
473    }
474}
475
476/// A 1-byte domain separator for hashing Object ID in IOTA. It is starting from
477/// 0xf0 to ensure no hashing collision for any ObjectId vs Address which is
478/// derived as the hash of `flag || pubkey`.
479#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
480#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
481#[repr(u8)]
482enum HashingIntent {
483    #[cfg(feature = "serde")]
484    ChildObjectId = 0xf0,
485    RegularObjectId = 0xf1,
486}
487
488impl crate::ObjectId {
489    /// Create an ObjectId from a transaction digest and `count`.
490    ///
491    /// `count` is the number of objects that have been created during a
492    /// transactions.
493    pub fn derive_id(digest: crate::Digest, count: u64) -> Self {
494        let mut hasher = Hasher::new();
495        hasher.update([HashingIntent::RegularObjectId as u8]);
496        hasher.update(digest);
497        hasher.update(count.to_le_bytes());
498        let digest = hasher.finalize();
499        Self::new(digest.into_inner())
500    }
501
502    /// Derive an ObjectId for a Dynamic Child Object.
503    ///
504    /// hash(parent || len(key) || key || key_type_tag)
505    #[cfg(feature = "serde")]
506    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
507    pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
508        let mut hasher = Hasher::new();
509        hasher.update([HashingIntent::ChildObjectId as u8]);
510        hasher.update(self);
511        hasher.update(
512            u64::try_from(key_bytes.len())
513                .expect("key_bytes must fit into a u64")
514                .to_le_bytes(),
515        );
516        hasher.update(key_bytes);
517        bcs::serialize_into(&mut hasher, key_type_tag)
518            .expect("bcs serialization of `TypeTag` cannot fail");
519        let digest = hasher.finalize();
520
521        Self::new(digest.into_inner())
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use test_strategy::proptest;
528
529    use super::HashingIntent;
530    use crate::SignatureScheme;
531
532    impl HashingIntent {
533        fn from_byte(byte: u8) -> Result<Self, u8> {
534            match byte {
535                0xf0 => Ok(Self::ChildObjectId),
536                0xf1 => Ok(Self::RegularObjectId),
537                invalid => Err(invalid),
538            }
539        }
540    }
541
542    #[proptest]
543    fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
544        SignatureScheme::from_byte(intent as u8).unwrap_err();
545    }
546
547    #[proptest]
548    fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
549        HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
550    }
551
552    #[proptest]
553    fn roundtrip_hashing_intent(intent: HashingIntent) {
554        assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
555    }
556}