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 ExactSizeIterator<Item = Address> {
202        self.internal_derive_addresses()
203    }
204
205    // Private internal function
206    fn internal_derive_addresses(&self) -> DerivedAddressIter {
207        let primary = self.derive_address_padded();
208        let mut addresses = DerivedAddressIter::new(primary);
209
210        // If address_seed starts with a zero byte then we know that this zklogin authenticator has
211        // two addresses
212        if self.address_seed().padded()[0] == 0 {
213            let secondary_address = self.derive_address_unpadded();
214
215            addresses.extra = Some(secondary_address);
216        }
217
218        addresses
219    }
220}
221
222impl crate::ZkLoginAuthenticator {
223    pub fn derive_address_padded(&self) -> Address {
224        self.inputs.public_identifier().derive_address_padded()
225    }
226
227    pub fn derive_address_unpadded(&self) -> Address {
228        self.inputs.public_identifier().derive_address_unpadded()
229    }
230
231    pub fn derive_address(&self) -> impl ExactSizeIterator<Item = Address> {
232        self.inputs.public_identifier().derive_address()
233    }
234}
235
236impl crate::PasskeyPublicKey {
237    /// Derive an `Address` from this Passkey Public Key
238    ///
239    /// An `Address` can be derived from a `PasskeyPublicKey` by hashing the bytes of the
240    /// `Secp256r1PublicKey` that corresponds to this passkey prefixed with the Passkey
241    /// `SignatureScheme` flag (`0x06`).
242    ///
243    /// `hash( 0x06 || 33-byte secp256r1-public-key)`
244    pub fn derive_address(&self) -> Address {
245        let mut hasher = Hasher::new();
246        self.write_into_hasher(&mut hasher);
247        let digest = hasher.finalize();
248        Address::new(digest.into_inner())
249    }
250
251    fn write_into_hasher(&self, hasher: &mut Hasher) {
252        hasher.update([self.scheme().to_u8()]);
253        hasher.update(self.inner().inner());
254    }
255}
256
257impl crate::PasskeyAuthenticator {
258    pub fn derive_address(&self) -> Address {
259        self.public_key().derive_address()
260    }
261}
262
263impl crate::MultisigCommittee {
264    /// Derive an `Address` from this MultisigCommittee.
265    ///
266    /// A MultiSig address
267    /// is defined as the 32-byte Blake2b hash of serializing the `SignatureScheme` flag (0x03), the
268    /// threshold (in little endian), and the concatenation of all n flag, public keys and
269    /// its weight.
270    ///
271    /// `hash(0x03 || threshold || flag_1 || pk_1 || weight_1
272    /// || ... || flag_n || pk_n || weight_n)`.
273    ///
274    /// When flag_i is ZkLogin, the pk_i for the [`ZkLoginPublicIdentifier`] refers to the same
275    /// input used when deriving the address using the
276    /// [`ZkLoginPublicIdentifier::derive_address_padded`] method (using the full 32-byte
277    /// `address_seed` value).
278    ///
279    /// [`ZkLoginPublicIdentifier`]: crate::ZkLoginPublicIdentifier
280    /// [`ZkLoginPublicIdentifier::derive_address_padded`]: crate::ZkLoginPublicIdentifier::derive_address_padded
281    pub fn derive_address(&self) -> Address {
282        use crate::MultisigMemberPublicKey::*;
283
284        let mut hasher = Hasher::new();
285        hasher.update([self.scheme().to_u8()]);
286        hasher.update(self.threshold().to_le_bytes());
287
288        for member in self.members() {
289            match member.public_key() {
290                Ed25519(p) => p.write_into_hasher(&mut hasher),
291                Secp256k1(p) => p.write_into_hasher(&mut hasher),
292                Secp256r1(p) => p.write_into_hasher(&mut hasher),
293                ZkLogin(p) => p.write_into_hasher_padded(&mut hasher),
294                Passkey(p) => p.write_into_hasher(&mut hasher),
295            }
296
297            hasher.update(member.weight().to_le_bytes());
298        }
299
300        let digest = hasher.finalize();
301        Address::new(digest.into_inner())
302    }
303}
304
305impl crate::MultisigAggregatedSignature {
306    pub fn derive_address(&self) -> Address {
307        self.committee().derive_address()
308    }
309}
310
311impl crate::SimpleSignature {
312    pub fn derive_address(&self) -> Address {
313        match self {
314            crate::SimpleSignature::Ed25519 { public_key, .. } => public_key.derive_address(),
315            crate::SimpleSignature::Secp256k1 { public_key, .. } => public_key.derive_address(),
316            crate::SimpleSignature::Secp256r1 { public_key, .. } => public_key.derive_address(),
317        }
318    }
319}
320
321impl crate::UserSignature {
322    pub fn derive_address(&self) -> Address {
323        self.derive_addresses().next().unwrap()
324    }
325
326    pub fn derive_addresses(&self) -> impl ExactSizeIterator<Item = Address> {
327        match self {
328            crate::UserSignature::Simple(simple) => {
329                DerivedAddressIter::new(simple.derive_address())
330            }
331            crate::UserSignature::Multisig(multisig) => {
332                DerivedAddressIter::new(multisig.derive_address())
333            }
334            crate::UserSignature::ZkLogin(zk) => {
335                zk.inputs.public_identifier().internal_derive_addresses()
336            }
337            crate::UserSignature::Passkey(passkey) => {
338                DerivedAddressIter::new(passkey.derive_address())
339            }
340        }
341    }
342}
343
344struct DerivedAddressIter {
345    primary: Option<Address>,
346    extra: Option<Address>,
347}
348
349impl DerivedAddressIter {
350    fn new(primary: Address) -> Self {
351        Self {
352            primary: Some(primary),
353            extra: None,
354        }
355    }
356}
357
358impl ExactSizeIterator for DerivedAddressIter {}
359impl Iterator for DerivedAddressIter {
360    type Item = Address;
361
362    fn next(&mut self) -> Option<Self::Item> {
363        if self.primary.is_some() {
364            self.primary.take()
365        } else if self.extra.is_some() {
366            self.extra.take()
367        } else {
368            None
369        }
370    }
371
372    fn size_hint(&self) -> (usize, Option<usize>) {
373        let len = self.primary.iter().len() + self.extra.iter().len();
374        (len, Some(len))
375    }
376}
377
378#[cfg(feature = "serde")]
379#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
380mod type_digest {
381    use super::Hasher;
382    use crate::Digest;
383
384    impl crate::Object {
385        /// Calculate the digest of this `Object`
386        ///
387        /// This is done by hashing the BCS bytes of this `Object` prefixed
388        pub fn digest(&self) -> Digest {
389            const SALT: &str = "Object::";
390            type_digest(SALT, self)
391        }
392    }
393
394    impl crate::CheckpointSummary {
395        pub fn digest(&self) -> Digest {
396            const SALT: &str = "CheckpointSummary::";
397            type_digest(SALT, self)
398        }
399    }
400
401    impl crate::CheckpointContents {
402        pub fn digest(&self) -> Digest {
403            const SALT: &str = "CheckpointContents::";
404            type_digest(SALT, self)
405        }
406    }
407
408    impl crate::Transaction {
409        pub fn digest(&self) -> Digest {
410            const SALT: &str = "TransactionData::";
411            type_digest(SALT, self)
412        }
413    }
414
415    impl crate::TransactionEffects {
416        pub fn digest(&self) -> Digest {
417            const SALT: &str = "TransactionEffects::";
418            type_digest(SALT, self)
419        }
420    }
421
422    impl crate::TransactionEvents {
423        pub fn digest(&self) -> Digest {
424            const SALT: &str = "TransactionEvents::";
425            type_digest(SALT, self)
426        }
427    }
428
429    fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
430        let mut hasher = Hasher::new();
431        hasher.update(salt);
432        bcs::serialize_into(&mut hasher, ty).unwrap();
433        hasher.finalize()
434    }
435}
436
437#[cfg(feature = "serde")]
438#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
439mod signing_message {
440    use crate::hash::Hasher;
441    use crate::Digest;
442    use crate::Intent;
443    use crate::IntentAppId;
444    use crate::IntentScope;
445    use crate::IntentVersion;
446    use crate::PersonalMessage;
447    use crate::SigningDigest;
448    use crate::Transaction;
449
450    impl Transaction {
451        pub fn signing_digest(&self) -> SigningDigest {
452            const INTENT: Intent = Intent {
453                scope: IntentScope::TransactionData,
454                version: IntentVersion::V0,
455                app_id: IntentAppId::Sui,
456            };
457            let digest = signing_digest(INTENT, self);
458            digest.into_inner()
459        }
460    }
461
462    fn signing_digest<T: serde::Serialize + ?Sized>(intent: Intent, ty: &T) -> Digest {
463        let mut hasher = Hasher::new();
464        hasher.update(intent.to_bytes());
465        bcs::serialize_into(&mut hasher, ty).unwrap();
466        hasher.finalize()
467    }
468
469    impl PersonalMessage<'_> {
470        pub fn signing_digest(&self) -> SigningDigest {
471            const INTENT: Intent = Intent {
472                scope: IntentScope::PersonalMessage,
473                version: IntentVersion::V0,
474                app_id: IntentAppId::Sui,
475            };
476            let digest = signing_digest(INTENT, &self.0);
477            digest.into_inner()
478        }
479    }
480
481    impl crate::CheckpointSummary {
482        pub fn signing_message(&self) -> Vec<u8> {
483            const INTENT: Intent = Intent {
484                scope: IntentScope::CheckpointSummary,
485                version: IntentVersion::V0,
486                app_id: IntentAppId::Sui,
487            };
488            let mut message = Vec::new();
489            message.extend(INTENT.to_bytes());
490            bcs::serialize_into(&mut message, self).unwrap();
491            bcs::serialize_into(&mut message, &self.epoch).unwrap();
492            message
493        }
494    }
495}
496
497/// A 1-byte domain separator for deriving `ObjectId`s in Sui. It is starting from `0xf0` to ensure
498/// no hashing collision for any ObjectId vs Address which is derived as the hash of `flag ||
499/// pubkey`.
500#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
501#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
502#[repr(u8)]
503enum HashingIntent {
504    #[cfg(feature = "serde")]
505    ChildObjectId = 0xf0,
506    RegularObjectId = 0xf1,
507}
508
509impl crate::Address {
510    /// Create an ObjectId from `TransactionDigest` and `count`.
511    ///
512    /// `count` is the number of objects that have been created during a transactions.
513    pub fn derive_id(digest: crate::Digest, count: u64) -> Self {
514        let mut hasher = Hasher::new();
515        hasher.update([HashingIntent::RegularObjectId as u8]);
516        hasher.update(digest);
517        hasher.update(count.to_le_bytes());
518        let digest = hasher.finalize();
519        Self::new(digest.into_inner())
520    }
521
522    /// Derive an ObjectId for a Dynamic Child Object.
523    ///
524    /// hash(parent || len(key) || key || key_type_tag)
525    #[cfg(feature = "serde")]
526    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
527    pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
528        let mut hasher = Hasher::new();
529        hasher.update([HashingIntent::ChildObjectId as u8]);
530        hasher.update(self);
531        hasher.update(
532            u64::try_from(key_bytes.len())
533                .expect("key_bytes must fit into a u64")
534                .to_le_bytes(),
535        );
536        hasher.update(key_bytes);
537        bcs::serialize_into(&mut hasher, key_type_tag)
538            .expect("bcs serialization of `TypeTag` cannot fail");
539        let digest = hasher.finalize();
540
541        Self::new(digest.into_inner())
542    }
543
544    /// Derive the address of a `derived_object`
545    ///
546    /// hash(parent || len(key) || key || DerivedObjectKey(key_type_tag))
547    #[cfg(feature = "serde")]
548    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
549    pub fn derive_object_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
550        use crate::Identifier;
551        use crate::StructTag;
552
553        let struct_tag = StructTag {
554            address: Address::from_static("0x2"),
555            module: Identifier::from_static("derived_object"),
556            name: Identifier::from_static("DerivedObjectKey"),
557            type_params: vec![key_type_tag.clone()],
558        };
559
560        self.derive_dynamic_child_id(&struct_tag.into(), key_bytes)
561    }
562}
563
564#[cfg(test)]
565mod test {
566    use super::HashingIntent;
567    use crate::Address;
568    use crate::SignatureScheme;
569    use crate::TypeTag;
570    use test_strategy::proptest;
571
572    #[cfg(target_arch = "wasm32")]
573    use wasm_bindgen_test::wasm_bindgen_test as test;
574
575    impl HashingIntent {
576        fn from_byte(byte: u8) -> Result<Self, u8> {
577            match byte {
578                0xf0 => Ok(Self::ChildObjectId),
579                0xf1 => Ok(Self::RegularObjectId),
580                invalid => Err(invalid),
581            }
582        }
583    }
584
585    #[proptest]
586    fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
587        SignatureScheme::from_byte(intent as u8).unwrap_err();
588    }
589
590    #[proptest]
591    fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
592        HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
593    }
594
595    #[proptest]
596    fn roundtrip_hashing_intent(intent: HashingIntent) {
597        assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
598    }
599
600    // Snapshot tests that match the on-chain `derive_address` logic.
601    // These snapshots can also be found in `derived_object_tests.move` unit tests.
602    #[test]
603    #[cfg(feature = "serde")]
604    fn test_derive_object_snapshot() {
605        // Our key is `UID, Vec<u8>, b"foo"`
606        let key_bytes = bcs::to_bytes("foo").unwrap();
607        let key_type_tag = TypeTag::Vector(Box::new(TypeTag::U8));
608
609        let id = Address::from_hex("0x2")
610            .unwrap()
611            .derive_object_id(&key_type_tag, &key_bytes);
612
613        assert_eq!(
614            id,
615            Address::from_hex("0xa2b411aa9588c398d8e3bc97dddbdd430b5ded7f81545d05e33916c3ca0f30c3")
616                .unwrap()
617        );
618    }
619}