posemesh_node_registration/
crypto.rs1use chrono::{DateTime, SecondsFormat, Utc};
2use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1, SecretKey};
3use sha2::{Digest as Sha2Digest, Sha256};
4use sha3::Keccak256;
5
6pub fn load_secp256k1_privhex(hex_str: &str) -> anyhow::Result<SecretKey> {
8 let s = hex_str.trim();
9 let s = s.strip_prefix("0x").unwrap_or(s);
10 let bytes = hex::decode(s)?;
11 if bytes.len() != 32 {
12 anyhow::bail!("invalid secp256k1 secret length: {}", bytes.len());
13 }
14 let sk = SecretKey::from_slice(&bytes)?;
15 Ok(sk)
16}
17
18pub fn secp256k1_pubkey_uncompressed_hex(sk: &SecretKey) -> String {
20 let secp = Secp256k1::new();
21 let pk = PublicKey::from_secret_key(&secp, sk);
22 let uncompressed = pk.serialize_uncompressed(); hex::encode(uncompressed)
24}
25
26pub fn sign_compact_hex(sk: &SecretKey, msg: &[u8]) -> String {
29 let digest = Sha256::digest(msg);
30 let message = Message::from_digest_slice(&digest).expect("sha256 is 32 bytes");
31 let secp = Secp256k1::new();
32 let sig: Signature = secp.sign_ecdsa(&message, sk);
33 let compact = sig.serialize_compact();
34 hex::encode(compact)
35}
36
37pub fn sign_recoverable_keccak_hex(sk: &SecretKey, msg: &[u8]) -> String {
39 let mut hasher = Keccak256::new();
41 hasher.update(msg);
42 let hash = hasher.finalize();
43 let message = Message::from_digest_slice(&hash).expect("keccak256 is 32 bytes");
44 let secp = Secp256k1::new();
45 let rsig = secp.sign_ecdsa_recoverable(&message, sk);
46 let (rid, sig_bytes) = rsig.serialize_compact();
47 let mut out = [0u8; 65];
48 out[..64].copy_from_slice(&sig_bytes);
49 out[64] = rid.to_i32() as u8; hex::encode(out)
51}
52
53pub fn format_timestamp_nanos(ts: DateTime<Utc>) -> String {
55 ts.to_rfc3339_opts(SecondsFormat::Nanos, true)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use chrono::{NaiveDate, NaiveTime};
62
63 #[test]
64 fn timestamp_nanos_format() {
65 let date = NaiveDate::from_ymd_opt(2024, 1, 2).unwrap();
66 let time = NaiveTime::from_hms_nano_opt(3, 4, 5, 6_007_008).unwrap();
67 let dt = DateTime::<Utc>::from_naive_utc_and_offset(date.and_time(time), Utc);
68 let s = format_timestamp_nanos(dt);
69 assert_eq!(s, "2024-01-02T03:04:05.006007008Z");
70 }
71
72 #[test]
73 fn sign_fixed_keccak_recoverable_hex_has_expected_shape() {
74 let sk = load_secp256k1_privhex(
76 "e331b6d69882b4ed5bb7f55b585d7d0f7dc3aeca4a3deee8d16bde3eca51aace",
77 )
78 .expect("key");
79 let url = "https://node.example.com";
80 let ts = "2024-01-02T03:04:05.000000000Z";
81 let msg = format!("{}{}", url, ts);
82 let sig = sign_recoverable_keccak_hex(&sk, msg.as_bytes());
83 assert_eq!(sig.len(), 130);
85 assert!(sig
87 .chars()
88 .all(|c| c.is_ascii_hexdigit() && c.is_ascii_lowercase() || c.is_ascii_digit()));
89 }
90}