1use crate::Address;
2use crate::Digest;
3
4use blake2::Digest as DigestTrait;
5
6type Blake2b256 = blake2::Blake2b<blake2::digest::consts::U32>;
7
8#[derive(Debug, Default)]
10pub struct Hasher(Blake2b256);
11
12impl Hasher {
13 pub fn new() -> Self {
15 Self(Blake2b256::new())
16 }
17
18 pub fn update<T: AsRef<[u8]>>(&mut self, data: T) {
20 self.0.update(data)
21 }
22
23 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 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 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 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 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 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]); hasher.update(self.iss());
178 hasher.update(self.address_seed().padded());
179 }
180
181 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]); 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 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 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 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 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 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#[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 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 #[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}