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 let mut buf = [0; Digest::LENGTH];
30 let result = self.0.finalize();
31
32 buf.copy_from_slice(&result);
33
34 Digest::new(buf)
35 }
36
37 pub fn digest<T: AsRef<[u8]>>(data: T) -> Digest {
40 let mut hasher = Self::new();
41 hasher.update(data);
42 hasher.finalize()
43 }
44}
45
46impl std::io::Write for Hasher {
47 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
48 self.0.write(buf)
49 }
50
51 fn flush(&mut self) -> std::io::Result<()> {
52 self.0.flush()
53 }
54}
55
56impl crate::Ed25519PublicKey {
57 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.inner());
85 }
86}
87
88impl crate::Secp256k1PublicKey {
89 pub fn derive_address(&self) -> Address {
111 let mut hasher = Hasher::new();
112 self.write_into_hasher(&mut hasher);
113 let digest = hasher.finalize();
114 Address::new(digest.into_inner())
115 }
116
117 fn write_into_hasher(&self, hasher: &mut Hasher) {
118 hasher.update([self.scheme().to_u8()]);
119 hasher.update(self.inner());
120 }
121}
122
123impl crate::Secp256r1PublicKey {
124 pub fn derive_address(&self) -> Address {
146 let mut hasher = Hasher::new();
147 self.write_into_hasher(&mut hasher);
148 let digest = hasher.finalize();
149 Address::new(digest.into_inner())
150 }
151
152 fn write_into_hasher(&self, hasher: &mut Hasher) {
153 hasher.update([self.scheme().to_u8()]);
154 hasher.update(self.inner());
155 }
156}
157
158impl crate::ZkLoginPublicIdentifier {
159 pub fn derive_address_padded(&self) -> Address {
166 let mut hasher = Hasher::new();
167 self.write_into_hasher_padded(&mut hasher);
168 let digest = hasher.finalize();
169 Address::new(digest.into_inner())
170 }
171
172 fn write_into_hasher_padded(&self, hasher: &mut Hasher) {
173 hasher.update([self.scheme().to_u8()]);
174 hasher.update([self.iss().len() as u8]); hasher.update(self.iss());
176 hasher.update(self.address_seed().padded());
177 }
178
179 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> {
204 let main_address = self.derive_address_padded();
205 let mut addresses = [Some(main_address), None];
206 if self.address_seed().padded()[0] == 0 {
209 let secondary_address = self.derive_address_unpadded();
210
211 addresses[1] = Some(secondary_address);
212 }
213
214 addresses.into_iter().flatten()
215 }
216}
217
218impl crate::PasskeyPublicKey {
219 pub fn derive_address(&self) -> Address {
227 let mut hasher = Hasher::new();
228 self.write_into_hasher(&mut hasher);
229 let digest = hasher.finalize();
230 Address::new(digest.into_inner())
231 }
232
233 fn write_into_hasher(&self, hasher: &mut Hasher) {
234 hasher.update([self.scheme().to_u8()]);
235 hasher.update(self.inner().inner());
236 }
237}
238
239impl crate::MultisigCommittee {
240 pub fn derive_address(&self) -> Address {
258 use crate::MultisigMemberPublicKey::*;
259
260 let mut hasher = Hasher::new();
261 hasher.update([self.scheme().to_u8()]);
262 hasher.update(self.threshold().to_le_bytes());
263
264 for member in self.members() {
265 match member.public_key() {
266 Ed25519(p) => p.write_into_hasher(&mut hasher),
267 Secp256k1(p) => p.write_into_hasher(&mut hasher),
268 Secp256r1(p) => p.write_into_hasher(&mut hasher),
269 ZkLogin(p) => p.write_into_hasher_padded(&mut hasher),
270 }
271
272 hasher.update(member.weight().to_le_bytes());
273 }
274
275 let digest = hasher.finalize();
276 Address::new(digest.into_inner())
277 }
278}
279
280#[cfg(feature = "serde")]
281#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
282mod type_digest {
283 use base64ct::Encoding;
284
285 use super::Hasher;
286 use crate::Digest;
287
288 impl crate::Object {
289 pub fn digest(&self) -> Digest {
294 const SALT: &str = "Object::";
295 type_digest(SALT, self)
296 }
297 }
298
299 impl crate::CheckpointSummary {
300 pub fn digest(&self) -> Digest {
301 const SALT: &str = "CheckpointSummary::";
302 type_digest(SALT, self)
303 }
304 }
305
306 impl crate::CheckpointContents {
307 pub fn digest(&self) -> Digest {
308 const SALT: &str = "CheckpointContents::";
309 type_digest(SALT, self)
310 }
311 }
312
313 impl crate::Transaction {
314 pub fn digest(&self) -> Digest {
315 const SALT: &str = "TransactionData::";
316 type_digest(SALT, &self)
317 }
318
319 pub fn to_bcs(&self) -> Vec<u8> {
321 bcs::to_bytes(self).expect("bcs serialization failed")
322 }
323
324 pub fn to_base64(&self) -> String {
326 base64ct::Base64::encode_string(&self.to_bcs())
327 }
328
329 pub fn from_bcs(bytes: &[u8]) -> Result<Self, bcs::Error> {
331 bcs::from_bytes::<Self>(bytes)
332 }
333
334 pub fn from_base64(bytes: &str) -> Result<Self, bcs::Error> {
336 let decoded = base64ct::Base64::decode_vec(bytes)
337 .map_err(|e| bcs::Error::Custom(e.to_string()))?;
338 Self::from_bcs(&decoded)
339 }
340 }
341
342 impl crate::TransactionV1 {
343 pub fn digest(&self) -> Digest {
344 const SALT: &str = "TransactionData::";
345 type_digest(SALT, &crate::Transaction::V1(self.clone()))
346 }
347
348 pub fn to_bcs(&self) -> Vec<u8> {
350 bcs::to_bytes(self).expect("bcs serialization failed")
351 }
352
353 pub fn to_base64(&self) -> String {
355 base64ct::Base64::encode_string(&self.to_bcs())
356 }
357
358 pub fn from_bcs(bytes: &[u8]) -> Result<Self, bcs::Error> {
360 bcs::from_bytes::<Self>(bytes)
361 }
362
363 pub fn from_base64(bytes: &str) -> Result<Self, bcs::Error> {
365 let decoded = base64ct::Base64::decode_vec(bytes)
366 .map_err(|e| bcs::Error::Custom(e.to_string()))?;
367 Self::from_bcs(&decoded)
368 }
369 }
370
371 impl crate::TransactionEffects {
372 pub fn digest(&self) -> Digest {
373 const SALT: &str = "TransactionEffects::";
374 type_digest(SALT, self)
375 }
376 }
377
378 impl crate::TransactionEvents {
379 pub fn digest(&self) -> Digest {
380 const SALT: &str = "TransactionEvents::";
381 type_digest(SALT, self)
382 }
383 }
384
385 fn type_digest<T: serde::Serialize>(salt: &str, ty: &T) -> Digest {
386 let mut hasher = Hasher::new();
387 hasher.update(salt);
388 bcs::serialize_into(&mut hasher, ty).unwrap();
389 hasher.finalize()
390 }
391}
392
393#[cfg(feature = "serde")]
394#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
395mod signing_message {
396 use crate::{
397 Digest, Intent, IntentAppId, IntentScope, IntentVersion, PersonalMessage, SigningDigest,
398 Transaction, TransactionV1, hash::Hasher,
399 };
400
401 impl Transaction {
402 pub fn signing_digest(&self) -> SigningDigest {
403 const INTENT: Intent = Intent {
404 scope: IntentScope::TransactionData,
405 version: IntentVersion::V0,
406 app_id: IntentAppId::Iota,
407 };
408 let digest = signing_digest(INTENT, self);
409 digest.into_inner()
410 }
411
412 pub fn signing_digest_hex(&self) -> String {
413 hex::encode(self.signing_digest())
414 }
415 }
416
417 impl TransactionV1 {
418 pub fn signing_digest(&self) -> SigningDigest {
419 const INTENT: Intent = Intent {
420 scope: IntentScope::TransactionData,
421 version: IntentVersion::V0,
422 app_id: IntentAppId::Iota,
423 };
424 let digest = signing_digest(INTENT, &Transaction::V1(self.clone()));
425 digest.into_inner()
426 }
427
428 pub fn signing_digest_hex(&self) -> String {
429 hex::encode(self.signing_digest())
430 }
431 }
432
433 fn signing_digest<T: serde::Serialize + ?Sized>(intent: Intent, ty: &T) -> Digest {
434 let mut hasher = Hasher::new();
435 hasher.update(intent.to_bytes());
436 bcs::serialize_into(&mut hasher, ty).unwrap();
437 hasher.finalize()
438 }
439
440 impl PersonalMessage<'_> {
441 pub fn signing_digest(&self) -> SigningDigest {
442 const INTENT: Intent = Intent {
443 scope: IntentScope::PersonalMessage,
444 version: IntentVersion::V0,
445 app_id: IntentAppId::Iota,
446 };
447 let digest = signing_digest(INTENT, &self.0);
448 digest.into_inner()
449 }
450
451 pub fn signing_digest_hex(&self) -> String {
452 hex::encode(self.signing_digest())
453 }
454 }
455
456 impl crate::CheckpointSummary {
457 pub fn signing_message(&self) -> Vec<u8> {
458 const INTENT: Intent = Intent {
459 scope: IntentScope::CheckpointSummary,
460 version: IntentVersion::V0,
461 app_id: IntentAppId::Iota,
462 };
463 let mut message = Vec::new();
464 message.extend(INTENT.to_bytes());
465 bcs::serialize_into(&mut message, self).unwrap();
466 bcs::serialize_into(&mut message, &self.epoch).unwrap();
467 message
468 }
469
470 pub fn signing_message_hex(&self) -> String {
471 hex::encode(self.signing_message())
472 }
473 }
474}
475
476#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
480#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
481#[repr(u8)]
482enum HashingIntent {
483 #[cfg(feature = "serde")]
484 ChildObjectId = 0xf0,
485 RegularObjectId = 0xf1,
486}
487
488impl crate::ObjectId {
489 pub fn derive_id(digest: crate::Digest, count: u64) -> Self {
494 let mut hasher = Hasher::new();
495 hasher.update([HashingIntent::RegularObjectId as u8]);
496 hasher.update(digest);
497 hasher.update(count.to_le_bytes());
498 let digest = hasher.finalize();
499 Self::new(digest.into_inner())
500 }
501
502 #[cfg(feature = "serde")]
506 #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
507 pub fn derive_dynamic_child_id(&self, key_type_tag: &crate::TypeTag, key_bytes: &[u8]) -> Self {
508 let mut hasher = Hasher::new();
509 hasher.update([HashingIntent::ChildObjectId as u8]);
510 hasher.update(self);
511 hasher.update(
512 u64::try_from(key_bytes.len())
513 .expect("key_bytes must fit into a u64")
514 .to_le_bytes(),
515 );
516 hasher.update(key_bytes);
517 bcs::serialize_into(&mut hasher, key_type_tag)
518 .expect("bcs serialization of `TypeTag` cannot fail");
519 let digest = hasher.finalize();
520
521 Self::new(digest.into_inner())
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use test_strategy::proptest;
528
529 use super::HashingIntent;
530 use crate::SignatureScheme;
531
532 impl HashingIntent {
533 fn from_byte(byte: u8) -> Result<Self, u8> {
534 match byte {
535 0xf0 => Ok(Self::ChildObjectId),
536 0xf1 => Ok(Self::RegularObjectId),
537 invalid => Err(invalid),
538 }
539 }
540 }
541
542 #[proptest]
543 fn hashing_intent_does_not_overlap_with_signature_scheme(intent: HashingIntent) {
544 SignatureScheme::from_byte(intent as u8).unwrap_err();
545 }
546
547 #[proptest]
548 fn signature_scheme_does_not_overlap_with_hashing_intent(scheme: SignatureScheme) {
549 HashingIntent::from_byte(scheme.to_u8()).unwrap_err();
550 }
551
552 #[proptest]
553 fn roundtrip_hashing_intent(intent: HashingIntent) {
554 assert_eq!(Ok(intent), HashingIntent::from_byte(intent as u8));
555 }
556}