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                Passkey(p) => p.write_into_hasher(&mut hasher),
269            }
270
271            hasher.update(member.weight().to_le_bytes());
272        }
273
274        let digest = hasher.finalize();
275        Address::new(digest.into_inner())
276    }
277}
278
279#[cfg(feature = "serde")]
280#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
281mod type_digest {
282    use super::Hasher;
283    use crate::CheckpointContents;
284    use crate::CheckpointContentsDigest;
285    use crate::CheckpointDigest;
286    use crate::CheckpointSummary;
287    use crate::Digest;
288    use crate::Object;
289    use crate::ObjectDigest;
290    use crate::Transaction;
291    use crate::TransactionDigest;
292    use crate::TransactionEffects;
293    use crate::TransactionEffectsDigest;
294    use crate::TransactionEvents;
295    use crate::TransactionEventsDigest;
296
297    impl Object {
298        /// Calculate the digest of this `Object`
299        ///
300        /// This is done by hashing the BCS bytes of this `Object` prefixed
301        pub fn digest(&self) -> ObjectDigest {
302            const SALT: &str = "Object::";
303            let digest = type_digest(SALT, self);
304            ObjectDigest::new(digest.into_inner())
305        }
306    }
307
308    impl CheckpointSummary {
309        pub fn digest(&self) -> CheckpointDigest {
310            const SALT: &str = "CheckpointSummary::";
311            let digest = type_digest(SALT, self);
312            CheckpointDigest::new(digest.into_inner())
313        }
314    }
315
316    impl CheckpointContents {
317        pub fn digest(&self) -> CheckpointContentsDigest {
318            const SALT: &str = "CheckpointContents::";
319            let digest = type_digest(SALT, self);
320            CheckpointContentsDigest::new(digest.into_inner())
321        }
322    }
323
324    impl Transaction {
325        pub fn digest(&self) -> TransactionDigest {
326            const SALT: &str = "TransactionData::";
327            let digest = type_digest(SALT, self);
328            TransactionDigest::new(digest.into_inner())
329        }
330    }
331
332    impl TransactionEffects {
333        pub fn digest(&self) -> TransactionEffectsDigest {
334            const SALT: &str = "TransactionEffects::";
335            let digest = type_digest(SALT, self);
336            TransactionEffectsDigest::new(digest.into_inner())
337        }
338    }
339
340    impl TransactionEvents {
341        pub fn digest(&self) -> TransactionEventsDigest {
342            const SALT: &str = "TransactionEvents::";
343            let digest = type_digest(SALT, self);
344            TransactionEventsDigest::new(digest.into_inner())
345        }
346    }
347
348    fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
349        let mut hasher = Hasher::new();
350        hasher.update(salt);
351        bcs::serialize_into(&mut hasher, ty).unwrap();
352        hasher.finalize()
353    }
354}
355
356#[cfg(feature = "serde")]
357#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
358mod signing_message {
359    use crate::hash::Hasher;
360    use crate::Digest;
361    use crate::Intent;
362    use crate::IntentAppId;
363    use crate::IntentScope;
364    use crate::IntentVersion;
365    use crate::PersonalMessage;
366    use crate::SigningDigest;
367    use crate::Transaction;
368
369    impl Transaction {
370        pub fn signing_digest(&self) -> SigningDigest {
371            const INTENT: Intent = Intent {
372                scope: IntentScope::TransactionData,
373                version: IntentVersion::V0,
374                app_id: IntentAppId::Sui,
375            };
376            let digest = signing_digest(INTENT, self);
377            digest.into_inner()
378        }
379    }
380
381    fn signing_digest<T: serde::Serialize + ?Sized>(intent: Intent, ty: &T) -> Digest {
382        let mut hasher = Hasher::new();
383        hasher.update(intent.to_bytes());
384        bcs::serialize_into(&mut hasher, ty).unwrap();
385        hasher.finalize()
386    }
387
388    impl PersonalMessage<'_> {
389        pub fn signing_digest(&self) -> SigningDigest {
390            const INTENT: Intent = Intent {
391                scope: IntentScope::PersonalMessage,
392                version: IntentVersion::V0,
393                app_id: IntentAppId::Sui,
394            };
395            let digest = signing_digest(INTENT, &self.0);
396            digest.into_inner()
397        }
398    }
399}
400
401/// A 1-byte domain separator for deriving `ObjectId`s in Sui. It is starting from `0xf0` to ensure
402/// no hashing collision for any ObjectId vs Address which is derived as the hash of `flag ||
403/// pubkey`.
404#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
405#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
406#[repr(u8)]
407enum HashingIntent {
408    #[cfg(feature = "serde")]
409    ChildObjectId = 0xf0,
410    RegularObjectId = 0xf1,
411}
412
413impl crate::ObjectId {
414    /// Create an ObjectId from `TransactionDigest` and `count`.
415    ///
416    /// `count` is the number of objects that have been created during a transactions.
417    pub fn derive_id(digest: crate::TransactionDigest, count: u64) -> Self {
418        let mut hasher = Hasher::new();
419        hasher.update([HashingIntent::RegularObjectId as u8]);
420        hasher.update(digest);
421        hasher.update(count.to_le_bytes());
422        let digest = hasher.finalize();
423        Self::new(digest.into_inner())
424    }
425
426    /// Derive an ObjectId for a Dynamic Child Object.
427    ///
428    /// hash(parent || len(key) || key || key_type_tag)
429    #[cfg(feature = "serde")]
430    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
431    pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
432        let mut hasher = Hasher::new();
433        hasher.update([HashingIntent::ChildObjectId as u8]);
434        hasher.update(self);
435        hasher.update(
436            u64::try_from(key_bytes.len())
437                .expect("key_bytes must fit into a u64")
438                .to_le_bytes(),
439        );
440        hasher.update(key_bytes);
441        bcs::serialize_into(&mut hasher, key_type_tag)
442            .expect("bcs serialization of `TypeTag` cannot fail");
443        let digest = hasher.finalize();
444
445        Self::new(digest.into_inner())
446    }
447}
448
449#[cfg(test)]
450mod test {
451    use super::HashingIntent;
452    use crate::SignatureScheme;
453    use test_strategy::proptest;
454
455    #[cfg(target_arch = "wasm32")]
456    use wasm_bindgen_test::wasm_bindgen_test as test;
457
458    impl HashingIntent {
459        fn from_byte(byte: u8) -> Result<Self, u8> {
460            match byte {
461                0xf0 => Ok(Self::ChildObjectId),
462                0xf1 => Ok(Self::RegularObjectId),
463                invalid => Err(invalid),
464            }
465        }
466    }
467
468    #[proptest]
469    fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
470        SignatureScheme::from_byte(intent as u8).unwrap_err();
471    }
472
473    #[proptest]
474    fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
475        HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
476    }
477
478    #[proptest]
479    fn roundtrip_hashing_intent(intent: HashingIntent) {
480        assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
481    }
482}