use sha2::{Digest, Sha256};
pub fn tap_memo_hash(transfer_id: &str) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(transfer_id.as_bytes());
hasher.finalize().into()
}
pub fn encode_text_memo(transfer_id: &str) -> String {
format!("tap:1:{}", hex::encode(tap_memo_hash(transfer_id)))
}
pub fn encode_binary_memo(transfer_id: &str) -> [u8; 32] {
tap_memo_hash(transfer_id)
}
pub fn verify_text_memo(memo: &str, transfer_id: &str) -> bool {
let Some(tail) = memo.strip_prefix("tap:1:") else {
return false;
};
if tail.len() != 64 {
return false;
}
if tail.chars().any(|c| c.is_ascii_uppercase()) {
return false;
}
let Ok(bytes) = hex::decode(tail) else {
return false;
};
bytes.as_slice() == tap_memo_hash(transfer_id).as_slice()
}
pub fn verify_binary_memo(memo: &[u8], transfer_id: &str) -> bool {
memo.len() == 32 && memo == tap_memo_hash(transfer_id).as_slice()
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_TRANSFER_ID: &str = "3fa85f64-5717-4562-b3fc-2c963f66afa6";
const SAMPLE_HEX: &str = "c7aa09cd25da8b6ab686f96da282d29ce9a7a2a0d7c27e1e359eb2cac6fbfaaf";
#[test]
fn text_and_binary_profiles_agree() {
let text = encode_text_memo(SAMPLE_TRANSFER_ID);
let bin = encode_binary_memo(SAMPLE_TRANSFER_ID);
assert_eq!(text, format!("tap:1:{}", SAMPLE_HEX));
assert_eq!(hex::encode(bin), SAMPLE_HEX);
}
#[test]
fn round_trip_text_verifies() {
let memo = encode_text_memo(SAMPLE_TRANSFER_ID);
assert!(verify_text_memo(&memo, SAMPLE_TRANSFER_ID));
}
#[test]
fn round_trip_binary_verifies() {
let memo = encode_binary_memo(SAMPLE_TRANSFER_ID);
assert!(verify_binary_memo(&memo, SAMPLE_TRANSFER_ID));
}
}