tap_msg/utils/
memo_hash.rs1use sha2::{Digest, Sha256};
15
16pub fn tap_memo_hash(transfer_id: &str) -> [u8; 32] {
19 let mut hasher = Sha256::new();
20 hasher.update(transfer_id.as_bytes());
21 hasher.finalize().into()
22}
23
24pub fn encode_text_memo(transfer_id: &str) -> String {
26 format!("tap:1:{}", hex::encode(tap_memo_hash(transfer_id)))
27}
28
29pub fn encode_binary_memo(transfer_id: &str) -> [u8; 32] {
31 tap_memo_hash(transfer_id)
32}
33
34pub fn verify_text_memo(memo: &str, transfer_id: &str) -> bool {
41 let Some(tail) = memo.strip_prefix("tap:1:") else {
42 return false;
43 };
44 if tail.len() != 64 {
45 return false;
46 }
47 if tail.chars().any(|c| c.is_ascii_uppercase()) {
48 return false;
49 }
50 let Ok(bytes) = hex::decode(tail) else {
51 return false;
52 };
53 bytes.as_slice() == tap_memo_hash(transfer_id).as_slice()
54}
55
56pub fn verify_binary_memo(memo: &[u8], transfer_id: &str) -> bool {
59 memo.len() == 32 && memo == tap_memo_hash(transfer_id).as_slice()
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 const SAMPLE_TRANSFER_ID: &str = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
67 const SAMPLE_HEX: &str = "c7aa09cd25da8b6ab686f96da282d29ce9a7a2a0d7c27e1e359eb2cac6fbfaaf";
68
69 #[test]
70 fn text_and_binary_profiles_agree() {
71 let text = encode_text_memo(SAMPLE_TRANSFER_ID);
72 let bin = encode_binary_memo(SAMPLE_TRANSFER_ID);
73
74 assert_eq!(text, format!("tap:1:{}", SAMPLE_HEX));
75 assert_eq!(hex::encode(bin), SAMPLE_HEX);
76 }
77
78 #[test]
79 fn round_trip_text_verifies() {
80 let memo = encode_text_memo(SAMPLE_TRANSFER_ID);
81 assert!(verify_text_memo(&memo, SAMPLE_TRANSFER_ID));
82 }
83
84 #[test]
85 fn round_trip_binary_verifies() {
86 let memo = encode_binary_memo(SAMPLE_TRANSFER_ID);
87 assert!(verify_binary_memo(&memo, SAMPLE_TRANSFER_ID));
88 }
89}