sui_sdk_types/
hash.rs

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