use curve25519_dalek::{
edwards::{CompressedEdwardsY, EdwardsPoint},
montgomery::MontgomeryPoint,
};
use elliptic_curve::{sec1::EncodedPoint, NonZeroScalar, ProjectivePoint};
use hopr_primitive_types::errors::GeneralError::ParseError;
use hopr_primitive_types::prelude::*;
use k256::elliptic_curve::group::prime::PrimeCurveAffine;
use k256::{
ecdsa::{
self,
signature::{hazmat::PrehashVerifier, Verifier},
RecoveryId, Signature as ECDSASignature, SigningKey, VerifyingKey,
},
elliptic_curve::{
self,
generic_array::GenericArray,
sec1::{FromEncodedPoint, ToEncodedPoint},
CurveArithmetic,
},
AffinePoint, Secp256k1,
};
use serde::{Deserialize, Serialize};
use sha2::Sha512;
use std::fmt::Debug;
use std::sync::OnceLock;
use std::{
fmt::{Display, Formatter},
ops::Add,
str::FromStr,
};
use tracing::warn;
use crate::utils::random_group_element;
use crate::{
errors::{
CryptoError::{self, CalculationError, InvalidInputValue},
Result,
},
keypairs::{ChainKeypair, Keypair, OffchainKeypair},
primitives::{DigestLike, EthDigest},
};
pub use libp2p_identity::PeerId;
mod arrays {
use std::{convert::TryInto, marker::PhantomData};
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
pub fn serialize<S: Serializer, T: Serialize, const N: usize>(data: &[T; N], ser: S) -> Result<S::Ok, S::Error> {
let mut s = ser.serialize_tuple(N)?;
for item in data {
s.serialize_element(item)?;
}
s.end()
}
struct ArrayVisitor<T, const N: usize>(PhantomData<T>);
impl<'de, T, const N: usize> Visitor<'de> for ArrayVisitor<T, N>
where
T: Deserialize<'de>,
{
type Value = [T; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(&format!("an array of length {}", N))
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut data = Vec::with_capacity(N);
for _ in 0..N {
match seq.next_element()? {
Some(val) => data.push(val),
None => return Err(serde::de::Error::invalid_length(N, &self)),
}
}
match data.try_into() {
Ok(arr) => Ok(arr),
Err(_) => unreachable!(),
}
}
}
pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserializer.deserialize_tuple(N, ArrayVisitor::<T, N>(PhantomData))
}
}
#[derive(Clone, Debug)]
pub struct CurvePoint {
pub(crate) affine: AffinePoint,
compressed: EncodedPoint<Secp256k1>,
uncompressed: OnceLock<EncodedPoint<Secp256k1>>,
}
impl CurvePoint {
pub const SIZE_COMPRESSED: usize = 33;
pub const SIZE_UNCOMPRESSED: usize = 65;
pub fn to_address(&self) -> Address {
let serialized = self.as_uncompressed();
let hash = Hash::create(&[&serialized.as_bytes()[1..]]);
Address::new(&hash.as_ref()[12..])
}
pub fn from_exponent(exponent: &[u8]) -> Result<Self> {
PublicKey::from_privkey(exponent).map(CurvePoint::from)
}
pub fn into_projective_point(self) -> ProjectivePoint<Secp256k1> {
self.affine.into()
}
pub fn as_compressed(&self) -> &EncodedPoint<Secp256k1> {
&self.compressed
}
pub fn as_uncompressed(&self) -> &EncodedPoint<Secp256k1> {
self.uncompressed.get_or_init(|| self.affine.to_encoded_point(false))
}
pub fn combine(summands: &[&CurvePoint]) -> CurvePoint {
let affine: AffinePoint = summands
.iter()
.map(|p| ProjectivePoint::<Secp256k1>::from(p.affine))
.fold(<Secp256k1 as CurveArithmetic>::ProjectivePoint::IDENTITY, |acc, x| {
acc.add(x)
})
.to_affine();
affine.into()
}
}
impl Default for CurvePoint {
fn default() -> Self {
Self::from(AffinePoint::default())
}
}
impl PartialEq for CurvePoint {
fn eq(&self, other: &Self) -> bool {
self.affine.eq(&other.affine)
}
}
impl Eq for CurvePoint {}
impl From<PublicKey> for CurvePoint {
fn from(pubkey: PublicKey) -> Self {
pubkey.0
}
}
impl From<&PublicKey> for CurvePoint {
fn from(pubkey: &PublicKey) -> Self {
pubkey.0.clone()
}
}
impl From<AffinePoint> for CurvePoint {
fn from(affine: AffinePoint) -> Self {
Self {
affine,
compressed: affine.to_encoded_point(true),
uncompressed: OnceLock::new(),
}
}
}
impl TryFrom<HalfKeyChallenge> for CurvePoint {
type Error = GeneralError;
fn try_from(value: HalfKeyChallenge) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.0.as_ref())
}
}
impl FromStr for CurvePoint {
type Err = CryptoError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(CurvePoint::try_from(
hex::decode(s)
.map_err(|_| GeneralError::ParseError("CurvePoint".into()))?
.as_slice(),
)?)
}
}
impl From<CurvePoint> for AffinePoint {
fn from(value: CurvePoint) -> Self {
value.affine
}
}
impl AsRef<[u8]> for CurvePoint {
fn as_ref(&self) -> &[u8] {
self.compressed.as_ref()
}
}
impl TryFrom<&[u8]> for CurvePoint {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
let ep = elliptic_curve::sec1::EncodedPoint::<Secp256k1>::from_bytes(value)
.map_err(|_| GeneralError::ParseError("invalid secp256k1 encoded point".into()))?;
Ok(Self {
affine: Option::from(AffinePoint::from_encoded_point(&ep))
.ok_or(GeneralError::ParseError("invalid secp256k1 encoded point".into()))?,
compressed: if ep.is_compressed() { ep } else { ep.compress() },
uncompressed: if !ep.is_compressed() {
ep.into()
} else {
OnceLock::new()
},
})
}
}
impl BytesRepresentable for CurvePoint {
const SIZE: usize = Self::SIZE_COMPRESSED;
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Challenge(CurvePoint);
impl Challenge {
pub fn to_ethereum_challenge(&self) -> EthereumChallenge {
EthereumChallenge::new(self.0.to_address().as_ref())
}
}
impl From<Challenge> for EthereumChallenge {
fn from(challenge: Challenge) -> Self {
challenge.to_ethereum_challenge()
}
}
impl Challenge {
pub fn from_hint_and_share(own_share: &HalfKeyChallenge, hint: &HalfKeyChallenge) -> Result<Self> {
let curve_point: CurvePoint = PublicKey::combine(&[
&PublicKey::try_from(own_share.0.as_ref())?,
&PublicKey::try_from(hint.0.as_ref())?,
])
.into();
Ok(curve_point.into())
}
pub fn from_own_share_and_half_key(own_share: &HalfKeyChallenge, half_key: &HalfKey) -> Result<Self> {
Self::from_hint_and_share(own_share, &half_key.to_challenge())
}
}
impl From<CurvePoint> for Challenge {
fn from(curve_point: CurvePoint) -> Self {
Self(curve_point)
}
}
impl From<Response> for Challenge {
fn from(response: Response) -> Self {
response.to_challenge()
}
}
impl AsRef<[u8]> for Challenge {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for Challenge {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}
impl BytesRepresentable for Challenge {
const SIZE: usize = CurvePoint::SIZE_COMPRESSED;
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct HalfKey {
hkey: [u8; Self::SIZE],
}
impl Default for HalfKey {
fn default() -> Self {
let mut ret = Self {
hkey: [0u8; Self::SIZE],
};
ret.hkey.copy_from_slice(
NonZeroScalar::<Secp256k1>::from_uint(1u16.into())
.unwrap()
.to_bytes()
.as_slice(),
);
ret
}
}
impl HalfKey {
pub fn random() -> Self {
Self {
hkey: random_group_element().0,
}
}
pub fn to_challenge(&self) -> HalfKeyChallenge {
CurvePoint::from_exponent(&self.hkey)
.map(|cp| HalfKeyChallenge::new(cp.as_compressed().as_bytes()))
.expect("invalid public key")
}
}
impl AsRef<[u8]> for HalfKey {
fn as_ref(&self) -> &[u8] {
&self.hkey
}
}
impl TryFrom<&[u8]> for HalfKey {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self {
hkey: value.try_into().map_err(|_| ParseError("HalfKey".into()))?,
})
}
}
impl BytesRepresentable for HalfKey {
const SIZE: usize = 32;
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub struct HalfKeyChallenge(#[serde(with = "arrays")] [u8; Self::SIZE]);
impl Display for HalfKeyChallenge {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl Default for HalfKeyChallenge {
fn default() -> Self {
let mut ret = Self([0u8; Self::SIZE]);
ret.0[Self::SIZE - 1] = 1;
ret
}
}
impl HalfKeyChallenge {
pub fn new(half_key_challenge: &[u8]) -> Self {
assert_eq!(half_key_challenge.len(), Self::SIZE, "invalid length");
let mut ret = Self::default();
ret.0.copy_from_slice(half_key_challenge);
ret
}
pub fn to_address(&self) -> Address {
PublicKey::try_from(self.0.as_ref())
.expect("invalid half-key")
.to_address()
}
}
impl AsRef<[u8]> for HalfKeyChallenge {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for HalfKeyChallenge {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(
value.try_into().map_err(|_| ParseError("HalfKeyChallenge".into()))?,
))
}
}
impl BytesRepresentable for HalfKeyChallenge {
const SIZE: usize = PublicKey::SIZE_COMPRESSED;
}
impl std::hash::Hash for HalfKeyChallenge {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl FromStr for HalfKeyChallenge {
type Err = GeneralError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::from_hex(s)
}
}
impl From<HalfKey> for HalfKeyChallenge {
fn from(half_key: HalfKey) -> Self {
half_key.to_challenge()
}
}
#[derive(Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize, PartialOrd, Ord, std::hash::Hash)]
pub struct Hash([u8; Self::SIZE]);
impl Debug for Hash {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl Display for Hash {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl FromStr for Hash {
type Err = GeneralError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::from_hex(s)
}
}
impl Hash {
pub fn hash(&self) -> Self {
Self::create(&[&self.0])
}
pub fn create(inputs: &[&[u8]]) -> Self {
let mut hash = EthDigest::default();
inputs.iter().for_each(|v| hash.update(v));
let mut ret = Self([0u8; Self::SIZE]);
hash.finalize_into(&mut ret.0);
ret
}
}
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Hash {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(value.try_into().map_err(|_| ParseError("Hash".into()))?))
}
}
impl BytesRepresentable for Hash {
const SIZE: usize = EthDigest::SIZE;
}
impl From<[u8; Self::SIZE]> for Hash {
fn from(hash: [u8; Self::SIZE]) -> Self {
Self(hash)
}
}
impl From<Hash> for [u8; Hash::SIZE] {
fn from(value: Hash) -> Self {
value.0
}
}
impl From<&Hash> for [u8; Hash::SIZE] {
fn from(value: &Hash) -> Self {
value.0
}
}
impl From<Hash> for primitive_types::H256 {
fn from(value: Hash) -> Self {
value.0.into()
}
}
impl From<primitive_types::H256> for Hash {
fn from(value: primitive_types::H256) -> Self {
Self(value.0)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
pub struct OffchainPublicKey(CompressedEdwardsY);
impl AsRef<[u8]> for OffchainPublicKey {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl TryFrom<&[u8]> for OffchainPublicKey {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(
CompressedEdwardsY::from_slice(value).map_err(|_| ParseError("OffchainPublicKey".into()))?,
))
}
}
impl BytesRepresentable for OffchainPublicKey {
const SIZE: usize = 32;
}
impl TryFrom<[u8; OffchainPublicKey::SIZE]> for OffchainPublicKey {
type Error = GeneralError;
fn try_from(value: [u8; OffchainPublicKey::SIZE]) -> std::result::Result<Self, Self::Error> {
let v: &[u8] = &value;
v.try_into()
}
}
impl TryFrom<&PeerId> for OffchainPublicKey {
type Error = GeneralError;
fn try_from(value: &PeerId) -> std::result::Result<Self, Self::Error> {
let mh = value.as_ref();
if mh.code() == 0 {
libp2p_identity::PublicKey::try_decode_protobuf(mh.digest())
.map_err(|_| GeneralError::ParseError("invalid ed25519 peer id".into()))
.and_then(|pk| {
pk.try_into_ed25519()
.map(|p| p.to_bytes())
.map_err(|_| GeneralError::ParseError("invalid ed25519 peer id".into()))
})
.and_then(|pk| {
CompressedEdwardsY::from_slice(&pk)
.map_err(|_| GeneralError::ParseError("invalid ed25519 peerid".into()))
})
.map(Self)
} else {
Err(GeneralError::ParseError("invalid ed25519 peer id".into()))
}
}
}
impl TryFrom<PeerId> for OffchainPublicKey {
type Error = GeneralError;
fn try_from(value: PeerId) -> std::result::Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl From<OffchainPublicKey> for PeerId {
fn from(value: OffchainPublicKey) -> Self {
let k = libp2p_identity::ed25519::PublicKey::try_from_bytes(value.0.as_bytes()).unwrap();
PeerId::from_public_key(&k.into())
}
}
impl From<&OffchainPublicKey> for PeerId {
fn from(value: &OffchainPublicKey) -> Self {
(*value).into()
}
}
impl Display for OffchainPublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl OffchainPublicKey {
pub fn from_privkey(private_key: &[u8]) -> Result<Self> {
let mut pk: [u8; ed25519_dalek::SECRET_KEY_LENGTH] = private_key.try_into().map_err(|_| InvalidInputValue)?;
let sk = libp2p_identity::ed25519::SecretKey::try_from_bytes(&mut pk).map_err(|_| InvalidInputValue)?;
let kp: libp2p_identity::ed25519::Keypair = sk.into();
Ok(Self(
CompressedEdwardsY::from_slice(&kp.public().to_bytes())
.map_err(|_| GeneralError::ParseError("OffchainPublicKey".into()))?,
))
}
pub fn to_peerid_str(&self) -> String {
PeerId::from(self).to_base58()
}
}
impl From<&OffchainPublicKey> for EdwardsPoint {
fn from(value: &OffchainPublicKey) -> Self {
value.0.decompress().unwrap()
}
}
impl From<&OffchainPublicKey> for MontgomeryPoint {
fn from(value: &OffchainPublicKey) -> Self {
value.0.decompress().unwrap().to_montgomery()
}
}
pub const PACKET_TAG_LENGTH: usize = 16;
pub type PacketTag = [u8; PACKET_TAG_LENGTH];
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PublicKey(CurvePoint);
impl PublicKey {
pub const SIZE_COMPRESSED: usize = 33;
pub const SIZE_UNCOMPRESSED_PLAIN: usize = 64;
pub const SIZE_UNCOMPRESSED: usize = 65;
pub fn from_privkey(private_key: &[u8]) -> Result<PublicKey> {
let secret_scalar = NonZeroScalar::<Secp256k1>::try_from(private_key)
.map_err(|_| GeneralError::ParseError("PublicKey".into()))?;
let key = elliptic_curve::PublicKey::<Secp256k1>::from_secret_scalar(&secret_scalar);
Ok(key.into())
}
fn from_raw_signature<R>(msg: &[u8], r: &[u8], s: &[u8], v: u8, recovery_method: R) -> Result<PublicKey>
where
R: Fn(&[u8], &ECDSASignature, RecoveryId) -> std::result::Result<VerifyingKey, ecdsa::Error>,
{
let recid = RecoveryId::try_from(v).map_err(|_| GeneralError::ParseError("Signature".into()))?;
let signature =
ECDSASignature::from_scalars(GenericArray::clone_from_slice(r), GenericArray::clone_from_slice(s))
.map_err(|_| GeneralError::ParseError("Signature".into()))?;
let recovered_key = *recovery_method(msg, &signature, recid)
.map_err(|_| CalculationError)?
.as_affine();
recovered_key.try_into()
}
pub fn from_signature(msg: &[u8], signature: &Signature) -> Result<PublicKey> {
let (raw_signature, recovery) = signature.raw_signature();
Self::from_raw_signature(
msg,
&raw_signature[0..Signature::SIZE / 2],
&raw_signature[Signature::SIZE / 2..],
recovery,
VerifyingKey::recover_from_msg,
)
}
pub fn from_signature_hash(hash: &[u8], signature: &Signature) -> Result<PublicKey> {
let (raw_signature, recovery) = signature.raw_signature();
Self::from_raw_signature(
hash,
&raw_signature[0..Signature::SIZE / 2],
&raw_signature[Signature::SIZE / 2..],
recovery,
VerifyingKey::recover_from_prehash,
)
}
pub fn combine(summands: &[&PublicKey]) -> PublicKey {
let cps = summands.iter().map(|pk| CurvePoint::from(*pk)).collect::<Vec<_>>();
let cps_ref = cps.iter().collect::<Vec<_>>();
CurvePoint::combine(&cps_ref)
.try_into()
.expect("combination results in the ec identity (which is an invalid pub key)")
}
pub fn tweak_add(key: &PublicKey, tweak: &[u8]) -> PublicKey {
let scalar = NonZeroScalar::<Secp256k1>::try_from(tweak).expect("zero tweak provided");
let new_pk = (key.0.clone().into_projective_point()
+ <Secp256k1 as CurveArithmetic>::ProjectivePoint::GENERATOR * scalar.as_ref())
.to_affine();
new_pk.try_into().expect("tweak add resulted in an invalid public key")
}
pub fn random() -> Self {
let (_, cp) = random_group_element();
cp.try_into()
.expect("random_group_element cannot generate identity points")
}
pub fn to_address(&self) -> Address {
let uncompressed = self.to_bytes(false);
let serialized = Hash::create(&[&uncompressed[1..]]);
Address::new(&serialized.as_ref()[12..])
}
pub fn to_bytes(&self, compressed: bool) -> Box<[u8]> {
match compressed {
true => self.0.as_compressed().to_bytes(),
false => self.0.as_uncompressed().to_bytes(),
}
}
pub fn to_hex(&self, compressed: bool) -> String {
let offset = if compressed { 0 } else { 1 };
format!("0x{}", hex::encode(&self.to_bytes(compressed)[offset..]))
}
}
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex(true))
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
match value.len() {
Self::SIZE_UNCOMPRESSED => {
let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(value)
.map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
Ok(key.into())
}
Self::SIZE_UNCOMPRESSED_PLAIN => {
let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(&[&[4u8], value].concat())
.map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
Ok(key.into())
}
Self::SIZE_COMPRESSED => {
let key = elliptic_curve::PublicKey::<Secp256k1>::from_sec1_bytes(value)
.map_err(|_| GeneralError::ParseError("invalid secp256k1 point".into()))?;
Ok(key.into())
}
_ => Err(GeneralError::ParseError("invalid secp256k1 point".into())),
}
}
}
impl TryFrom<AffinePoint> for PublicKey {
type Error = CryptoError;
fn try_from(value: AffinePoint) -> std::result::Result<Self, Self::Error> {
if value.is_identity().into() {
return Err(CryptoError::InvalidPublicKey);
}
Ok(Self(value.into()))
}
}
impl TryFrom<CurvePoint> for PublicKey {
type Error = CryptoError;
fn try_from(value: CurvePoint) -> std::result::Result<Self, Self::Error> {
if value.affine.is_identity().into() {
return Err(CryptoError::InvalidPublicKey);
}
Ok(Self(value))
}
}
impl From<elliptic_curve::PublicKey<Secp256k1>> for PublicKey {
fn from(key: elliptic_curve::PublicKey<Secp256k1>) -> Self {
Self((*key.as_affine()).into())
}
}
impl From<&PublicKey> for k256::ProjectivePoint {
fn from(value: &PublicKey) -> Self {
value.0.clone().into_projective_point()
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CompressedPublicKey(pub PublicKey);
impl TryFrom<&[u8]> for CompressedPublicKey {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(PublicKey::try_from(value)?.into())
}
}
impl AsRef<[u8]> for CompressedPublicKey {
fn as_ref(&self) -> &[u8] {
self.0 .0.as_ref()
}
}
impl BytesRepresentable for CompressedPublicKey {
const SIZE: usize = PublicKey::SIZE_COMPRESSED;
}
impl From<PublicKey> for CompressedPublicKey {
fn from(value: PublicKey) -> Self {
Self(value)
}
}
impl From<&CompressedPublicKey> for k256::ProjectivePoint {
fn from(value: &CompressedPublicKey) -> Self {
(&value.0).into()
}
}
impl CompressedPublicKey {
pub fn to_address(&self) -> Address {
self.0.to_address()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response([u8; Self::SIZE]);
impl Default for Response {
fn default() -> Self {
let mut ret = Self([0u8; Self::SIZE]);
ret.0.copy_from_slice(
NonZeroScalar::<Secp256k1>::from_uint(1u16.into())
.unwrap()
.to_bytes()
.as_slice(),
);
ret
}
}
impl Display for Response {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_hex().as_str())
}
}
impl Response {
pub fn to_challenge(&self) -> Challenge {
Challenge(CurvePoint::from_exponent(&self.0).expect("response represents an invalid non-zero scalar"))
}
pub fn from_half_keys(first: &HalfKey, second: &HalfKey) -> Result<Self> {
let res = NonZeroScalar::<Secp256k1>::try_from(first.as_ref())
.and_then(|s1| NonZeroScalar::<Secp256k1>::try_from(second.as_ref()).map(|s2| s1.as_ref() + s2.as_ref()))
.map_err(|_| CalculationError)?; Ok(Response::try_from(res.to_bytes().as_slice())?)
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Response {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(value.try_into().map_err(|_| ParseError("Response".into()))?))
}
}
impl BytesRepresentable for Response {
const SIZE: usize = 32;
}
impl From<[u8; Self::SIZE]> for Response {
fn from(value: [u8; Self::SIZE]) -> Self {
Self(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OffchainSignature {
#[serde(with = "arrays")]
signature: [u8; Self::SIZE],
}
impl OffchainSignature {
pub fn sign_message(msg: &[u8], signing_keypair: &OffchainKeypair) -> Self {
let expanded_sk = ed25519_dalek::hazmat::ExpandedSecretKey::from(
&ed25519_dalek::SecretKey::try_from(signing_keypair.secret().as_ref()).expect("invalid private key"),
);
let verifying = ed25519_dalek::VerifyingKey::from_bytes(signing_keypair.public().0.as_bytes()).unwrap();
ed25519_dalek::hazmat::raw_sign::<Sha512>(&expanded_sk, msg, &verifying).into()
}
pub fn verify_message(&self, msg: &[u8], public_key: &OffchainPublicKey) -> bool {
let sgn = ed25519_dalek::Signature::from_slice(&self.signature).expect("corrupted OffchainSignature");
let pk = ed25519_dalek::VerifyingKey::from_bytes(public_key.0.as_bytes()).unwrap();
pk.verify_strict(msg, &sgn).is_ok()
}
}
impl AsRef<[u8]> for OffchainSignature {
fn as_ref(&self) -> &[u8] {
&self.signature
}
}
impl TryFrom<&[u8]> for OffchainSignature {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(ed25519_dalek::Signature::from_slice(value)
.map_err(|_| ParseError("OffchainSignature".into()))?
.into())
}
}
impl BytesRepresentable for OffchainSignature {
const SIZE: usize = ed25519_dalek::Signature::BYTE_SIZE;
}
impl From<ed25519_dalek::Signature> for OffchainSignature {
fn from(value: ed25519_dalek::Signature) -> Self {
let mut ret = Self {
signature: [0u8; Self::SIZE],
};
ret.signature.copy_from_slice(value.to_bytes().as_ref());
ret
}
}
impl TryFrom<([u8; 32], [u8; 32])> for OffchainSignature {
type Error = GeneralError;
fn try_from(value: ([u8; 32], [u8; 32])) -> std::result::Result<Self, Self::Error> {
Ok(ed25519_dalek::Signature::from_components(value.0, value.1).into())
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Signature(#[serde(with = "arrays")] [u8; Self::SIZE]);
impl Signature {
pub fn new(raw_bytes: &[u8], recovery: u8) -> Signature {
assert_eq!(raw_bytes.len(), Self::SIZE, "invalid length");
assert!(recovery <= 1, "invalid recovery bit");
let mut ret = Self([0u8; Self::SIZE]);
ret.0.copy_from_slice(raw_bytes);
ret.embed_recovery_bit(recovery);
ret
}
fn sign<S>(data: &[u8], private_key: &[u8], signing_method: S) -> Signature
where
S: FnOnce(&SigningKey, &[u8]) -> ecdsa::signature::Result<(ECDSASignature, RecoveryId)>,
{
let key = SigningKey::from_bytes(private_key.into()).expect("invalid signing key");
let (sig, rec) = signing_method(&key, data).expect("signing failed");
Self::new(&sig.to_vec(), rec.to_byte())
}
pub fn sign_message(message: &[u8], chain_keypair: &ChainKeypair) -> Signature {
Self::sign(
message,
chain_keypair.secret().as_ref(),
|k: &SigningKey, data: &[u8]| k.sign_recoverable(data),
)
}
pub fn sign_hash(hash: &[u8], chain_keypair: &ChainKeypair) -> Signature {
Self::sign(hash, chain_keypair.secret().as_ref(), |k: &SigningKey, data: &[u8]| {
k.sign_prehash_recoverable(data)
})
}
fn verify<V>(&self, message: &[u8], public_key: &[u8], verifier: V) -> bool
where
V: FnOnce(&VerifyingKey, &[u8], &ECDSASignature) -> ecdsa::signature::Result<()>,
{
let pub_key = VerifyingKey::from_sec1_bytes(public_key).expect("invalid public key");
if let Ok(signature) = ECDSASignature::try_from(self.raw_signature().0.as_ref()) {
verifier(&pub_key, message, &signature).is_ok()
} else {
warn!("un-parseable signature encountered");
false
}
}
pub fn verify_message(&self, message: &[u8], public_key: &PublicKey) -> bool {
self.verify(message, &public_key.to_bytes(false), |k, msg, sgn| k.verify(msg, sgn))
}
pub fn verify_hash(&self, hash: &[u8], public_key: &PublicKey) -> bool {
self.verify(hash, &public_key.to_bytes(false), |k, msg, sgn| {
k.verify_prehash(msg, sgn)
})
}
pub fn raw_signature(&self) -> ([u8; Self::SIZE], u8) {
let mut raw_sig = self.0;
let recovery: u8 = (raw_sig[Self::SIZE / 2] & 0x80 != 0).into();
raw_sig[Self::SIZE / 2] &= 0x7f;
(raw_sig, recovery)
}
fn embed_recovery_bit(&mut self, recovery: u8) {
self.0[Self::SIZE / 2] &= 0x7f;
self.0[Self::SIZE / 2] |= recovery << 7;
}
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Signature {
type Error = GeneralError;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
Ok(Self(value.try_into().map_err(|_| ParseError("Signature".into()))?))
}
}
impl BytesRepresentable for Signature {
const SIZE: usize = 64;
}
impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl Eq for Signature {}
#[cfg(test)]
mod tests {
use crate::utils::random_group_element;
use crate::{
keypairs::{ChainKeypair, Keypair, OffchainKeypair},
types::{
Challenge, CurvePoint, HalfKey, HalfKeyChallenge, Hash, OffchainPublicKey, OffchainSignature, PublicKey,
Response, Signature,
},
};
use ed25519_dalek::Signer;
use hex_literal::hex;
use hopr_primitive_types::prelude::*;
use k256::{
ecdsa::VerifyingKey,
elliptic_curve::{sec1::ToEncodedPoint, CurveArithmetic},
AffinePoint, {NonZeroScalar, Secp256k1, U256},
};
use libp2p_identity::PeerId;
use std::str::FromStr;
const PUBLIC_KEY: [u8; 33] = hex!("021464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8");
const PUBLIC_KEY_UNCOMPRESSED_PLAIN: [u8; 64] = hex!("1464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8fb0699d4f177f9c84712f6d7c5f6b7f4f6916116047fa25c79ef806fc6c9523e");
const PUBLIC_KEY_UNCOMPRESSED: [u8; 65] = hex!("041464586aeaea0eb5736884ca1bf42d165fc8e2243b1d917130fb9e321d7a93b8fb0699d4f177f9c84712f6d7c5f6b7f4f6916116047fa25c79ef806fc6c9523e");
const PRIVATE_KEY: [u8; 32] = hex!("e17fe86ce6e99f4806715b0c9412f8dad89334bf07f72d5834207a9d8f19d7f8");
#[test]
fn test_signature_signing() -> anyhow::Result<()> {
let msg = b"test12345";
let kp = ChainKeypair::from_secret(&PRIVATE_KEY)?;
let sgn = Signature::sign_message(msg, &kp);
let expected_pk = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
assert!(sgn.verify_message(msg, &expected_pk));
let extracted_pk = PublicKey::from_signature(msg, &sgn)?;
assert_eq!(expected_pk, extracted_pk, "key extracted from signature does not match");
Ok(())
}
#[test]
fn test_offchain_signature_signing() -> anyhow::Result<()> {
let msg = b"test12345";
let keypair = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
let key = ed25519_dalek::SecretKey::try_from(PRIVATE_KEY)?;
let kp = ed25519_dalek::SigningKey::from_bytes(&key);
let pk = ed25519_dalek::VerifyingKey::from(&kp);
let sgn = kp.sign(msg);
assert!(pk.verify_strict(msg, &sgn).is_ok(), "blomp");
let sgn_1 = OffchainSignature::sign_message(msg, &keypair);
let sgn_2 = OffchainSignature::try_from(sgn_1.as_ref())?;
assert!(
sgn_1.verify_message(msg, keypair.public()),
"cannot verify message via sig 1"
);
assert!(
sgn_2.verify_message(msg, keypair.public()),
"cannot verify message via sig 2"
);
assert_eq!(sgn_1, sgn_2, "signatures must be equal");
Ok(())
}
#[test]
fn test_signature_serialize() -> anyhow::Result<()> {
let msg = b"test000000";
let kp = ChainKeypair::from_secret(&PRIVATE_KEY)?;
let sgn = Signature::sign_message(msg, &kp);
let deserialized = Signature::try_from(sgn.as_ref())?;
assert_eq!(sgn, deserialized, "signatures don't match");
Ok(())
}
#[test]
fn test_offchain_signature() -> anyhow::Result<()> {
let msg = b"test12345";
let keypair = OffchainKeypair::from_secret(&PRIVATE_KEY)?;
let key = ed25519_dalek::SecretKey::try_from(PRIVATE_KEY)?;
let kp = ed25519_dalek::SigningKey::from_bytes(&key);
let pk = ed25519_dalek::VerifyingKey::from(&kp);
let sgn = kp.sign(msg);
assert!(pk.verify_strict(msg, &sgn).is_ok(), "blomp");
let sgn_1 = OffchainSignature::sign_message(msg, &keypair);
let sgn_2 = OffchainSignature::try_from(sgn_1.as_ref())?;
assert!(
sgn_1.verify_message(msg, keypair.public()),
"cannot verify message via sig 1"
);
assert!(
sgn_2.verify_message(msg, keypair.public()),
"cannot verify message via sig 2"
);
assert_eq!(sgn_1, sgn_2, "signatures must be equal");
Ok(())
}
#[test]
fn test_public_key_to_hex() -> anyhow::Result<()> {
let pk = PublicKey::from_privkey(&hex!(
"492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775"
))?;
assert_eq!("0x39d1bc2291826eaed86567d225cf243ebc637275e0a5aedb0d6b1dc82136a38e428804340d4c949a029846f682711d046920b4ca8b8ebeb9d1192b5bdaa54dba",
pk.to_hex(false));
assert_eq!(
"0x0239d1bc2291826eaed86567d225cf243ebc637275e0a5aedb0d6b1dc82136a38e",
pk.to_hex(true)
);
Ok(())
}
#[test]
fn test_public_key_recover() -> anyhow::Result<()> {
let address = Address::from_str("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")?;
let r = hex!("bcae4d37e3a1cd869984d1d68f9242291773cd33d26f1e754ecc1a9bfaee7d17");
let s = hex!("0b755ab5f6375595fc7fc245c45f6598cc873719183733f4c464d63eefd8579b");
let v = 1u8;
let hash = hex!("fac7acad27047640b069e8157b61623e3cb6bb86e6adf97151f93817c291f3cf");
assert_eq!(
address,
PublicKey::from_raw_signature(&hash, &r, &s, v, VerifyingKey::recover_from_prehash)?.to_address()
);
Ok(())
}
#[test]
fn test_public_key_combine_tweak() -> anyhow::Result<()> {
let (scalar1, point1) = random_group_element();
let (scalar2, point2) = random_group_element();
let pk1 = PublicKey::try_from(point1)?;
let pk2 = PublicKey::try_from(point2)?;
let sum = PublicKey::combine(&[&pk1, &pk2]);
let tweak1 = PublicKey::tweak_add(&pk1, &scalar2);
assert_eq!(sum, tweak1);
let tweak2 = PublicKey::tweak_add(&pk2, &scalar1);
assert_eq!(sum, tweak2);
Ok(())
}
#[test]
fn test_sign_and_recover() -> anyhow::Result<()> {
let msg = hex!("eff80b9f035b1d369c6a60f362ac7c8b8c3b61b76d151d1be535145ccaa3e83e");
let kp = ChainKeypair::from_secret(&PRIVATE_KEY)?;
let signature1 = Signature::sign_message(&msg, &kp);
let signature2 = Signature::sign_hash(&msg, &kp);
let pub_key1 = PublicKey::from_privkey(&PRIVATE_KEY)?;
let pub_key2 = PublicKey::from_signature(&msg, &signature1)?;
let pub_key3 = PublicKey::from_signature_hash(&msg, &signature2)?;
assert_eq!(pub_key1, kp.public().0);
assert_eq!(pub_key1, pub_key2, "recovered public key does not match");
assert_eq!(pub_key1, pub_key3, "recovered public key does not match");
assert!(
signature1.verify_message(&msg, &pub_key1),
"signature 1 verification failed with pub key 1"
);
assert!(
signature1.verify_message(&msg, &pub_key2),
"signature 1 verification failed with pub key 2"
);
assert!(
signature1.verify_message(&msg, &pub_key3),
"signature 1 verification failed with pub key 3"
);
assert!(
signature2.verify_hash(&msg, &pub_key1),
"signature 2 verification failed with pub key 1"
);
assert!(
signature2.verify_hash(&msg, &pub_key2),
"signature 2 verification failed with pub key 2"
);
assert!(
signature2.verify_hash(&msg, &pub_key3),
"signature 2 verification failed with pub key 3"
);
Ok(())
}
#[test]
fn test_public_key_serialize() -> anyhow::Result<()> {
let pk1 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
let pk2 = PublicKey::try_from(pk1.to_bytes(true).as_ref())?;
let pk3 = PublicKey::try_from(pk1.to_bytes(false).as_ref())?;
assert_eq!(pk1, pk2, "pub keys 1 2 don't match");
assert_eq!(pk2, pk3, "pub keys 2 3 don't match");
let pk1 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
let pk2 = PublicKey::try_from(PUBLIC_KEY_UNCOMPRESSED.as_ref())?;
let pk3 = PublicKey::try_from(PUBLIC_KEY_UNCOMPRESSED_PLAIN.as_ref())?;
assert_eq!(pk1, pk2, "pubkeys don't match");
assert_eq!(pk2, pk3, "pubkeys don't match");
assert_eq!(PublicKey::SIZE_COMPRESSED, pk1.to_bytes(true).len());
assert_eq!(PublicKey::SIZE_UNCOMPRESSED, pk1.to_bytes(false).len());
let shorter = hex!("f85e38b056284626a7aed0acc5d474605a408e6cccf76d7241ec7b4dedb31929b710e034f4f9a7dba97743b01e1cc35a45a60bebb29642cb0ba6a7fe8433316c");
let s1 = PublicKey::try_from(shorter.as_ref())?;
let s2 = PublicKey::try_from(s1.to_bytes(false).as_ref())?;
assert_eq!(s1, s2);
Ok(())
}
#[test]
fn test_public_key_should_not_accept_identity() -> anyhow::Result<()> {
let cp: CurvePoint = AffinePoint::IDENTITY.into();
PublicKey::try_from(cp).expect_err("must fail for identity point");
PublicKey::try_from(AffinePoint::IDENTITY).expect_err("must fail for identity point");
Ok(())
}
#[test]
fn test_public_key_curve_point() -> anyhow::Result<()> {
let cp1: CurvePoint = PublicKey::try_from(PUBLIC_KEY.as_ref())?.into();
let cp2 = CurvePoint::try_from(cp1.as_ref())?;
assert_eq!(cp1, cp2);
Ok(())
}
#[test]
fn test_public_key_from_privkey() -> anyhow::Result<()> {
let pk1 = PublicKey::from_privkey(&PRIVATE_KEY)?;
let pk2 = PublicKey::try_from(PUBLIC_KEY.as_ref())?;
assert_eq!(pk1, pk2, "failed to match deserialized pub key");
Ok(())
}
#[test]
fn test_offchain_public_key() -> anyhow::Result<()> {
let (s, pk1) = OffchainKeypair::random().unzip();
let pk2 = OffchainPublicKey::from_privkey(s.as_ref())?;
assert_eq!(pk1, pk2, "from privkey failed");
let pk3 = OffchainPublicKey::try_from(pk1.as_ref())?;
assert_eq!(pk1, pk3, "from bytes failed");
Ok(())
}
#[test]
fn test_offchain_public_key_peerid() -> anyhow::Result<()> {
let valid_peerid = PeerId::from_str("12D3KooWLYKsvDB4xEELYoHXxeStj2gzaDXjra2uGaFLpKCZkJHs")?;
let valid = OffchainPublicKey::try_from(valid_peerid)?;
assert_eq!(valid_peerid, valid.into(), "must work with ed25519 peer ids");
let invalid_peerid = PeerId::from_str("16Uiu2HAmPHGyJ7y1Rj3kJ64HxJQgM9rASaeT2bWfXF9EiX3Pbp3K")?;
let invalid = OffchainPublicKey::try_from(invalid_peerid);
assert!(invalid.is_err(), "must not work with secp256k1 peer ids");
let invalid_peerid_2 = PeerId::from_str("QmWvEwidPYBbLHfcZN6ATHdm4NPM4KbUx72LZnZRoRNKEN")?;
let invalid_2 = OffchainPublicKey::try_from(invalid_peerid_2);
assert!(invalid_2.is_err(), "must not work with rsa peer ids");
Ok(())
}
#[test]
pub fn test_response() -> anyhow::Result<()> {
let r1 = Response([0u8; Response::SIZE]);
let r2 = Response::try_from(r1.as_ref())?;
assert_eq!(r1, r2, "deserialized response does not match");
Ok(())
}
#[test]
fn test_curve_point() -> anyhow::Result<()> {
let scalar = NonZeroScalar::from_uint(U256::from_u8(100)).expect("should hold a value");
let test_point = (<Secp256k1 as CurveArithmetic>::ProjectivePoint::GENERATOR * scalar.as_ref()).to_affine();
let cp1 = CurvePoint::from_str(hex::encode(test_point.to_encoded_point(false).to_bytes()).as_str())?;
let cp2 = CurvePoint::try_from(cp1.as_ref())?;
assert_eq!(cp1, cp2, "failed to match deserialized curve point");
let pk = PublicKey::from_privkey(&scalar.to_bytes())?;
assert_eq!(
cp1.to_address(),
pk.to_address(),
"failed to match curve point address with pub key address"
);
let ch1 = Challenge(cp1);
let ch2 = Challenge(cp2);
assert_eq!(ch1.to_ethereum_challenge(), ch2.to_ethereum_challenge());
assert_eq!(ch1, ch2, "failed to match ethereum challenges from curve points");
let scalar2 = NonZeroScalar::from_uint(U256::from_u8(123)).expect("should hold a value");
let test_point2 = (<Secp256k1 as CurveArithmetic>::ProjectivePoint::GENERATOR * scalar2.as_ref()).to_affine();
let uncompressed = test_point2.to_encoded_point(false);
assert!(!uncompressed.is_compressed(), "given point is compressed");
let compressed = uncompressed.compress();
assert!(compressed.is_compressed(), "failed to compress points");
let cp3 = CurvePoint::try_from(uncompressed.as_bytes())?;
let cp4 = CurvePoint::try_from(compressed.as_bytes())?;
assert_eq!(
cp3, cp4,
"failed to match curve point from compressed and uncompressed source"
);
Ok(())
}
#[test]
fn test_half_key() -> anyhow::Result<()> {
let hk1 = HalfKey {
hkey: [0u8; HalfKey::SIZE],
};
let hk2 = HalfKey::try_from(hk1.as_ref())?;
assert_eq!(hk1, hk2, "failed to match deserialized half-key");
Ok(())
}
#[test]
fn test_half_key_challenge() -> anyhow::Result<()> {
let hkc1 = HalfKeyChallenge::try_from(PUBLIC_KEY.as_ref())?;
let hkc2 = HalfKeyChallenge::try_from(hkc1.as_ref())?;
assert_eq!(hkc1, hkc2, "failed to match deserialized half key challenge");
Ok(())
}
#[test]
fn test_hash() -> anyhow::Result<()> {
let hash1 = Hash::create(&[b"msg"]);
assert_eq!(
"0x92aef1b955b9de564fc50e31a55b470b0c8cdb931f186485d620729fb03d6f2c",
hash1.to_hex(),
"hash test vector failed to match"
);
let hash2 = Hash::try_from(hash1.as_ref())?;
assert_eq!(hash1, hash2, "failed to match deserialized hash");
assert_eq!(
hash1.hash(),
Hash::try_from(hex!("1c4d8d521eccee7225073ea180e0fa075a6443afb7ca06076a9566b07d29470f").as_ref())?
);
Ok(())
}
}