use std::str;
pub const SHA1_HEX_LEN: usize = 40;
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ScriptHashKey {
buf: [u8; SHA1_HEX_LEN],
}
impl Default for ScriptHashKey {
fn default() -> Self {
Self {
buf: [b'0'; SHA1_HEX_LEN],
}
}
}
impl ScriptHashKey {
pub fn new(sha1_digest: &[u8; 20]) -> Self {
let mut buf = [0u8; SHA1_HEX_LEN];
for (i, &b) in sha1_digest.iter().enumerate() {
buf[i * 2] = HEX_CHARS[(b >> 4) as usize];
buf[i * 2 + 1] = HEX_CHARS[(b & 0xf) as usize];
}
Self { buf }
}
pub fn from_hex(hex: &[u8]) -> Option<Self> {
if hex.len() != SHA1_HEX_LEN {
return None;
}
let mut buf = [0u8; SHA1_HEX_LEN];
for (i, &b) in hex.iter().enumerate() {
buf[i] = b.to_ascii_lowercase();
if !buf[i].is_ascii_hexdigit() {
return None;
}
}
Some(Self { buf })
}
pub fn as_str(&self) -> &str {
str::from_utf8(&self.buf).expect("hex 摘要恒为 ASCII")
}
pub fn copy_to(&self, destination: &mut [u8]) -> bool {
if destination.len() < SHA1_HEX_LEN {
return false;
}
destination[..SHA1_HEX_LEN].copy_from_slice(&self.buf);
true
}
pub fn equals(&self, other: &ScriptHashKey) -> bool {
self.buf == other.buf
}
}
#[cfg(test)]
mod tests {
use super::ScriptHashKey;
#[test]
fn digest_to_hex_and_copy() {
let digest = [0xabu8; 20];
let key = ScriptHashKey::new(&digest);
assert_eq!(key.as_str(), &"ab".repeat(20));
assert_eq!(key.as_str().len(), 40);
let mut dst = [0u8; 40];
assert!(key.copy_to(&mut dst));
assert_eq!(&dst, key.as_str().as_bytes());
assert!(!key.copy_to(&mut [0u8; 39]));
}
#[test]
fn equals_matches_content() {
let a = ScriptHashKey::new(&[1u8; 20]);
let b = ScriptHashKey::new(&[1u8; 20]);
let c = ScriptHashKey::new(&[2u8; 20]);
assert!(a.equals(&b));
assert!(!a.equals(&c));
}
}