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::Digest;
284
285 impl crate::Object {
286 pub fn digest(&self) -> Digest {
290 const SALT: &str = "Object::";
291 type_digest(SALT, self)
292 }
293 }
294
295 impl crate::CheckpointSummary {
296 pub fn digest(&self) -> Digest {
297 const SALT: &str = "CheckpointSummary::";
298 type_digest(SALT, self)
299 }
300 }
301
302 impl crate::CheckpointContents {
303 pub fn digest(&self) -> Digest {
304 const SALT: &str = "CheckpointContents::";
305 type_digest(SALT, self)
306 }
307 }
308
309 impl crate::Transaction {
310 pub fn digest(&self) -> Digest {
311 const SALT: &str = "TransactionData::";
312 type_digest(SALT, self)
313 }
314 }
315
316 impl crate::TransactionEffects {
317 pub fn digest(&self) -> Digest {
318 const SALT: &str = "TransactionEffects::";
319 type_digest(SALT, self)
320 }
321 }
322
323 impl crate::TransactionEvents {
324 pub fn digest(&self) -> Digest {
325 const SALT: &str = "TransactionEvents::";
326 type_digest(SALT, self)
327 }
328 }
329
330 fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
331 let mut hasher = Hasher::new();
332 hasher.update(salt);
333 bcs::serialize_into(&mut hasher, ty).unwrap();
334 hasher.finalize()
335 }
336}
337
338#[cfg(feature = "serde")]
339#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
340mod signing_message {
341 use crate::hash::Hasher;
342 use crate::Digest;
343 use crate::Intent;
344 use crate::IntentAppId;
345 use crate::IntentScope;
346 use crate::IntentVersion;
347 use crate::PersonalMessage;
348 use crate::SigningDigest;
349 use crate::Transaction;
350
351 impl Transaction {
352 pub fn signing_digest(&self) -> SigningDigest {
353 const INTENT: Intent = Intent {
354 scope: IntentScope::TransactionData,
355 version: IntentVersion::V0,
356 app_id: IntentAppId::Sui,
357 };
358 let digest = signing_digest(INTENT, self);
359 digest.into_inner()
360 }
361 }
362
363 fn signing_digest<T: serde::Serialize + ?Sized>(intent: Intent, ty: &T) -> Digest {
364 let mut hasher = Hasher::new();
365 hasher.update(intent.to_bytes());
366 bcs::serialize_into(&mut hasher, ty).unwrap();
367 hasher.finalize()
368 }
369
370 impl PersonalMessage<'_> {
371 pub fn signing_digest(&self) -> SigningDigest {
372 const INTENT: Intent = Intent {
373 scope: IntentScope::PersonalMessage,
374 version: IntentVersion::V0,
375 app_id: IntentAppId::Sui,
376 };
377 let digest = signing_digest(INTENT, &self.0);
378 digest.into_inner()
379 }
380 }
381
382 impl crate::CheckpointSummary {
383 pub fn signing_message(&self) -> Vec<u8> {
384 const INTENT: Intent = Intent {
385 scope: IntentScope::CheckpointSummary,
386 version: IntentVersion::V0,
387 app_id: IntentAppId::Sui,
388 };
389 let mut message = Vec::new();
390 message.extend(INTENT.to_bytes());
391 bcs::serialize_into(&mut message, self).unwrap();
392 bcs::serialize_into(&mut message, &self.epoch).unwrap();
393 message
394 }
395 }
396}
397
398#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
402#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
403#[repr(u8)]
404enum HashingIntent {
405 #[cfg(feature = "serde")]
406 ChildObjectId = 0xf0,
407 RegularObjectId = 0xf1,
408}
409
410impl crate::Address {
411 pub fn derive_id(digest: crate::Digest, count: u64) -> Self {
415 let mut hasher = Hasher::new();
416 hasher.update([HashingIntent::RegularObjectId as u8]);
417 hasher.update(digest);
418 hasher.update(count.to_le_bytes());
419 let digest = hasher.finalize();
420 Self::new(digest.into_inner())
421 }
422
423 #[cfg(feature = "serde")]
427 #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
428 pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
429 let mut hasher = Hasher::new();
430 hasher.update([HashingIntent::ChildObjectId as u8]);
431 hasher.update(self);
432 hasher.update(
433 u64::try_from(key_bytes.len())
434 .expect("key_bytes must fit into a u64")
435 .to_le_bytes(),
436 );
437 hasher.update(key_bytes);
438 bcs::serialize_into(&mut hasher, key_type_tag)
439 .expect("bcs serialization of `TypeTag` cannot fail");
440 let digest = hasher.finalize();
441
442 Self::new(digest.into_inner())
443 }
444}
445
446#[cfg(test)]
447mod test {
448 use super::HashingIntent;
449 use crate::SignatureScheme;
450 use test_strategy::proptest;
451
452 #[cfg(target_arch = "wasm32")]
453 use wasm_bindgen_test::wasm_bindgen_test as test;
454
455 impl HashingIntent {
456 fn from_byte(byte: u8) -> Result<Self, u8> {
457 match byte {
458 0xf0 => Ok(Self::ChildObjectId),
459 0xf1 => Ok(Self::RegularObjectId),
460 invalid => Err(invalid),
461 }
462 }
463 }
464
465 #[proptest]
466 fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
467 SignatureScheme::from_byte(intent as u8).unwrap_err();
468 }
469
470 #[proptest]
471 fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
472 HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
473 }
474
475 #[proptest]
476 fn roundtrip_hashing_intent(intent: HashingIntent) {
477 assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
478 }
479}