1use sha2::{Digest, Sha256};
8
9use crate::hash;
10
11#[cfg(not(feature = "std"))]
12use alloc::vec::Vec;
13#[cfg(feature = "std")]
14use std::vec::Vec;
15
16pub const LITHIUM_BLOCK_HASH_LEN: usize = 32;
18pub const LITHIUM_CYBERLINKS_MERKLE_LEN: usize = 32;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct LithiumCanonicalVector {
24 pub miner_address: &'static str,
25 pub nonce: u64,
26 pub block_hash_hex: &'static str,
27 pub cyberlinks_merkle_hex: &'static str,
28 pub hash_hex: &'static str,
29}
30
31pub const LITHIUM_CANONICAL_VECTORS: [LithiumCanonicalVector; 2] = [
33 LithiumCanonicalVector {
34 miner_address: "bostrom1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwxyzab",
35 nonce: 1,
36 block_hash_hex: "0000000000000000000000000000000000000000000000000000000000000000",
37 cyberlinks_merkle_hex: "1111111111111111111111111111111111111111111111111111111111111111",
38 hash_hex: "c90e35884ae2dc15327121aa0009fac30afa77eb4a0c1b3b5127c5bdbd60fe4d",
39 },
40 LithiumCanonicalVector {
41 miner_address: "bostrom1s7fuy43h8v6hzjtulx9gxyp30rl9t5cz3z56mk",
42 nonce: 226_943,
43 block_hash_hex: "45ee333a09e4f32dbfb4ca2b22287d10758e7dc2e9ca68f45fcec66ac31b055e",
44 cyberlinks_merkle_hex: "f9b24273b7b5a399b394945aff3c9fe08a126b957516b9f357c7f1e868f8378c",
45 hash_hex: "b4c98eb2752f240247266380e870fce04914abfa7199fa78c80acb80200192e0",
46 },
47];
48
49pub fn lithium_header(
52 miner_address: &str,
53 block_hash: &[u8; LITHIUM_BLOCK_HASH_LEN],
54 cyberlinks_merkle: &[u8; LITHIUM_CYBERLINKS_MERKLE_LEN],
55) -> [u8; 32] {
56 let mut hasher = Sha256::new();
57 hasher.update(miner_address.as_bytes());
58 hasher.update(block_hash);
59 hasher.update(cyberlinks_merkle);
60 hasher.finalize().into()
61}
62
63pub fn lithium_preimage(
66 miner_address: &str,
67 nonce: u64,
68 block_hash: &[u8; LITHIUM_BLOCK_HASH_LEN],
69 cyberlinks_merkle: &[u8; LITHIUM_CYBERLINKS_MERKLE_LEN],
70) -> Vec<u8> {
71 let header = lithium_header(miner_address, block_hash, cyberlinks_merkle);
72 let mut input = Vec::with_capacity(32usize + core::mem::size_of::<u64>());
73 input.extend_from_slice(&header);
74 input.extend_from_slice(&nonce.to_le_bytes());
75 input
76}
77
78pub fn lithium_hash(
81 miner_address: &str,
82 nonce: u64,
83 block_hash: &[u8; LITHIUM_BLOCK_HASH_LEN],
84 cyberlinks_merkle: &[u8; LITHIUM_CYBERLINKS_MERKLE_LEN],
85) -> [u8; 32] {
86 let input = lithium_preimage(miner_address, nonce, block_hash, cyberlinks_merkle);
87 hash(&input)
88}
89
90#[cfg(test)]
91mod tests {
92 use super::{lithium_hash, lithium_header, lithium_preimage, LITHIUM_CANONICAL_VECTORS};
93 use hex::decode;
94
95 #[test]
96 fn lithium_preimage_layout_is_stable() {
97 let miner = "bostrom1minerxyz";
98 let nonce = 0x0102_0304_0506_0708u64;
99 let block = [0x11u8; 32];
100 let merkle = [0x22u8; 32];
101
102 let preimage = lithium_preimage(miner, nonce, &block, &merkle);
103 let expected_len = 32 + 8;
104 assert_eq!(preimage.len(), expected_len);
105
106 let header = lithium_header(miner, &block, &merkle);
107 assert_eq!(&preimage[..32], &header);
108 assert_eq!(&preimage[32..], &nonce.to_le_bytes());
109 }
110
111 #[test]
112 fn lithium_hash_changes_when_nonce_changes() {
113 let miner = "bostrom1minerxyz";
114 let block = [0xABu8; 32];
115 let merkle = [0xCDu8; 32];
116
117 let h1 = lithium_hash(miner, 1, &block, &merkle);
118 let h2 = lithium_hash(miner, 2, &block, &merkle);
119 assert_ne!(h1, h2);
120 }
121
122 #[test]
123 fn lithium_hash_changes_when_address_changes() {
124 let block = [0xABu8; 32];
125 let merkle = [0xCDu8; 32];
126
127 let h1 = lithium_hash("bostrom1a", 7, &block, &merkle);
128 let h2 = lithium_hash("bostrom1b", 7, &block, &merkle);
129 assert_ne!(h1, h2);
130 }
131
132 #[test]
133 fn lithium_hash_matches_canonical_vectors() {
134 for vector in LITHIUM_CANONICAL_VECTORS {
135 let block: [u8; 32] = decode(vector.block_hash_hex)
136 .expect("valid hex")
137 .try_into()
138 .expect("32-byte block hash");
139 let merkle: [u8; 32] = decode(vector.cyberlinks_merkle_hex)
140 .expect("valid hex")
141 .try_into()
142 .expect("32-byte merkle");
143 let out = lithium_hash(vector.miner_address, vector.nonce, &block, &merkle);
144 let expected = decode(vector.hash_hex).expect("valid hex");
145 assert_eq!(&out[..], &expected[..], "vector failed: {:?}", vector);
146 }
147 }
148
149 #[test]
150 fn lithium_hash_high_nonce_is_arch_stable() {
151 let miner = "bostrom1cdafnfet29jxahudhae3cfcs3fvwtp8m0zmgr5";
152 let nonce = 1_771_439_487_000_014u64;
153 let block: [u8; 32] =
154 decode("1842aececd7440ee5e5a1474bc6c4035ab32a16b644e19d85d4257c8c6d702ad")
155 .expect("valid hex")
156 .try_into()
157 .expect("32-byte block");
158 let merkle: [u8; 32] =
159 decode("b2af290c2cc22c1b3a444030e705800557466b7beac6d883e7c4e4143eead3ea")
160 .expect("valid hex")
161 .try_into()
162 .expect("32-byte merkle");
163 let expected =
164 decode("7dd369dcd233b8f3d815ce0d6217734a4256b9286fc8b482f7721724ec39a382")
165 .expect("valid hex");
166
167 let out = lithium_hash(miner, nonce, &block, &merkle);
168 assert_eq!(&out[..], &expected[..]);
169 }
170}