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 }
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 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#[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 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 #[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}