kobe_primitives/
encoding.rs1use alloc::string::String;
11use alloc::vec::Vec;
12
13use ripemd::Ripemd160;
14use sha2::{Digest, Sha256};
15
16#[must_use]
21pub fn hash160(data: &[u8]) -> [u8; 20] {
22 let sha = Sha256::digest(data);
23 Ripemd160::digest(sha).into()
24}
25
26#[must_use]
31pub fn double_sha256(data: &[u8]) -> [u8; 32] {
32 let first = Sha256::digest(data);
33 Sha256::digest(first).into()
34}
35
36#[must_use]
42pub fn base58check_encode(payload: &[u8]) -> String {
43 let checksum = double_sha256(payload);
44 let mut buf = Vec::with_capacity(payload.len() + 4);
45 buf.extend_from_slice(payload);
46 buf.extend_from_slice(&checksum[..4]);
47 bs58::encode(buf).into_string()
48}
49
50#[must_use]
52#[allow(
53 clippy::indexing_slicing,
54 reason = "fixed-size 21-byte Base58Check body is sized by construction"
55)]
56pub fn base58check_versioned(version: u8, payload_20: &[u8; 20]) -> String {
57 let mut body = [0u8; 21];
58 body[0] = version;
59 body[1..].copy_from_slice(payload_20);
60 base58check_encode(&body)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn hash160_empty_known() {
69 let h = hash160(b"");
71 assert_eq!(hex::encode(h), "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb");
72 }
73
74 #[test]
75 fn base58check_p2pkh_mainnet_prefix() {
76 let payload = [0x11u8; 20];
78 let encoded = base58check_versioned(0x00, &payload);
79 assert!(encoded.starts_with('1'));
80 }
81
82 #[test]
83 fn double_sha256_deterministic() {
84 let a = double_sha256(b"kobe");
85 let b = double_sha256(b"kobe");
86 assert_eq!(a, b);
87 assert_ne!(a, double_sha256(b"other"));
88 }
89}