use std::{collections::HashSet, fmt, str::FromStr};
use bitcoin::{secp256k1, secp256k1::Secp256k1};
use lexe_byte_array::ByteArray;
use lexe_crypto::ed25519::{self, Signable};
#[cfg(any(test, feature = "test-utils"))]
use lexe_crypto::rng::FastRng;
use lexe_hex::hex;
use lexe_serde::hexstr_or_bytes;
use lexe_sha256::sha256;
use lexe_std::array;
#[cfg(any(test, feature = "test-utils"))]
use proptest::{
arbitrary::{Arbitrary, any},
strategy::{BoxedStrategy, Strategy},
};
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use ref_cast::RefCast;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(any(test, feature = "test-utils"))]
use crate::root_seed::RootSeed;
use crate::secp256k1_ctx::SECP256K1;
#[cfg(any(test, feature = "test-utils"))]
use crate::test_utils::arbitrary;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct User {
pub user_pk: UserPk,
pub node_pk: NodePk,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct MaybeUser {
pub maybe_user: Option<User>,
}
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, RefCast, Serialize, Deserialize)]
#[repr(transparent)]
pub struct UserPk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Copy, Clone, Hash, Eq, PartialEq, RefCast, Serialize, Deserialize)]
#[repr(transparent)]
pub struct ShortUserPk(#[serde(with = "hexstr_or_bytes")] [u8; 4]);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct UserPkStruct {
pub user_pk: UserPk,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct UserPkSet {
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_hashset::<UserPk>()")
)]
pub user_pks: HashSet<UserPk>,
}
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
#[derive(RefCast, Serialize, Deserialize)]
#[repr(transparent)]
pub struct NodePk(pub secp256k1::PublicKey);
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize)]
pub struct NodePkProof {
node_pk: NodePk,
sig: secp256k1::ecdsa::Signature,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct NodePkStruct {
pub node_pk: NodePk,
}
#[derive(Debug, Error)]
#[error("invalid node pk proof signature")]
pub struct InvalidNodePkProofSignature;
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Serialize, Deserialize)]
pub struct Scid(pub u64);
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct ScidStruct {
pub scid: Scid,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct Scids {
pub scids: Vec<Scid>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct MaybeScid {
pub maybe_scid: Option<Scid>,
}
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct UserScid {
pub node_pk: NodePk,
pub scid: Scid,
}
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct UserScids {
pub user_scids: Vec<UserScid>,
}
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetNewScidsRequest {
pub node_pk: NodePk,
pub min_scids: usize,
}
impl UserPk {
pub const fn new(inner: [u8; 32]) -> Self {
Self(inner)
}
pub const fn from_ref(inner: &[u8; 32]) -> &Self {
lexe_std::const_utils::const_ref_cast(inner)
}
pub const fn as_ed25519(&self) -> &ed25519::PublicKey {
ed25519::PublicKey::from_ref(&self.0)
}
pub fn short(&self) -> ShortUserPk {
ShortUserPk::from(self)
}
pub fn from_u64(v: u64) -> Self {
let bytes = v.to_le_bytes();
let mut inner = [0u8; 32];
inner[0..8].copy_from_slice(&bytes);
Self(inner)
}
pub fn to_u64(self) -> u64 {
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&self.0[0..8]);
u64::from_le_bytes(bytes)
}
}
lexe_byte_array::impl_byte_array!(UserPk, 32);
lexe_byte_array::impl_fromstr_fromhex!(UserPk, 32);
lexe_byte_array::impl_debug_display_as_hex!(UserPk);
impl ShortUserPk {
pub const fn new(bytes: [u8; 4]) -> Self {
Self(bytes)
}
pub fn is_prefix_of(&self, long: &UserPk) -> bool {
self.0 == long.0[..4]
}
}
lexe_byte_array::impl_byte_array!(ShortUserPk, 4);
lexe_byte_array::impl_fromstr_fromhex!(ShortUserPk, 4);
lexe_byte_array::impl_debug_display_as_hex!(ShortUserPk);
impl From<&UserPk> for ShortUserPk {
fn from(long: &UserPk) -> Self {
(long.0)[..4].try_into().map(Self).unwrap()
}
}
impl From<ed25519::PublicKey> for UserPk {
fn from(pk: ed25519::PublicKey) -> Self {
Self::new(pk.into_inner())
}
}
impl NodePk {
pub fn inner(self) -> secp256k1::PublicKey {
self.0
}
pub fn as_inner(&self) -> &secp256k1::PublicKey {
&self.0
}
pub fn from_slice(bytes: &[u8]) -> Result<Self, secp256k1::Error> {
secp256k1::PublicKey::from_slice(bytes).map(Self)
}
pub fn from_inner_ref(pk: &secp256k1::PublicKey) -> &Self {
Self::ref_cast(pk)
}
pub fn to_array(&self) -> [u8; 33] {
self.0.serialize()
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.serialize().to_vec()
}
}
impl FromStr for NodePk {
type Err = bitcoin::secp256k1::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
secp256k1::PublicKey::from_str(s).map(Self)
}
}
impl fmt::Display for NodePk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for NodePk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NodePk({self})")
}
}
impl From<secp256k1::PublicKey> for NodePk {
fn from(public_key: secp256k1::PublicKey) -> Self {
Self(public_key)
}
}
impl From<NodePk> for secp256k1::PublicKey {
fn from(node_pk: NodePk) -> secp256k1::PublicKey {
node_pk.0
}
}
#[cfg(any(test, feature = "test-utils"))]
impl Arbitrary for NodePk {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<FastRng>()
.prop_map(|mut rng| RootSeed::from_rng(&mut rng).derive_node_pk())
.boxed()
}
}
impl NodePkProof {
fn message(node_pk: &NodePk) -> secp256k1::Message {
let node_pk_bytes = node_pk.0.serialize();
let hash = sha256::digest_many(&[
&NodePkProof::DOMAIN_SEPARATOR,
&node_pk_bytes,
]);
secp256k1::Message::from_digest(hash.to_array())
}
pub fn sign(node_key_pair: &secp256k1::Keypair) -> Self {
let node_pk = NodePk::from(node_key_pair.public_key());
let msg = Self::message(&node_pk);
let sig = SECP256K1.sign_ecdsa(&msg, &node_key_pair.secret_key());
Self { node_pk, sig }
}
pub fn verify(&self) -> Result<&NodePk, InvalidNodePkProofSignature> {
let msg = Self::message(&self.node_pk);
Secp256k1::verification_only()
.verify_ecdsa(&msg, &self.sig, &self.node_pk.0)
.map(|()| &self.node_pk)
.map_err(|_| InvalidNodePkProofSignature)
}
pub fn to_hex_string(&self) -> String {
hex::encode(&bcs::to_bytes(self).expect("Failed to serialize"))
}
}
impl Signable for NodePkProof {
const DOMAIN_SEPARATOR: [u8; 32] = array::pad(*b"LEXE-REALM::NodePkProof");
}
#[cfg(any(test, feature = "test-utils"))]
impl Arbitrary for NodePkProof {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any::<FastRng>()
.prop_map(|mut rng| {
let key_pair =
RootSeed::from_rng(&mut rng).derive_node_key_pair();
NodePkProof::sign(&key_pair)
})
.boxed()
}
}
impl Scid {
pub fn to_i64(&self) -> i64 {
let bytes = self.0.to_le_bytes();
i64::from_le_bytes(bytes)
}
pub fn from_i64(bytes_i64: i64) -> Self {
let bytes = bytes_i64.to_le_bytes();
Self(u64::from_le_bytes(bytes))
}
}
impl FromStr for Scid {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
u64::from_str(s).map(Self)
}
}
impl fmt::Display for Scid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<i64> for Scid {
fn from(i: i64) -> Self {
Self::from_i64(i)
}
}
impl From<Scid> for i64 {
fn from(scid: Scid) -> Self {
scid.to_i64()
}
}
#[cfg(test)]
mod test {
use proptest::{prop_assume, proptest};
use super::*;
use crate::test_utils::roundtrip;
#[test]
fn user_node_pk_ser_examples() {
let mut rng = FastRng::from_u64(811011698);
let root_seed = RootSeed::from_rng(&mut rng);
let user_pk = root_seed.derive_user_pk();
let node_key_pair = root_seed.derive_node_key_pair();
let node_pk = NodePk(node_key_pair.public_key());
let node_pk_proof = NodePkProof::sign(&node_key_pair);
assert_eq!(
"52b999003525a3d905f9916eff26cee6625a3976fc25270ce5b3e79aa3c16f45",
user_pk.to_string()
);
assert_eq!(
"024de9a91aaf32588a7b0bb97ba7fad3db22fcfe62a52bc2b2d389c5fa9d946e1b",
node_pk.to_string(),
);
assert_eq!(
"024de9a91aaf32588a7b0bb97ba7fad3db22fcfe62a52bc2b2d389c5fa9d946e1b46304402206f762d23d206f3af2ffa452a71a11bca3df68838408851ab77931d7eb7fa1ef6022057141408428d6885d00ca6ca50e6d702aeab227c1550135be5fce4af4e726736",
node_pk_proof.to_hex_string(),
);
}
#[test]
fn user_pk_consistent() {
let user_pk1 = UserPk::new(hex::decode_const(
b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
));
let user_pk2 = UserPk::new(hex::decode_const(
b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
));
assert_eq!(user_pk1, user_pk2);
}
#[test]
fn user_pk_human_readable() {
roundtrip::fromstr_display_roundtrip_proptest::<UserPk>();
}
#[test]
fn user_pk_json() {
roundtrip::json_string_roundtrip_proptest::<UserPk>();
}
#[test]
fn node_pk_human_readable() {
roundtrip::fromstr_display_roundtrip_proptest::<NodePk>();
}
#[test]
fn node_pk_json() {
roundtrip::json_string_roundtrip_proptest::<NodePk>();
}
#[test]
fn node_pk_proof_bcs() {
roundtrip::bcs_roundtrip_proptest::<NodePkProof>();
}
#[test]
fn node_pk_proofs_verify() {
let arb_mutation = any::<Vec<u8>>()
.prop_filter("can't be empty or all zeroes", |m| {
!m.is_empty() && !m.iter().all(|x| x == &0u8)
});
proptest!(|(
mut rng: FastRng,
mut_offset in any::<usize>(),
mut mutation in arb_mutation,
)| {
let node_key_pair = RootSeed::from_rng(&mut rng)
.derive_node_key_pair();
let node_pk1 = NodePk::from(node_key_pair.public_key());
let proof1 = NodePkProof::sign(&node_key_pair);
let proof2 = NodePkProof::sign(&node_key_pair);
assert_eq!(proof1, proof2);
let node_pk2 = proof1.verify().unwrap();
assert_eq!(&node_pk1, node_pk2);
let mut proof_bytes = bcs::to_bytes(&proof1).unwrap();
mutation.truncate(proof_bytes.len());
prop_assume!(
!mutation.is_empty() && !mutation.iter().all(|x| x == &0)
);
for (idx_mut, m) in mutation.into_iter().enumerate() {
let idx_sig = idx_mut
.wrapping_add(mut_offset) % proof_bytes.len();
proof_bytes[idx_sig] ^= m;
}
bcs::from_bytes::<NodePkProof>(&proof_bytes)
.map_err(anyhow::Error::new)
.and_then(|proof| {
proof.verify()
.map(|_| ())
.map_err(anyhow::Error::new)
})
.unwrap_err();
});
}
#[test]
fn scid_basic() {
let scid = Scid(69);
assert_eq!(serde_json::to_string(&scid).unwrap(), "69");
}
#[test]
fn scid_roundtrips() {
roundtrip::json_string_roundtrip_proptest::<Scid>();
roundtrip::fromstr_display_roundtrip_proptest::<Scid>();
}
#[test]
fn user_scid_roundtrips() {
roundtrip::json_value_roundtrip_proptest::<UserScid>();
}
#[test]
fn user_pk_struct_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<UserPkStruct>();
}
#[test]
fn node_pk_struct_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<NodePkStruct>();
}
#[test]
fn scid_qs_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<ScidStruct>();
roundtrip::query_string_roundtrip_proptest::<GetNewScidsRequest>();
}
}