use crate::error::{Error, Result};
use core::ffi::c_char;
use core::fmt;
use core::str::FromStr;
use nwep_sys as sys;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId([u8; sys::NWEP_NODEID_SIZE]);
impl NodeId {
pub fn from_pubkey(pubkey: &[u8; sys::NWEP_PUBKEY_SIZE]) -> Result<NodeId> {
let mut out = sys::nwep_node_id {
bytes: [0; sys::NWEP_NODEID_SIZE],
};
Error::check(unsafe { sys::nwep_nodeid_from_pubkey(&mut out, pubkey.as_ptr()) })?;
Ok(NodeId(out.bytes))
}
pub fn from_base58(s: &str) -> Result<NodeId> {
let mut out = sys::nwep_node_id {
bytes: [0; sys::NWEP_NODEID_SIZE],
};
Error::check(unsafe {
sys::nwep_nodeid_from_base58(&mut out, s.as_ptr().cast::<c_char>(), s.len())
})?;
Ok(NodeId(out.bytes))
}
pub fn to_base58(&self) -> String {
let id = sys::nwep_node_id { bytes: self.0 };
let mut buf = [0u8; 64];
let mut len = buf.len();
let rc =
unsafe { sys::nwep_nodeid_to_base58(buf.as_mut_ptr().cast::<c_char>(), &mut len, &id) };
debug_assert_eq!(
rc, 0,
"nodeid base58 encode should not fail into a 64 byte buffer"
);
String::from_utf8_lossy(&buf[..len]).into_owned()
}
pub fn verify(&self, pubkey: &[u8; sys::NWEP_PUBKEY_SIZE]) -> bool {
let id = sys::nwep_node_id { bytes: self.0 };
unsafe { sys::nwep_nodeid_verify(&id, pubkey.as_ptr()) == 0 }
}
pub fn as_bytes(&self) -> &[u8; sys::NWEP_NODEID_SIZE] {
&self.0
}
pub fn from_bytes(bytes: [u8; sys::NWEP_NODEID_SIZE]) -> NodeId {
NodeId(bytes)
}
pub(crate) fn raw(&self) -> sys::nwep_node_id {
sys::nwep_node_id { bytes: self.0 }
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_base58())
}
}
impl fmt::Debug for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NodeId({})", self.to_base58())
}
}
impl FromStr for NodeId {
type Err = Error;
fn from_str(s: &str) -> Result<NodeId> {
NodeId::from_base58(s)
}
}
pub struct Identity {
node_id: NodeId,
pubkey: [u8; sys::NWEP_PUBKEY_SIZE],
privkey: [u8; sys::NWEP_PRIVKEY_SIZE],
}
impl Identity {
pub fn generate() -> Result<Identity> {
let mut id = sys::nwep_node_id {
bytes: [0; sys::NWEP_NODEID_SIZE],
};
let mut kp = sys::nwep_keypair {
pub_: [0; sys::NWEP_PUBKEY_SIZE],
priv_: [0; sys::NWEP_PRIVKEY_SIZE],
};
Error::check(unsafe { sys::nwep_identity_generate(&mut id, &mut kp) })?;
let identity = Identity {
node_id: NodeId(id.bytes),
pubkey: kp.pub_,
privkey: kp.priv_,
};
zeroize(&mut kp.priv_);
Ok(identity)
}
pub fn from_pem(pem: &str) -> Result<Identity> {
let mut kp = sys::nwep_keypair {
pub_: [0; sys::NWEP_PUBKEY_SIZE],
priv_: [0; sys::NWEP_PRIVKEY_SIZE],
};
Error::check(unsafe { sys::nwep_keypair_load_pem(&mut kp, pem.as_ptr(), pem.len()) })?;
let mut nid = sys::nwep_node_id {
bytes: [0; sys::NWEP_NODEID_SIZE],
};
let derived = unsafe { sys::nwep_nodeid_from_pubkey(&mut nid, kp.pub_.as_ptr()) };
if let Err(e) = Error::check(derived) {
zeroize(&mut kp.priv_);
return Err(e);
}
let identity = Identity {
node_id: NodeId(nid.bytes),
pubkey: kp.pub_,
privkey: kp.priv_,
};
zeroize(&mut kp.priv_);
Ok(identity)
}
pub fn to_pem(&self) -> Result<String> {
let mut kp = sys::nwep_keypair {
pub_: self.pubkey,
priv_: self.privkey,
};
let mut buf = vec![0u8; 4096];
let mut len = buf.len();
let rc = unsafe { sys::nwep_keypair_save_pem(buf.as_mut_ptr(), &mut len, &kp) };
zeroize(&mut kp.priv_);
Error::check(rc)?;
buf.truncate(len);
String::from_utf8(buf).map_err(|_| Error::Internal)
}
pub fn sign(&self, msg: &[u8]) -> Result<[u8; sys::NWEP_SIG_SIZE]> {
let mut sig = [0u8; sys::NWEP_SIG_SIZE];
Error::check(unsafe {
sys::nwep_ed25519_sign(
sig.as_mut_ptr(),
msg.as_ptr(),
msg.len(),
self.privkey.as_ptr(),
)
})?;
Ok(sig)
}
pub fn node_id(&self) -> &NodeId {
&self.node_id
}
pub fn public_key(&self) -> &[u8; sys::NWEP_PUBKEY_SIZE] {
&self.pubkey
}
pub(crate) fn with_keypair<R>(&self, f: impl FnOnce(*const sys::nwep_keypair) -> R) -> R {
let mut kp = sys::nwep_keypair {
pub_: self.pubkey,
priv_: self.privkey,
};
let out = f(&kp);
zeroize(&mut kp.priv_);
out
}
pub(crate) fn with_raw_keys<R>(&self, f: impl FnOnce(*const u8, *const u8) -> R) -> R {
let mut secret = self.privkey;
let out = f(self.pubkey.as_ptr(), secret.as_ptr());
zeroize(&mut secret);
out
}
}
impl Drop for Identity {
fn drop(&mut self) {
zeroize(&mut self.privkey);
}
}
impl fmt::Debug for Identity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Identity")
.field("node_id", &self.node_id)
.finish_non_exhaustive()
}
}
fn zeroize(bytes: &mut [u8]) {
unsafe { sys::nwep_zeroize(bytes.as_mut_ptr().cast(), bytes.len()) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_then_verify_and_round_trip_base58() {
let id = Identity::generate().unwrap();
assert!(id.node_id().verify(id.public_key()));
let s = id.node_id().to_base58();
let back = NodeId::from_base58(&s).unwrap();
assert_eq!(&back, id.node_id());
assert_eq!(s.parse::<NodeId>().unwrap(), back);
assert_eq!(back.to_string(), s);
}
#[test]
fn from_pubkey_matches_generated_node_id() {
let id = Identity::generate().unwrap();
let derived = NodeId::from_pubkey(id.public_key()).unwrap();
assert_eq!(&derived, id.node_id());
}
#[test]
fn verify_rejects_a_different_key() {
let a = Identity::generate().unwrap();
let b = Identity::generate().unwrap();
assert!(!a.node_id().verify(b.public_key()));
}
#[test]
fn sign_produces_a_verifiable_signature() {
let id = Identity::generate().unwrap();
let msg = b"web/1 binding test";
let sig = id.sign(msg).unwrap();
let ok = unsafe {
sys::nwep_ed25519_verify(
sig.as_ptr(),
msg.as_ptr(),
msg.len(),
id.public_key().as_ptr(),
)
};
assert_eq!(ok, 0);
}
#[test]
fn pem_round_trips_to_the_same_node_id() {
let id = Identity::generate().unwrap();
let pem = id.to_pem().unwrap();
let loaded = Identity::from_pem(&pem).unwrap();
assert_eq!(loaded.node_id(), id.node_id());
assert_eq!(loaded.public_key(), id.public_key());
}
#[test]
fn bad_base58_is_an_error_not_a_crash() {
assert!(NodeId::from_base58("not valid base58 !!!").is_err());
}
}