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 ExactSizeIterator<Item = Address> {
202 self.internal_derive_addresses()
203 }
204
205 fn internal_derive_addresses(&self) -> DerivedAddressIter {
207 let primary = self.derive_address_padded();
208 let mut addresses = DerivedAddressIter::new(primary);
209
210 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 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 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 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#[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 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 #[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 #[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 #[test]
603 #[cfg(feature = "serde")]
604 fn test_derive_object_snapshot() {
605 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}