Skip to main content

aptos_sdk/crypto/
hash.rs

1//! Hash functions for the Aptos SDK.
2//!
3//! Provides SHA2-256 and SHA3-256 hash functions used throughout Aptos.
4
5use sha2::Digest as Sha2Digest;
6
7/// Available hash functions.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum HashFunction {
10    /// SHA2-256. General-purpose; **not** used for Aptos signature hashing.
11    Sha2_256,
12    /// SHA3-256 (used for Ed25519, Secp256k1 ECDSA, and authentication keys).
13    Sha3_256,
14}
15
16/// Computes the SHA2-256 hash of the input.
17///
18/// This is a general-purpose SHA2-256 helper. Note that Aptos Secp256k1 ECDSA
19/// signing hashes the message with **SHA3-256**, not SHA2-256, so this function
20/// is not part of the signing path.
21///
22/// # Example
23///
24/// ```rust
25/// use aptos_sdk::crypto::sha2_256;
26///
27/// let hash = sha2_256(b"hello world");
28/// assert_eq!(hash.len(), 32);
29/// ```
30pub fn sha2_256(data: &[u8]) -> [u8; 32] {
31    let mut hasher = sha2::Sha256::new();
32    hasher.update(data);
33    let result = hasher.finalize();
34    let mut output = [0u8; 32];
35    output.copy_from_slice(&result);
36    output
37}
38
39/// Computes the SHA3-256 hash of the input.
40///
41/// This is used for Ed25519 signatures and authentication key derivation.
42///
43/// # Example
44///
45/// ```rust
46/// use aptos_sdk::crypto::sha3_256;
47///
48/// let hash = sha3_256(b"hello world");
49/// assert_eq!(hash.len(), 32);
50/// ```
51pub fn sha3_256(data: &[u8]) -> [u8; 32] {
52    let mut hasher = sha3::Sha3_256::new();
53    hasher.update(data);
54    let result = hasher.finalize();
55    let mut output = [0u8; 32];
56    output.copy_from_slice(&result);
57    output
58}
59
60/// Computes the SHA3-256 hash of multiple byte slices.
61#[allow(dead_code)] // Public API for users
62pub fn sha3_256_of<I, T>(items: I) -> [u8; 32]
63where
64    I: IntoIterator<Item = T>,
65    T: AsRef<[u8]>,
66{
67    let mut hasher = sha3::Sha3_256::new();
68    for item in items {
69        hasher.update(item.as_ref());
70    }
71    let result = hasher.finalize();
72    let mut output = [0u8; 32];
73    output.copy_from_slice(&result);
74    output
75}
76
77/// Builds the signing message for an Aptos transaction preimage.
78///
79/// Aptos does **not** hash the whole `domain || bcs_bytes` blob into a single
80/// digest. The signing message is the SHA3-256 hash of the domain separator
81/// **concatenated with** the raw (unhashed) BCS bytes:
82///
83/// `SHA3-256(b"APTOS::{domain}") || bcs_bytes`
84///
85/// This matches the on-wire construction produced by
86/// [`crate::transaction::types::RawTransaction::signing_message`]. Signing a
87/// single hash taken over the concatenation (the previous behaviour of this
88/// helper) yields a message the chain rejects with `INVALID_SIGNATURE`.
89#[allow(dead_code)] // Public API for users
90pub fn signing_message(domain: &str, bcs_bytes: &[u8]) -> Vec<u8> {
91    let prefix = sha3_256(format!("APTOS::{domain}").as_bytes());
92    let mut message = Vec::with_capacity(prefix.len() + bcs_bytes.len());
93    message.extend_from_slice(&prefix);
94    message.extend_from_slice(bcs_bytes);
95    message
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_sha2_256() {
104        let hash = sha2_256(b"hello world");
105        assert_eq!(hash.len(), 32);
106        // Known hash value
107        let expected =
108            const_hex::decode("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
109                .unwrap();
110        assert_eq!(hash.as_slice(), expected.as_slice());
111    }
112
113    #[test]
114    fn test_sha3_256() {
115        let hash = sha3_256(b"hello world");
116        assert_eq!(hash.len(), 32);
117        // Verify it's different from SHA2-256
118        let sha2_hash = sha2_256(b"hello world");
119        assert_ne!(hash, sha2_hash);
120    }
121
122    #[test]
123    fn test_sha3_256_of_multiple() {
124        let hash1 = sha3_256(b"helloworld");
125        let hash2 = sha3_256_of([b"hello".as_slice(), b"world".as_slice()]);
126        assert_eq!(hash1, hash2);
127    }
128
129    #[test]
130    fn test_signing_message() {
131        let bcs_bytes = b"transaction_bytes";
132        let msg = signing_message("RawTransaction", bcs_bytes);
133
134        // Must be `SHA3-256(domain) || bcs_bytes` (hashed prefix CONCATENATED
135        // with raw BCS bytes), NOT a single hash over the concatenation.
136        let expected_prefix = sha3_256(b"APTOS::RawTransaction");
137        assert_eq!(msg.len(), 32 + bcs_bytes.len());
138        assert_eq!(&msg[..32], &expected_prefix[..]);
139        assert_eq!(&msg[32..], bcs_bytes);
140
141        // It must NOT equal the (incorrect) single hash of the whole blob.
142        let single_hash = sha3_256_of([b"APTOS::RawTransaction".as_slice(), bcs_bytes]);
143        assert_ne!(&msg[..32], &single_hash[..]);
144    }
145
146    #[test]
147    fn test_signing_message_known_bytes() {
148        // Hardcoded expected bytes for a small fixed input. The 32-byte prefix
149        // is SHA3-256("APTOS::RawTransaction"); the two trailing bytes are the
150        // raw BCS payload appended verbatim.
151        let expected = const_hex::decode(
152            "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b193aabb",
153        )
154        .unwrap();
155        let msg = signing_message("RawTransaction", &[0xaa, 0xbb]);
156        assert_eq!(msg, expected);
157    }
158}