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
use k256::{
  elliptic_curve::sec1::{Tag, ToEncodedPoint},
  ProjectivePoint,
};

use bitcoin::key::XOnlyPublicKey;

/// Get the x coordinate of a non-infinity, even point. Panics on invalid input.
pub fn x(key: &ProjectivePoint) -> [u8; 32] {
  let encoded = key.to_encoded_point(true);
  assert_eq!(encoded.tag(), Tag::CompressedEvenY, "x coordinate of odd key");
  (*encoded.x().expect("point at infinity")).into()
}

/// Convert a non-infinity even point to a XOnlyPublicKey. Panics on invalid input.
pub fn x_only(key: &ProjectivePoint) -> XOnlyPublicKey {
  XOnlyPublicKey::from_slice(&x(key)).expect("x_only was passed a point which was infinity or odd")
}

/// Make a point even by adding the generator until it is even.
///
/// Returns the even point and the amount of additions required.
#[cfg(any(feature = "std", feature = "hazmat"))]
pub fn make_even(mut key: ProjectivePoint) -> (ProjectivePoint, u64) {
  let mut c = 0;
  while key.to_encoded_point(true).tag() == Tag::CompressedOddY {
    key += ProjectivePoint::GENERATOR;
    c += 1;
  }
  (key, c)
}

#[cfg(feature = "std")]
mod frost_crypto {
  use core::fmt::Debug;
  use std_shims::{vec::Vec, io};

  use zeroize::Zeroizing;
  use rand_core::{RngCore, CryptoRng};

  use bitcoin::hashes::{HashEngine, Hash, sha256::Hash as Sha256};

  use k256::{elliptic_curve::ops::Reduce, U256, Scalar};

  use frost::{
    curve::{Ciphersuite, Secp256k1},
    Participant, ThresholdKeys, ThresholdView, FrostError,
    algorithm::{Hram as HramTrait, Algorithm, IetfSchnorr as FrostSchnorr},
  };

  use super::*;

  /// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm.
  ///
  /// If passed an odd nonce, it will have the generator added until it is even.
  ///
  /// If the key is odd, this will panic.
  #[derive(Clone, Copy, Debug)]
  pub struct Hram;
  #[allow(non_snake_case)]
  impl HramTrait<Secp256k1> for Hram {
    fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
      // Convert the nonce to be even
      let (R, _) = make_even(*R);

      const TAG_HASH: Sha256 = Sha256::const_hash(b"BIP0340/challenge");

      let mut data = Sha256::engine();
      data.input(TAG_HASH.as_ref());
      data.input(TAG_HASH.as_ref());
      data.input(&x(&R));
      data.input(&x(A));
      data.input(m);

      Scalar::reduce(U256::from_be_slice(Sha256::from_engine(data).as_ref()))
    }
  }

  /// BIP-340 Schnorr signature algorithm.
  ///
  /// This must be used with a ThresholdKeys whose group key is even. If it is odd, this will panic.
  #[derive(Clone)]
  pub struct Schnorr(FrostSchnorr<Secp256k1, Hram>);
  impl Schnorr {
    /// Construct a Schnorr algorithm continuing the specified transcript.
    #[allow(clippy::new_without_default)]
    pub fn new() -> Schnorr {
      Schnorr(FrostSchnorr::ietf())
    }
  }

  impl Algorithm<Secp256k1> for Schnorr {
    type Transcript = <FrostSchnorr<Secp256k1, Hram> as Algorithm<Secp256k1>>::Transcript;
    type Addendum = ();
    type Signature = [u8; 64];

    fn transcript(&mut self) -> &mut Self::Transcript {
      self.0.transcript()
    }

    fn nonces(&self) -> Vec<Vec<ProjectivePoint>> {
      self.0.nonces()
    }

    fn preprocess_addendum<R: RngCore + CryptoRng>(
      &mut self,
      rng: &mut R,
      keys: &ThresholdKeys<Secp256k1>,
    ) {
      self.0.preprocess_addendum(rng, keys)
    }

    fn read_addendum<R: io::Read>(&self, reader: &mut R) -> io::Result<Self::Addendum> {
      self.0.read_addendum(reader)
    }

    fn process_addendum(
      &mut self,
      view: &ThresholdView<Secp256k1>,
      i: Participant,
      addendum: (),
    ) -> Result<(), FrostError> {
      self.0.process_addendum(view, i, addendum)
    }

    fn sign_share(
      &mut self,
      params: &ThresholdView<Secp256k1>,
      nonce_sums: &[Vec<<Secp256k1 as Ciphersuite>::G>],
      nonces: Vec<Zeroizing<<Secp256k1 as Ciphersuite>::F>>,
      msg: &[u8],
    ) -> <Secp256k1 as Ciphersuite>::F {
      self.0.sign_share(params, nonce_sums, nonces, msg)
    }

    #[must_use]
    fn verify(
      &self,
      group_key: ProjectivePoint,
      nonces: &[Vec<ProjectivePoint>],
      sum: Scalar,
    ) -> Option<Self::Signature> {
      self.0.verify(group_key, nonces, sum).map(|mut sig| {
        // Make the R of the final signature even
        let offset;
        (sig.R, offset) = make_even(sig.R);
        // s = r + cx. Since we added to the r, add to s
        sig.s += Scalar::from(offset);
        // Convert to a Bitcoin signature by dropping the byte for the point's sign bit
        sig.serialize()[1 ..].try_into().unwrap()
      })
    }

    fn verify_share(
      &self,
      verification_share: ProjectivePoint,
      nonces: &[Vec<ProjectivePoint>],
      share: Scalar,
    ) -> Result<Vec<(Scalar, ProjectivePoint)>, ()> {
      self.0.verify_share(verification_share, nonces, share)
    }
  }
}
#[cfg(feature = "std")]
pub use frost_crypto::*;