hash.rs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. //! Hashing and tamper-evidence for the ledger.
  2. //!
  3. //! Every transfer gets a content-addressed [`EnvelopeId`] (double-SHA256 of its
  4. //! canonical serialization), which serves as both the idempotency key and the
  5. //! tamper-evidence artifact.
  6. use sha2::{Digest, Sha256};
  7. use kuatia_types::{Account, AccountSnapshotId, Envelope, EnvelopeId, ToBytes};
  8. // ---------------------------------------------------------------------------
  9. // Double-SHA256
  10. // ---------------------------------------------------------------------------
  11. /// Double-SHA256 — the standard hash used throughout the ledger.
  12. /// Prevents length-extension attacks.
  13. pub fn double_sha256(data: &[u8]) -> [u8; 32] {
  14. let first = Sha256::digest(data);
  15. let second = Sha256::digest(first);
  16. let mut out = [0u8; 32];
  17. out.copy_from_slice(&second);
  18. out
  19. }
  20. // ---------------------------------------------------------------------------
  21. // Transfer hashing
  22. // ---------------------------------------------------------------------------
  23. /// Deterministic binary serialization of an envelope.
  24. pub fn canonical_bytes(envelope: &Envelope) -> Vec<u8> {
  25. envelope.to_bytes()
  26. }
  27. /// Double-SHA256 content hash. Returns a [`EnvelopeId`].
  28. pub fn content_hash(data: &[u8]) -> EnvelopeId {
  29. EnvelopeId(double_sha256(data))
  30. }
  31. /// Convenience: `envelope.to_bytes()` → double-SHA256 → [`EnvelopeId`].
  32. pub fn envelope_id(envelope: &Envelope) -> EnvelopeId {
  33. content_hash(&envelope.to_bytes())
  34. }
  35. // ---------------------------------------------------------------------------
  36. // Account hashing
  37. // ---------------------------------------------------------------------------
  38. /// Deterministic binary serialization of an account snapshot.
  39. pub fn account_canonical_bytes(account: &Account) -> Vec<u8> {
  40. account.to_bytes()
  41. }
  42. /// Double-SHA256 of an account's canonical bytes.
  43. pub fn account_hash(account: &Account) -> [u8; 32] {
  44. double_sha256(&account.to_bytes())
  45. }
  46. /// Compute the [`AccountSnapshotId`] for an account's current state.
  47. pub fn account_snapshot_id(account: &Account) -> AccountSnapshotId {
  48. AccountSnapshotId {
  49. account: account.id,
  50. snapshot_id: account_hash(account),
  51. }
  52. }
  53. #[cfg(test)]
  54. mod tests {
  55. use super::*;
  56. use kuatia_types::*;
  57. fn sample_envelope() -> Envelope {
  58. EnvelopeBuilder::new()
  59. .creates(vec![NewPosting {
  60. owner: AccountId::new(1),
  61. asset: AssetId::new(1),
  62. value: Cent::from(100),
  63. payer: None,
  64. }])
  65. .build()
  66. }
  67. #[test]
  68. fn content_hash_deterministic() {
  69. let t = sample_envelope();
  70. let id1 = envelope_id(&t);
  71. let id2 = envelope_id(&t);
  72. assert_eq!(id1, id2);
  73. }
  74. #[test]
  75. fn different_envelopes_different_hashes() {
  76. let t1 = sample_envelope();
  77. let mut t2 = sample_envelope();
  78. t2.creates[0].value = Cent::from(200);
  79. assert_ne!(envelope_id(&t1), envelope_id(&t2));
  80. }
  81. #[test]
  82. fn to_bytes_sha256_consistency() {
  83. let t = sample_envelope();
  84. assert_eq!(double_sha256(&t.to_bytes()), envelope_id(&t).0);
  85. }
  86. }