1use blake2::Digest as DigestTrait;
6
7use crate::{Address, Digest, PublicKeyExt};
8
9type Blake2b256 = blake2::Blake2b<blake2::digest::consts::U32>;
10
11#[derive(Debug, Default)]
13pub struct Hasher(Blake2b256);
14
15impl Hasher {
16 pub fn new() -> Self {
18 Self(Blake2b256::new())
19 }
20
21 pub fn update<T: AsRef<[u8]>>(&mut self, data: T) {
23 self.0.update(data)
24 }
25
26 pub fn finalize(self) -> Digest {
29 Digest::new(self.0.finalize().into())
30 }
31
32 pub fn digest<T: AsRef<[u8]>>(data: T) -> Digest {
35 let mut hasher = Self::new();
36 hasher.update(data);
37 hasher.finalize()
38 }
39}
40
41impl std::io::Write for Hasher {
42 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
43 self.0.write(buf)
44 }
45
46 fn flush(&mut self) -> std::io::Result<()> {
47 self.0.flush()
48 }
49}
50
51fn derive_address_from(write: impl FnOnce(&mut Hasher)) -> Address {
53 let mut hasher = Hasher::new();
54 write(&mut hasher);
55 Address::new(hasher.finalize().into_inner())
56}
57
58impl crate::Ed25519PublicKey {
59 pub fn derive_address(&self) -> Address {
79 derive_address_from(|hasher| self.write_into_hasher(hasher))
80 }
81
82 fn write_into_hasher(&self, hasher: &mut Hasher) {
83 hasher.update(self.inner());
84 }
85}
86
87impl From<crate::Ed25519PublicKey> for Address {
88 fn from(public_key: crate::Ed25519PublicKey) -> Self {
89 public_key.derive_address()
90 }
91}
92
93impl From<&crate::Ed25519PublicKey> for Address {
94 fn from(public_key: &crate::Ed25519PublicKey) -> Self {
95 public_key.derive_address()
96 }
97}
98
99impl crate::Secp256k1PublicKey {
100 pub fn derive_address(&self) -> Address {
122 derive_address_from(|hasher| self.write_into_hasher(hasher))
123 }
124
125 fn write_into_hasher(&self, hasher: &mut Hasher) {
126 hasher.update([self.scheme().to_u8()]);
127 hasher.update(self.inner());
128 }
129}
130
131impl From<crate::Secp256k1PublicKey> for Address {
132 fn from(public_key: crate::Secp256k1PublicKey) -> Self {
133 public_key.derive_address()
134 }
135}
136
137impl From<&crate::Secp256k1PublicKey> for Address {
138 fn from(public_key: &crate::Secp256k1PublicKey) -> Self {
139 public_key.derive_address()
140 }
141}
142
143impl crate::Secp256r1PublicKey {
144 pub fn derive_address(&self) -> Address {
166 derive_address_from(|hasher| self.write_into_hasher(hasher))
167 }
168
169 fn write_into_hasher(&self, hasher: &mut Hasher) {
170 hasher.update([self.scheme().to_u8()]);
171 hasher.update(self.inner());
172 }
173}
174
175impl From<crate::Secp256r1PublicKey> for Address {
176 fn from(public_key: crate::Secp256r1PublicKey) -> Self {
177 public_key.derive_address()
178 }
179}
180
181impl From<&crate::Secp256r1PublicKey> for Address {
182 fn from(public_key: &crate::Secp256r1PublicKey) -> Self {
183 public_key.derive_address()
184 }
185}
186
187impl crate::PasskeyPublicKey {
188 pub fn derive_address(&self) -> Address {
196 derive_address_from(|hasher| self.write_into_hasher(hasher))
197 }
198
199 fn write_into_hasher(&self, hasher: &mut Hasher) {
200 hasher.update([self.scheme().to_u8()]);
201 hasher.update(self.inner().inner());
202 }
203}
204
205impl From<crate::PasskeyPublicKey> for Address {
206 fn from(public_key: crate::PasskeyPublicKey) -> Self {
207 public_key.derive_address()
208 }
209}
210
211impl From<&crate::PasskeyPublicKey> for Address {
212 fn from(public_key: &crate::PasskeyPublicKey) -> Self {
213 public_key.derive_address()
214 }
215}
216
217impl crate::PublicKey {
218 pub fn derive_address(&self) -> Address {
223 derive_address_from(|hasher| self.write_into_hasher(hasher))
224 }
225
226 fn write_into_hasher(&self, hasher: &mut Hasher) {
227 match self {
228 Self::Ed25519(pk) => pk.write_into_hasher(hasher),
229 Self::Secp256k1(pk) => pk.write_into_hasher(hasher),
230 Self::Secp256r1(pk) => pk.write_into_hasher(hasher),
231 Self::Passkey(pk) => pk.write_into_hasher(hasher),
232 }
233 }
234}
235
236impl From<crate::PublicKey> for Address {
237 fn from(public_key: crate::PublicKey) -> Self {
238 public_key.derive_address()
239 }
240}
241
242impl From<&crate::PublicKey> for Address {
243 fn from(public_key: &crate::PublicKey) -> Self {
244 public_key.derive_address()
245 }
246}
247
248impl crate::MultisigCommittee {
249 pub fn derive_address(&self) -> Address {
260 derive_address_from(|hasher| {
261 hasher.update([self.scheme().to_u8()]);
262 hasher.update(self.threshold().to_le_bytes());
263
264 for member in self.members() {
265 member.public_key().write_into_hasher(hasher);
266 hasher.update(member.weight().to_le_bytes());
267 }
268 })
269 }
270}
271
272impl From<crate::MultisigCommittee> for Address {
273 fn from(committee: crate::MultisigCommittee) -> Self {
274 committee.derive_address()
275 }
276}
277
278impl From<&crate::MultisigCommittee> for Address {
279 fn from(committee: &crate::MultisigCommittee) -> Self {
280 committee.derive_address()
281 }
282}
283
284impl crate::UserSignature {
285 pub fn derive_address(&self) -> Address {
287 match self {
288 Self::Simple(simple) => simple.to_public_key().derive_address(),
289 Self::Multisig(multisig) => multisig.committee().derive_address(),
290 Self::PasskeyAuthenticator(passkey) => passkey.public_key().derive_address(),
291 Self::MoveAuthenticator(move_authenticator) => move_authenticator.address(),
292 }
293 }
294}
295
296impl From<crate::UserSignature> for Address {
297 fn from(signature: crate::UserSignature) -> Self {
298 signature.derive_address()
299 }
300}
301
302impl From<&crate::UserSignature> for Address {
303 fn from(signature: &crate::UserSignature) -> Self {
304 signature.derive_address()
305 }
306}
307
308#[cfg(feature = "serde")]
312#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
313#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
314#[error("no signature found for address {address}")]
315pub struct MissingSignatureError {
316 pub address: Address,
318}
319
320#[cfg(feature = "serde")]
324#[derive(serde::Serialize)]
325enum TransactionRef<'a> {
326 V1(&'a crate::TransactionV1),
327}
328
329#[cfg(feature = "serde")]
330#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
331mod type_digest {
332 use super::Hasher;
333 use crate::{
334 CheckpointContentsDigest, CheckpointDigest, Digest, ObjectDigest, SenderSignedDataDigest,
335 TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest,
336 };
337
338 impl crate::Object {
339 pub fn digest(&self) -> ObjectDigest {
344 const SALT: &str = "Object::";
345 type_digest(SALT, self).into()
346 }
347 }
348
349 impl crate::CheckpointSummary {
350 pub fn digest(&self) -> CheckpointDigest {
351 const SALT: &str = "CheckpointSummary::";
352 type_digest(SALT, self).into()
353 }
354 }
355
356 impl crate::CheckpointContents {
357 pub fn digest(&self) -> CheckpointContentsDigest {
358 const SALT: &str = "CheckpointContents::";
359 type_digest(SALT, self).into()
360 }
361 }
362
363 impl crate::Transaction {
364 pub fn digest(&self) -> TransactionDigest {
365 const SALT: &str = "TransactionData::";
366 type_digest(SALT, self).into()
367 }
368 }
369
370 impl crate::TransactionV1 {
371 pub fn digest(&self) -> TransactionDigest {
372 const SALT: &str = "TransactionData::";
373 type_digest(SALT, &super::TransactionRef::V1(self)).into()
374 }
375 }
376
377 impl crate::SenderSignedTransaction {
378 pub fn full_message_digest(&self) -> SenderSignedDataDigest {
384 let mut hasher = Hasher::new();
385 bcs::serialize_into(&mut hasher, self).expect("bcs serialization failed");
386 hasher.finalize().into()
387 }
388 }
389
390 impl crate::TransactionEffects {
391 pub fn digest(&self) -> TransactionEffectsDigest {
392 const SALT: &str = "TransactionEffects::";
393 type_digest(SALT, self).into()
394 }
395 }
396
397 impl crate::TransactionEvents {
398 pub fn digest(&self) -> TransactionEventsDigest {
399 const SALT: &str = "TransactionEvents::";
400 type_digest(SALT, self).into()
401 }
402 }
403
404 impl crate::MoveAuthenticator {
405 pub fn digest(&self) -> Digest {
406 const SALT: &str = "MoveAuthenticator::";
407 type_digest(SALT, self)
408 }
409 }
410
411 impl crate::UserSignature {
412 pub fn auth_digest(&self) -> Digest {
420 match self {
421 Self::MoveAuthenticator(authenticator) => authenticator.digest(),
422 Self::Simple(_) | Self::Multisig(_) | Self::PasskeyAuthenticator(_) => {
423 Hasher::digest(self.to_bytes())
424 }
425 }
426 }
427 }
428
429 impl crate::SignedTransaction {
430 pub fn compute_auth_digests(
438 &self,
439 ) -> Result<(Digest, Option<Digest>), super::MissingSignatureError> {
440 let crate::Transaction::V1(transaction) = &self.transaction;
441
442 let digest_for_address = |address| {
443 self.signatures
444 .iter()
445 .find(|signature| signature.derive_address() == address)
446 .map(crate::UserSignature::auth_digest)
447 .ok_or(super::MissingSignatureError { address })
448 };
449
450 let sender_auth_digest = digest_for_address(transaction.sender)?;
451 let gas_owner = transaction.gas_payment.owner;
452 let sponsor_auth_digest = if gas_owner != transaction.sender {
453 Some(digest_for_address(gas_owner)?)
454 } else {
455 None
456 };
457
458 Ok((sender_auth_digest, sponsor_auth_digest))
459 }
460 }
461
462 fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
463 let mut hasher = Hasher::new();
464 hasher.update(salt);
465 bcs::serialize_into(&mut hasher, ty).expect("bcs serialization failed");
466 hasher.finalize()
467 }
468}
469
470#[cfg(feature = "serde")]
471#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
472mod signing_message {
473 use crate::{
474 Intent, IntentMessage, IntentScope, PersonalMessage, SigningDigest, Transaction,
475 TransactionV1, hash::Hasher,
476 };
477
478 impl Transaction {
479 pub fn signing_digest(&self) -> SigningDigest {
480 self.intent_message().signing_digest()
481 }
482
483 pub fn signing_digest_hex(&self) -> String {
484 hex::encode(self.signing_digest())
485 }
486 }
487
488 impl TransactionV1 {
489 pub fn signing_digest(&self) -> SigningDigest {
490 IntentMessage::new(Intent::iota_transaction(), super::TransactionRef::V1(self))
491 .signing_digest()
492 }
493
494 pub fn signing_digest_hex(&self) -> String {
495 hex::encode(self.signing_digest())
496 }
497 }
498
499 impl PersonalMessage<'_> {
500 pub fn signing_digest(&self) -> SigningDigest {
501 IntentMessage::new(Intent::personal_message(), &self.0).signing_digest()
502 }
503
504 pub fn signing_digest_hex(&self) -> String {
505 hex::encode(self.signing_digest())
506 }
507 }
508
509 impl crate::CheckpointSummary {
510 pub fn signing_message(&self) -> Vec<u8> {
511 let mut message = Vec::new();
512 message.extend(Intent::iota_app(IntentScope::CheckpointSummary).to_bytes());
513 bcs::serialize_into(&mut message, self).expect("bcs serialization failed");
514 bcs::serialize_into(&mut message, &self.epoch).expect("bcs serialization failed");
515 message
516 }
517
518 pub fn signing_message_hex(&self) -> String {
519 hex::encode(self.signing_message())
520 }
521 }
522
523 impl<T> IntentMessage<T>
524 where
525 T: serde::Serialize,
526 {
527 pub fn signing_digest(&self) -> SigningDigest {
528 let mut hasher = Hasher::new();
529 bcs::serialize_into(&mut hasher, self).expect("bcs serialization failed");
530 hasher.finalize().into()
531 }
532 }
533}
534
535#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
539#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
540#[repr(u8)]
541enum HashingIntent {
542 #[cfg(feature = "serde")]
543 ChildObjectId = 0xf0,
544 RegularObjectId = 0xf1,
545}
546
547impl crate::ObjectId {
548 pub fn derive_id(digest: crate::TransactionDigest, count: u64) -> Self {
553 let mut hasher = Hasher::new();
554 hasher.update([HashingIntent::RegularObjectId as u8]);
555 hasher.update(digest);
556 hasher.update(count.to_le_bytes());
557 let digest = hasher.finalize();
558 Self::new(digest.into_inner())
559 }
560
561 #[cfg(feature = "serde")]
565 #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
566 pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
567 let mut hasher = Hasher::new();
568 hasher.update([HashingIntent::ChildObjectId as u8]);
569 hasher.update(self);
570 hasher.update(
571 u64::try_from(key_bytes.len())
572 .expect("key_bytes must fit into a u64")
573 .to_le_bytes(),
574 );
575 hasher.update(key_bytes);
576 bcs::serialize_into(&mut hasher, key_type_tag)
577 .expect("bcs serialization of `TypeTag` cannot fail");
578 let digest = hasher.finalize();
579
580 Self::new(digest.into_inner())
581 }
582
583 #[cfg(feature = "serde")]
587 #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
588 pub fn derive_object_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
589 let wrapper_type_tag = crate::TypeTag::Struct(Box::new(crate::StructTag::new(
592 crate::Address::FRAMEWORK,
593 crate::Identifier::DERIVED_OBJECT_MODULE,
594 crate::Identifier::DERIVED_OBJECT_KEY,
595 vec![key_type_tag.clone()],
596 )));
597
598 self.derive_dynamic_child_id(&wrapper_type_tag, key_bytes)
599 }
600}
601
602#[cfg(all(test, feature = "proptest"))]
603mod tests {
604 use test_strategy::proptest;
605
606 use super::HashingIntent;
607 use crate::SignatureScheme;
608
609 impl HashingIntent {
610 fn from_byte(byte: u8) -> Result<Self, u8> {
611 match byte {
612 0xf0 => Ok(Self::ChildObjectId),
613 0xf1 => Ok(Self::RegularObjectId),
614 invalid => Err(invalid),
615 }
616 }
617 }
618
619 #[proptest]
620 fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
621 SignatureScheme::from_byte(intent as u8).unwrap_err();
622 }
623
624 #[proptest]
625 fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
626 HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
627 }
628
629 #[proptest]
630 fn roundtrip_hashing_intent(intent: HashingIntent) {
631 assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
632 }
633
634 #[cfg(feature = "serde")]
637 #[proptest]
638 fn transaction_v1_digests_match_transaction(transaction: crate::TransactionV1) {
639 let digest = transaction.digest();
640 let signing_digest = transaction.signing_digest();
641 let transaction = crate::Transaction::V1(transaction);
642 assert_eq!(digest, transaction.digest());
643 assert_eq!(signing_digest, transaction.signing_digest());
644 }
645}
646
647#[cfg(all(test, feature = "serde"))]
648mod serde_tests {
649 use std::str::FromStr;
650
651 use base64ct::Encoding;
652
653 use crate::{Address, Identifier, ObjectId, StructTag, TypeTag, UserSignature};
654
655 #[test]
658 fn test_address_from_user_signature() {
659 let fixtures = [
660 (
662 "AO/2qtqkYPqq3UzI7dVLmqt7dy2B5Ta2Hv7F1ssYO9auPyrcRGpawALnzyNyPBT/v/PIxSbNTskTs+ts6kGtkQYNfas1jI2tqk76AEmnWwdDZVWxCjaCGbtoD3BXE0nXdQ==",
663 "0xebb23f93d022ac213e99ac7d85b7f7e1e4a18f045b379755565cffa08804c9a1",
664 ),
665 (
667 "ASWDBpw4ETzLiQlwS0kDKJA9PK47V8fp4e9S+7bpaJXfd4HfZ5oLSXTyezaHTRfcryQn3mdshteXwrEvj/ZAmiUCDhfNWTnkaxlmQZaM11mRC6JXfif6c/3jh225vsW86ys=",
668 "0xd9607cd03428c904949572b51471e7a9f60019aeb9a3d7ee5e72921cab8e8be7",
669 ),
670 (
672 "Ai1Hdv4ZtEslPN424BGG5+6BhzGrp4d4ykoyiQhG2hDzbGDdCPgXVLLv26sfFP77nhJi78OAWfD+ytdQRyYPpKcDR/uvI/A4q8TDCKJxEXoqTP+u3bxf+Bx1F7xsdKfttDA=",
673 "0x600b1081644fe46f76da3bdc19f8743b9f04458516364374c7d82959e790c19e",
674 ),
675 (
677 "AwIArJTVgdzrD8V+em1zREsmT0jD/fduXgh/zo+Z+lZTYoBLZpaAw9PNO4YNDYBTK9vT646klVBV4ntBmFcYzdDVDQGia/pl6A/nh/uixuM1hc84fokvTk37j/DNq3/OtckvsV1Mg84ygJ7UXsuohtKjHc+zfuu2uwcjh2lOPVBgZotQAwADAA19qzWMja2qTvoASadbB0NlVbEKNoIZu2gPcFcTSdd1AQECDhfNWTnkaxlmQZaM11mRC6JXfif6c/3jh225vsW86ysBAgNH+68j8DirxMMIonEReipM/67dvF/4HHUXvGx0p+20MAECAA==",
678 "0x34b66b6d090baea4effa1d1bf22e1adff466c3fb9425ded9c5bbb605865b2560",
679 ),
680 (
682 "BgByeyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsIm9yaWdpbiI6Imh0dHBzOi8vdGVzdC5pb3RhLm9yZyJ9YgJsW7qQ7IeOJMhUEgb7PFfoHEFCbk7qLZ27Nhqxpq8OLCapbkIG0nOO1iQgY7KjGPykr/gcDF+FtKxxcebcDOPXA0f7ryPwOKvEwwiicRF6Kkz/rt28X/gcdRe8bHSn7bQw",
683 "0x79214f7090abbaf3d14fdbe357ea794650f41783ea64ece65314682144f658a7",
684 ),
685 (
687 "BwAAAAEB7mKDoFTKsmYgpTHRjEnqNWAjnP2LqDo5O7qn1+7SCCoBAAAAAAAAAAA=",
688 "0xee6283a054cab26620a531d18c49ea3560239cfd8ba83a393bbaa7d7eed2082a",
689 ),
690 ];
691
692 for (b64, expected) in fixtures {
693 let sig = UserSignature::from_base64(b64).unwrap();
694 assert_eq!(sig.derive_address().to_string(), expected);
695 }
696
697 let zklogin_b64 = base64ct::Base64::encode_string(&[0x05u8, 0, 0, 0]);
700 assert!(UserSignature::from_base64(&zklogin_b64).is_err());
701 }
702
703 #[test]
707 fn test_derive_object_id_snapshot() {
708 let key_bytes = bcs::to_bytes("foo".as_bytes()).unwrap();
709 let key_type_tag = TypeTag::Vector(Box::new(TypeTag::U8));
710
711 let id = ObjectId::from_str("0x2")
712 .unwrap()
713 .derive_object_id(&key_type_tag, &key_bytes);
714
715 assert_eq!(
716 id,
717 ObjectId::from_str(
718 "0xa2b411aa9588c398d8e3bc97dddbdd430b5ded7f81545d05e33916c3ca0f30c3"
719 )
720 .unwrap()
721 );
722 }
723
724 #[test]
725 fn test_derive_object_id_with_struct_key_snapshot() {
726 #[derive(serde::Serialize)]
727 struct DemoStruct {
728 value: u64,
729 }
730
731 let key_bytes = bcs::to_bytes(&DemoStruct { value: 1 }).unwrap();
732 let key_type_tag = TypeTag::Struct(Box::new(StructTag::new(
733 Address::FRAMEWORK,
734 Identifier::from_static("derived_object_tests"),
735 Identifier::from_static("DemoStruct"),
736 vec![],
737 )));
738
739 let id = ObjectId::from_str("0x2")
740 .unwrap()
741 .derive_object_id(&key_type_tag, &key_bytes);
742
743 assert_eq!(
744 id,
745 ObjectId::from_str(
746 "0x20c58d8790a5d2214c159c23f18a5fdc347211e511186353e785ad543abcea6b"
747 )
748 .unwrap()
749 );
750 }
751
752 #[test]
753 fn test_derive_object_id_with_generic_struct_key_snapshot() {
754 #[derive(serde::Serialize)]
755 struct GenericStruct<T> {
756 value: T,
757 }
758
759 let key_bytes = bcs::to_bytes(&GenericStruct::<u64> { value: 1 }).unwrap();
760 let key_type_tag = TypeTag::Struct(Box::new(StructTag::new(
761 Address::FRAMEWORK,
762 Identifier::from_static("derived_object_tests"),
763 Identifier::from_static("GenericStruct"),
764 vec![TypeTag::U64],
765 )));
766
767 let id = ObjectId::from_str("0x2")
768 .unwrap()
769 .derive_object_id(&key_type_tag, &key_bytes);
770
771 assert_eq!(
772 id,
773 ObjectId::from_str(
774 "0xb497b8dcf1e297ae5fa69c040e4a08ef8240d5373bbc9d6b686ffbd7dfe04cbe"
775 )
776 .unwrap()
777 );
778 }
779}