use core::convert::{TryFrom, TryInto};
use core::str::FromStr;
use core::fmt;
use secp256k1::{PublicKey, Secp256k1, SecretKey};
use crate::NodeId;
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, string::String, vec::Vec};
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct NodePubkey(
pub PublicKey
);
impl NodePubkey {
#[cfg(feature = "node_pubkey_verify")]
pub fn verify<M: Into<secp256k1::Message>, C: secp256k1::Verification>(&self, secp: &Secp256k1<C>, message: M, signature: &[u8]) -> Result<(), secp256k1::Error> {
use secp256k1::ecdsa::Signature;
let signature = Signature::from_compact(signature)?;
let message = message.into();
secp.verify_ecdsa(&message, &signature, &self.0)
}
#[cfg(feature = "node_pubkey_recovery")]
pub fn verify_lightning_message<C: secp256k1::Verification>(&self, secp: &Secp256k1<C>, message: &[u8], signature: &[u8]) -> Result<(), secp256k1::Error> {
use secp256k1::Message;
use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use secp256k1::hashes::{sha256, sha256d, HashEngine, Hash};
let (recovery_id, signature) = signature
.split_first()
.ok_or(secp256k1::Error::InvalidSignature)?;
let recovery_id = recovery_id
.checked_sub(0x1f)
.ok_or(secp256k1::Error::InvalidSignature)?;
let recovery_id = RecoveryId::from_i32(recovery_id.into())?;
let signature = RecoverableSignature::from_compact(signature, recovery_id)?;
let mut hasher = sha256::HashEngine::default();
hasher.input(b"Lightning Signed Message:");
hasher.input(message);
let hash = sha256d::Hash::from_engine(hasher);
let message = Message::from_digest(hash.to_byte_array());
let pubkey = secp.recover_ecdsa(&message, &signature)?;
if pubkey == self.0 {
Ok(())
} else {
Err(secp256k1::Error::IncorrectSignature)
}
}
#[cfg(feature = "alloc")]
fn internal_parse<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, ParseError> {
match NodeId::parse_raw(s.as_ref()) {
Ok(node_id) => {
node_id.try_into()
.map_err(|error| ParseError {
input: s.into(),
reason: ParseErrorInner::Pubkey(error),
})
},
Err(error) => {
Err(ParseError {
input: s.into(),
reason: ParseErrorInner::NodeId(error),
})
}
}
}
#[cfg(not(feature = "alloc"))]
fn internal_parse<S: AsRef<str>>(s: S) -> Result<Self, ParseError> {
match NodeId::parse_raw(s.as_ref()) {
Ok(node_id) => {
node_id.try_into()
.map_err(|error| ParseError {
reason: ParseErrorInner::Pubkey(error),
})
},
Err(error) => {
Err(ParseError {
reason: ParseErrorInner::NodeId(error),
})
}
}
}
pub fn to_node_id(&self) -> NodeId {
NodeId::from_raw_bytes(self.0.serialize()).expect("secp256k1 always creates correct node ID")
}
pub fn from_secret_key<C: secp256k1::Signing>(secp: &Secp256k1<C>, sk: &SecretKey) -> Self {
NodePubkey(PublicKey::from_secret_key(secp, sk))
}
}
impl fmt::Display for NodePubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.to_node_id(), f)
}
}
impl fmt::Debug for NodePubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.to_node_id(), f)
}
}
impl fmt::LowerHex for NodePubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&self.to_node_id(), f)
}
}
impl fmt::UpperHex for NodePubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::UpperHex::fmt(&self.to_node_id(), f)
}
}
impl TryFrom<NodeId> for NodePubkey {
type Error = secp256k1::Error;
fn try_from(value: NodeId) -> Result<Self, Self::Error> {
Ok(NodePubkey(PublicKey::from_slice(value.as_ref())?))
}
}
impl From<NodePubkey> for NodeId {
fn from(value: NodePubkey) -> Self {
value.to_node_id()
}
}
impl<'a> From<&'a NodePubkey> for NodeId {
fn from(value: &'a NodePubkey) -> Self {
value.to_node_id()
}
}
impl AsRef<PublicKey> for NodePubkey {
fn as_ref(&self) -> &PublicKey {
&self.0
}
}
impl AsMut<PublicKey> for NodePubkey {
fn as_mut(&mut self) -> &mut PublicKey {
&mut self.0
}
}
impl core::borrow::Borrow<PublicKey> for NodePubkey {
fn borrow(&self) -> &PublicKey {
&self.0
}
}
impl core::borrow::BorrowMut<PublicKey> for NodePubkey {
fn borrow_mut(&mut self) -> &mut PublicKey {
&mut self.0
}
}
impl FromStr for NodePubkey {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::internal_parse(s)
}
}
impl<'a> TryFrom<&'a str> for NodePubkey {
type Error = ParseError;
#[inline]
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
Self::internal_parse(s)
}
}
#[cfg(feature = "alloc")]
impl TryFrom<String> for NodePubkey {
type Error = ParseError;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::internal_parse(s)
}
}
#[cfg(feature = "alloc")]
impl TryFrom<Box<str>> for NodePubkey {
type Error = ParseError;
#[inline]
fn try_from(s: Box<str>) -> Result<Self, Self::Error> {
Self::internal_parse(s)
}
}
impl<'a> TryFrom<&'a [u8]> for NodePubkey {
type Error = secp256k1::Error;
#[inline]
fn try_from(slice: &'a [u8]) -> Result<Self, Self::Error> {
Ok(NodePubkey(PublicKey::from_slice(slice)?))
}
}
#[cfg(feature = "alloc")]
impl TryFrom<Vec<u8>> for NodePubkey {
type Error = secp256k1::Error;
#[inline]
fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
(*vec).try_into()
}
}
#[cfg(feature = "alloc")]
impl TryFrom<Box<[u8]>> for NodePubkey {
type Error = secp256k1::Error;
#[inline]
fn try_from(slice: Box<[u8]>) -> Result<Self, Self::Error> {
(*slice).try_into()
}
}
impl From<NodePubkey> for [u8; 33] {
fn from(value: NodePubkey) -> Self {
value.to_node_id().into()
}
}
#[derive(Debug, Clone)]
pub struct ParseError {
#[cfg(feature = "alloc")]
input: String,
reason: ParseErrorInner,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_err!(f, "failed to parse{} Lightning Network node public key", opt_fmt!("alloc", format_args!(" '{}' as", &self.input)); &self.reason)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.reason)
}
}
#[derive(Debug, Clone)]
enum ParseErrorInner {
NodeId(crate::node_id::ParseErrorInner),
Pubkey(secp256k1::Error),
}
impl fmt::Display for ParseErrorInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseErrorInner::NodeId(error) => write_err!(f, "invalid node ID"; error),
ParseErrorInner::Pubkey(error) => write_err_ext!("secp256k1_std", f, "invalid public key"; error),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseErrorInner {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
ParseErrorInner::NodeId(error) => Some(error),
#[cfg(feature = "secp256k1_std")]
ParseErrorInner::Pubkey(error) => Some(error),
#[cfg(not(feature = "secp256k1_std"))]
ParseErrorInner::Pubkey(_) => None,
}
}
}
#[cfg(feature = "parse_arg")]
mod parse_arg_impl {
use core::fmt;
use super::NodePubkey;
impl parse_arg::ParseArgFromStr for NodePubkey {
fn describe_type<W: fmt::Write>(mut writer: W) -> fmt::Result {
writer.write_str("a hex-encoded LN node ID (66 hex digits/33 bytes)")
}
}
}
#[cfg(all(feature = "serde", feature = "secp256k1/serde"))]
mod serde_impls {
use serde::{Serialize, Deserialize, Serializer, Deserializer};
use super::NodePubkey;
use secp256k1::PublicKey;
#[cfg_attr(all(feature = "serde", feature = "secp256k1/serde"))]
impl Serialize for NodePubkey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
Seialize::serialize(&self.0, serializer)
}
}
#[cfg_attr(all(feature = "serde", feature = "secp256k1/serde"))]
impl<'de> Deserialize<'de> for NodePubkey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
Ok(PublicKey::deserialize(deserializer)?)
}
}
}
#[cfg(feature = "postgres-types")]
mod postgres_impl {
use alloc::boxed::Box;
use super::NodePubkey;
use crate::NodeId;
use postgres_types::{ToSql, FromSql, IsNull, Type};
use bytes::BytesMut;
use std::error::Error;
use core::convert::TryInto;
impl ToSql for NodePubkey {
fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Send + Sync + 'static>> {
self.to_node_id().to_sql(ty, out)
}
fn accepts(ty: &Type) -> bool {
<NodeId as ToSql>::accepts(ty)
}
postgres_types::to_sql_checked!();
}
impl<'a> FromSql<'a> for NodePubkey {
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
NodeId::from_sql(ty, raw)?.try_into().map_err(|error| Box::new(error) as _)
}
fn accepts(ty: &Type) -> bool {
<NodeId as FromSql>::accepts(ty)
}
}
}
#[cfg(feature = "slog")]
mod slog_impl {
use super::NodePubkey;
use slog::{Key, Value, Record, Serializer};
impl Value for NodePubkey {
fn serialize(&self, _rec: &Record, key: Key, serializer: &mut dyn Serializer) -> slog::Result {
serializer.emit_arguments(key, &format_args!("{}", self))
}
}
impl_error_value!(super::ParseError);
}
#[cfg(test)]
mod tests {
use super::NodePubkey;
#[test]
fn empty() {
let error = "".parse::<NodePubkey>().unwrap_err();
#[cfg(feature = "alloc")]
assert_eq!(error.input, "");
match &error.reason {
super::ParseErrorInner::NodeId(_) => (),
super::ParseErrorInner::Pubkey(error) => panic!("unexpected error variant: Pubkey({:?})", error),
}
}
chk_err_impl! {
parse_node_pubkey_error_empty, "", NodePubkey, [
"failed to parse '' as Lightning Network node public key",
"invalid node ID",
"invalid length (must be 66 chars)",
], [
"failed to parse Lightning Network node public key",
"invalid node ID",
"invalid length (must be 66 chars)",
];
}
#[cfg(any(not(feature = "std"), feature = "secp256k1_std"))]
chk_err_impl! {
parse_node_pubkey_error_invalid_pubkey, "020000000000000000000000000000000000000000000000000000000000000000", NodePubkey, [
"failed to parse '020000000000000000000000000000000000000000000000000000000000000000' as Lightning Network node public key",
"invalid public key",
"malformed public key",
], [
"failed to parse Lightning Network node public key",
"invalid public key",
"malformed public key",
];
}
#[cfg(all(feature = "std", not(feature = "secp256k1_std")))]
chk_err_impl! {
parse_node_pubkey_error_invalid_pubkey, "020000000000000000000000000000000000000000000000000000000000000000", NodePubkey, [
"failed to parse '020000000000000000000000000000000000000000000000000000000000000000' as Lightning Network node public key",
"invalid public key: malformed public key",
], [
"irrelevant, we definitely have std and thus alloc",
];
}
}