netget/server/devp2p/
crypto.rs1use aes::cipher::{KeyIvInit, StreamCipher};
13use anyhow::{bail, ensure, Context, Result};
14use hmac::{Hmac, Mac};
15use rand::RngCore;
16use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
17use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
18use sha2::{Digest, Sha256};
19use sha3::Keccak256;
20use std::sync::LazyLock;
21
22pub const ECIES_OVERHEAD: usize = 65 + 16 + 32;
24
25static SECP: LazyLock<Secp256k1<secp256k1::All>> = LazyLock::new(Secp256k1::new);
26
27type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
28
29pub fn keccak256(parts: &[&[u8]]) -> [u8; 32] {
31 let mut h = Keccak256::new();
32 for p in parts {
33 h.update(p);
34 }
35 h.finalize().into()
36}
37
38#[derive(Clone)]
41pub struct NodeKey {
42 secret: SecretKey,
43 public: PublicKey,
44}
45
46impl NodeKey {
47 pub fn random() -> NodeKey {
48 loop {
49 let mut b = [0u8; 32];
50 rand::thread_rng().fill_bytes(&mut b);
51 if let Ok(k) = NodeKey::from_bytes(&b) {
52 return k;
53 }
54 }
55 }
56
57 pub fn from_bytes(b: &[u8]) -> Result<NodeKey> {
58 let secret = SecretKey::from_slice(b).context("not a valid secp256k1 secret key")?;
59 Ok(NodeKey {
60 public: PublicKey::from_secret_key(&SECP, &secret),
61 secret,
62 })
63 }
64
65 pub fn from_hex(s: &str) -> Result<NodeKey> {
67 let b =
68 hex::decode(s.trim().trim_start_matches("0x")).context("the node key is not hex")?;
69 ensure!(b.len() == 32, "the node key is {} bytes, not 32", b.len());
70 NodeKey::from_bytes(&b)
71 }
72
73 pub fn id(&self) -> [u8; 64] {
74 node_id(&self.public)
75 }
76
77 pub fn public(&self) -> &PublicKey {
78 &self.public
79 }
80
81 pub fn secret(&self) -> &SecretKey {
82 &self.secret
83 }
84}
85
86pub fn node_id(pk: &PublicKey) -> [u8; 64] {
88 let full = pk.serialize_uncompressed();
89 let mut id = [0u8; 64];
90 id.copy_from_slice(&full[1..]);
91 id
92}
93
94pub fn public_from_id(id: &[u8]) -> Result<PublicKey> {
96 ensure!(id.len() == 64, "a node id is 64 bytes, not {}", id.len());
97 let mut full = [0u8; 65];
98 full[0] = 4;
99 full[1..].copy_from_slice(id);
100 PublicKey::from_slice(&full).context("the node id is not a secp256k1 public key")
101}
102
103pub fn ecdh_x(secret: &SecretKey, public: &PublicKey) -> [u8; 32] {
105 let point = secp256k1::ecdh::shared_secret_point(public, secret);
106 let mut x = [0u8; 32];
107 x.copy_from_slice(&point[..32]);
108 x
109}
110
111pub fn sign_recoverable(digest: &[u8; 32], secret: &SecretKey) -> [u8; 65] {
113 let sig = SECP.sign_ecdsa_recoverable(&Message::from_digest(*digest), secret);
114 let (rec, rs) = sig.serialize_compact();
115 let mut out = [0u8; 65];
116 out[..64].copy_from_slice(&rs);
117 out[64] = rec.to_i32() as u8;
118 out
119}
120
121pub fn recover(digest: &[u8; 32], sig: &[u8]) -> Result<PublicKey> {
123 ensure!(
124 sig.len() == 65,
125 "a signature is 65 bytes, not {}",
126 sig.len()
127 );
128 let rec = RecoveryId::from_i32(i32::from(sig[64])).context("bad recovery id")?;
129 let sig = RecoverableSignature::from_compact(&sig[..64], rec).context("bad signature")?;
130 SECP.recover_ecdsa(&Message::from_digest(*digest), &sig)
131 .context("the signature recovers no key")
132}
133
134fn concat_kdf(z: &[u8; 32]) -> [u8; 32] {
136 let mut h = Sha256::new();
137 h.update(1u32.to_be_bytes());
138 h.update(z);
139 h.finalize().into()
140}
141
142fn ecies_keys(z: &[u8; 32]) -> ([u8; 16], [u8; 32]) {
143 let k = concat_kdf(z);
144 let mut ke = [0u8; 16];
145 ke.copy_from_slice(&k[..16]);
146 let km: [u8; 32] = Sha256::digest(&k[16..]).into();
147 (ke, km)
148}
149
150fn tag(km: &[u8; 32], iv_and_ciphertext: &[u8], shared_mac_data: &[u8]) -> [u8; 32] {
151 let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(km).expect("HMAC takes any key length");
152 mac.update(iv_and_ciphertext);
153 mac.update(shared_mac_data);
154 mac.finalize().into_bytes().into()
155}
156
157pub fn ecies_encrypt(remote: &PublicKey, plain: &[u8], shared_mac_data: &[u8]) -> Vec<u8> {
159 let ephemeral = NodeKey::random();
160 let z = ecdh_x(ephemeral.secret(), remote);
161 let (ke, km) = ecies_keys(&z);
162 let mut iv = [0u8; 16];
163 rand::thread_rng().fill_bytes(&mut iv);
164 let mut body = Vec::with_capacity(16 + plain.len());
165 body.extend_from_slice(&iv);
166 body.extend_from_slice(plain);
167 Aes128Ctr::new(&ke.into(), &iv.into()).apply_keystream(&mut body[16..]);
168 let t = tag(&km, &body, shared_mac_data);
169 let mut out = Vec::with_capacity(ECIES_OVERHEAD + plain.len());
170 out.extend_from_slice(&ephemeral.public().serialize_uncompressed());
171 out.extend_from_slice(&body);
172 out.extend_from_slice(&t);
173 out
174}
175
176pub fn ecies_decrypt(secret: &SecretKey, data: &[u8], shared_mac_data: &[u8]) -> Result<Vec<u8>> {
179 ensure!(
180 data.len() > ECIES_OVERHEAD,
181 "ECIES message of {} bytes is shorter than its own overhead",
182 data.len()
183 );
184 if data[0] != 4 {
185 bail!("ECIES ephemeral key is not an uncompressed point");
186 }
187 let ephemeral =
188 PublicKey::from_slice(&data[..65]).context("ECIES ephemeral key is not on the curve")?;
189 let z = ecdh_x(secret, &ephemeral);
190 let (ke, km) = ecies_keys(&z);
191 let body = &data[65..data.len() - 32];
192 let want = tag(&km, body, shared_mac_data);
193 let mut diff = 0u8;
194 for (a, b) in want.iter().zip(&data[data.len() - 32..]) {
195 diff |= a ^ b;
196 }
197 ensure!(diff == 0, "ECIES tag mismatch");
198 let (iv, ciphertext) = body.split_at(16);
199 let mut plain = ciphertext.to_vec();
200 let iv: [u8; 16] = iv.try_into().expect("16 bytes");
201 Aes128Ctr::new(&ke.into(), &iv.into()).apply_keystream(&mut plain);
202 Ok(plain)
203}