1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use group::ff::PrimeField;
use k256::{
  elliptic_curve::{ops::Reduce, point::AffineCoordinates, sec1::ToEncodedPoint},
  ProjectivePoint, Scalar, U256 as KU256,
};
#[cfg(test)]
use k256::{elliptic_curve::point::DecompressPoint, AffinePoint};

use frost::{
  algorithm::{Hram, SchnorrSignature},
  curve::{Ciphersuite, Secp256k1},
};

use alloy_core::primitives::{Parity, Signature as AlloySignature};
use alloy_consensus::{SignableTransaction, Signed, TxLegacy};

use crate::abi::router::{Signature as AbiSignature};

pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] {
  alloy_core::primitives::keccak256(data).into()
}

pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar {
  <Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(data).into())
}

pub fn address(point: &ProjectivePoint) -> [u8; 20] {
  let encoded_point = point.to_encoded_point(false);
  // Last 20 bytes of the hash of the concatenated x and y coordinates
  // We obtain the concatenated x and y coordinates via the uncompressed encoding of the point
  keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap()
}

/// Deterministically sign a transaction.
///
/// This function panics if passed a transaction with a non-None chain ID.
pub fn deterministically_sign(tx: &TxLegacy) -> Signed<TxLegacy> {
  assert!(
    tx.chain_id.is_none(),
    "chain ID was Some when deterministically signing a TX (causing a non-deterministic signer)"
  );

  let sig_hash = tx.signature_hash().0;
  let mut r = hash_to_scalar(&[sig_hash.as_slice(), b"r"].concat());
  let mut s = hash_to_scalar(&[sig_hash.as_slice(), b"s"].concat());
  loop {
    let r_bytes: [u8; 32] = r.to_repr().into();
    let s_bytes: [u8; 32] = s.to_repr().into();
    let v = Parity::NonEip155(false);
    let signature =
      AlloySignature::from_scalars_and_parity(r_bytes.into(), s_bytes.into(), v).unwrap();
    let tx = tx.clone().into_signed(signature);
    if tx.recover_signer().is_ok() {
      return tx;
    }

    // Re-hash until valid
    r = hash_to_scalar(r_bytes.as_ref());
    s = hash_to_scalar(s_bytes.as_ref());
  }
}

/// The public key for a Schnorr-signing account.
#[allow(non_snake_case)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct PublicKey {
  pub(crate) A: ProjectivePoint,
  pub(crate) px: Scalar,
}

impl PublicKey {
  /// Construct a new `PublicKey`.
  ///
  /// This will return None if the provided point isn't eligible to be a public key (due to
  /// bounds such as parity).
  #[allow(non_snake_case)]
  pub fn new(A: ProjectivePoint) -> Option<PublicKey> {
    let affine = A.to_affine();
    // Only allow even keys to save a word within Ethereum
    let is_odd = bool::from(affine.y_is_odd());
    if is_odd {
      None?;
    }

    let x_coord = affine.x();
    let x_coord_scalar = <Scalar as Reduce<KU256>>::reduce_bytes(&x_coord);
    // Return None if a reduction would occur
    // Reductions would be incredibly unlikely and shouldn't be an issue, yet it's one less
    // headache/concern to have
    // This does ban a trivial amoount of public keys
    if x_coord_scalar.to_repr() != x_coord {
      None?;
    }

    Some(PublicKey { A, px: x_coord_scalar })
  }

  pub fn point(&self) -> ProjectivePoint {
    self.A
  }

  pub(crate) fn eth_repr(&self) -> [u8; 32] {
    self.px.to_repr().into()
  }

  #[cfg(test)]
  pub(crate) fn from_eth_repr(repr: [u8; 32]) -> Option<Self> {
    #[allow(non_snake_case)]
    let A = Option::<AffinePoint>::from(AffinePoint::decompress(&repr.into(), 0.into()))?.into();
    Option::from(Scalar::from_repr(repr.into())).map(|px| PublicKey { A, px })
  }
}

/// The HRAm to use for the Schnorr contract.
#[derive(Clone, Default)]
pub struct EthereumHram {}
impl Hram<Secp256k1> for EthereumHram {
  #[allow(non_snake_case)]
  fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
    let x_coord = A.to_affine().x();

    let mut data = address(R).to_vec();
    data.extend(x_coord.as_slice());
    data.extend(m);

    <Scalar as Reduce<KU256>>::reduce_bytes(&keccak256(&data).into())
  }
}

/// A signature for the Schnorr contract.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Signature {
  pub(crate) c: Scalar,
  pub(crate) s: Scalar,
}
impl Signature {
  pub fn verify(&self, public_key: &PublicKey, message: &[u8]) -> bool {
    #[allow(non_snake_case)]
    let R = (Secp256k1::generator() * self.s) - (public_key.A * self.c);
    EthereumHram::hram(&R, &public_key.A, message) == self.c
  }

  /// Construct a new `Signature`.
  ///
  /// This will return None if the signature is invalid.
  pub fn new(
    public_key: &PublicKey,
    message: &[u8],
    signature: SchnorrSignature<Secp256k1>,
  ) -> Option<Signature> {
    let c = EthereumHram::hram(&signature.R, &public_key.A, message);
    if !signature.verify(public_key.A, c) {
      None?;
    }

    let res = Signature { c, s: signature.s };
    assert!(res.verify(public_key, message));
    Some(res)
  }

  pub fn c(&self) -> Scalar {
    self.c
  }
  pub fn s(&self) -> Scalar {
    self.s
  }

  pub fn to_bytes(&self) -> [u8; 64] {
    let mut res = [0; 64];
    res[.. 32].copy_from_slice(self.c.to_repr().as_ref());
    res[32 ..].copy_from_slice(self.s.to_repr().as_ref());
    res
  }

  pub fn from_bytes(bytes: [u8; 64]) -> std::io::Result<Self> {
    let mut reader = bytes.as_slice();
    let c = Secp256k1::read_F(&mut reader)?;
    let s = Secp256k1::read_F(&mut reader)?;
    Ok(Signature { c, s })
  }
}
impl From<&Signature> for AbiSignature {
  fn from(sig: &Signature) -> AbiSignature {
    let c: [u8; 32] = sig.c.to_repr().into();
    let s: [u8; 32] = sig.s.to_repr().into();
    AbiSignature { c: c.into(), s: s.into() }
  }
}