use k256::elliptic_curve::ops::Reduce;
use k256::ecdsa::SigningKey;
use crate::{claim, cosmos, Error};
pub fn domain_scalar(entropy: &[u8; 32], domain: &str) -> k256::Scalar {
let mut input = Vec::with_capacity(33 + domain.len());
input.extend_from_slice(entropy);
input.push(0x00);
input.extend_from_slice(domain.as_bytes());
let h = hemera::hash(&input);
<k256::Scalar as Reduce<k256::U256>>::reduce(k256::U256::from_be_slice(h.as_bytes()))
}
pub struct DomainKey {
signing: SigningKey,
pub pubkey: [u8; 33],
pub bech32: String,
pub native: [u8; 32],
}
impl std::fmt::Debug for DomainKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DomainKey").field("bech32", &self.bech32).finish()
}
}
impl DomainKey {
pub fn derive(entropy: &[u8; 32], domain: &str, hrp: &str) -> Result<Self, Error> {
let scalar = domain_scalar(entropy, domain);
let signing = SigningKey::from_bytes(&scalar.to_bytes())
.map_err(|e| Error::Derive(e.to_string()))?;
let pubkey_point = signing.verifying_key().to_encoded_point(true);
let mut pubkey = [0u8; 33];
pubkey.copy_from_slice(pubkey_point.as_bytes());
let bech32 = cosmos::address(&pubkey, hrp)?;
let native = claim::neuron_of(&pubkey);
Ok(Self { signing, pubkey, bech32, native })
}
pub fn signing_key(&self) -> &SigningKey {
&self.signing
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_entropy_same_domain_same_key() {
let e = [3u8; 32];
let a = DomainKey::derive(&e, "example.com", "lytics").unwrap();
let b = DomainKey::derive(&e, "example.com", "lytics").unwrap();
assert_eq!(a.bech32, b.bech32);
assert_eq!(a.native, b.native);
}
#[test]
fn different_domains_different_keys() {
let e = [3u8; 32];
let a = DomainKey::derive(&e, "example.com", "lytics").unwrap();
let b = DomainKey::derive(&e, "cyber.page", "lytics").unwrap();
assert_ne!(a.bech32, b.bech32);
}
#[test]
fn domain_scalar_is_deterministic_and_domain_bound() {
let e = [3u8; 32];
assert_eq!(domain_scalar(&e, "example.com"), domain_scalar(&e, "example.com"));
assert_ne!(domain_scalar(&e, "example.com"), domain_scalar(&e, "cyber.page"));
}
#[test]
fn bech32_uses_hrp() {
let k = DomainKey::derive(&[7u8; 32], "example.com", "lytics").unwrap();
assert!(k.bech32.starts_with("lytics1"), "got {}", k.bech32);
}
#[test]
fn native_id_matches_the_bridge_formula() {
let k = DomainKey::derive(&[7u8; 32], "example.com", "lytics").unwrap();
assert_eq!(k.native, claim::neuron_of(&k.pubkey));
}
}