From 9b47ad56bbd431868b60bd6dcdc846996a0cd556 Mon Sep 17 00:00:00 2001
From: Luke Parker <lukeparker5132@gmail.com>
Date: Fri, 17 Mar 2023 20:47:42 -0400
Subject: [PATCH] Create a dedicated Algorithm for Bitcoin Schnorr

---
 Cargo.lock                     |   1 +
 coins/bitcoin/Cargo.toml       |   1 +
 coins/bitcoin/src/algorithm.rs | 131 +++++++++++++++++++++++++++++++++
 coins/bitcoin/src/crypto.rs    |  42 ++---------
 coins/bitcoin/src/lib.rs       |   2 +
 coins/bitcoin/src/tests/mod.rs |  22 +++---
 coins/bitcoin/src/wallet.rs    |  21 ++----
 7 files changed, 155 insertions(+), 65 deletions(-)
 create mode 100644 coins/bitcoin/src/algorithm.rs

diff --git a/Cargo.lock b/Cargo.lock
index 2fc5f8ec..b3e8a457 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -600,6 +600,7 @@ dependencies = [
  "serde_json",
  "sha2 0.10.6",
  "thiserror",
+ "zeroize",
 ]
 
 [[package]]
diff --git a/coins/bitcoin/Cargo.toml b/coins/bitcoin/Cargo.toml
index bc3f4f22..6c4d318e 100644
--- a/coins/bitcoin/Cargo.toml
+++ b/coins/bitcoin/Cargo.toml
@@ -11,6 +11,7 @@ edition = "2021"
 lazy_static = "1"
 thiserror = "1"
 
+zeroize = "^1.5"
 rand_core = "0.6"
 
 sha2 = "0.10"
diff --git a/coins/bitcoin/src/algorithm.rs b/coins/bitcoin/src/algorithm.rs
new file mode 100644
index 00000000..c5a668d4
--- /dev/null
+++ b/coins/bitcoin/src/algorithm.rs
@@ -0,0 +1,131 @@
+use core::fmt::Debug;
+use std::io;
+
+use lazy_static::lazy_static;
+
+use zeroize::Zeroizing;
+use rand_core::{RngCore, CryptoRng};
+
+use sha2::{Digest, Sha256};
+use transcript::Transcript;
+
+use secp256k1::schnorr::Signature;
+use k256::{elliptic_curve::ops::Reduce, U256, Scalar, ProjectivePoint};
+use frost::{
+  curve::{Ciphersuite, Secp256k1},
+  Participant, ThresholdKeys, ThresholdView, FrostError,
+  algorithm::{Hram as HramTrait, Algorithm, Schnorr as FrostSchnorr},
+};
+
+use crate::crypto::{x, make_even};
+
+/// 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.
+#[derive(Clone, Copy, Debug)]
+pub struct Hram {}
+
+lazy_static! {
+  static ref TAG_HASH: [u8; 32] = Sha256::digest(b"BIP0340/challenge").into();
+}
+
+#[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);
+
+    let mut data = Sha256::new();
+    data.update(*TAG_HASH);
+    data.update(*TAG_HASH);
+    data.update(x(&R));
+    data.update(x(A));
+    data.update(m);
+
+    Scalar::from_uint_reduced(U256::from_be_slice(&data.finalize()))
+  }
+}
+
+/// 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<T: Sync + Clone + Debug + Transcript>(FrostSchnorr<Secp256k1, T, Hram>);
+impl<T: Sync + Clone + Debug + Transcript> Schnorr<T> {
+  /// Construct a Schnorr algorithm continuing the specified transcript.
+  pub fn new(transcript: T) -> Schnorr<T> {
+    Schnorr(FrostSchnorr::new(transcript))
+  }
+}
+
+impl<T: Sync + Clone + Debug + Transcript> Algorithm<Secp256k1> for Schnorr<T> {
+  type Transcript = T;
+  type Addendum = ();
+  type Signature = Signature;
+
+  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 secp256k1 signature
+      Signature::from_slice(&sig.serialize()[1 ..]).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)
+  }
+}
diff --git a/coins/bitcoin/src/crypto.rs b/coins/bitcoin/src/crypto.rs
index 73e2077d..9dadaa4d 100644
--- a/coins/bitcoin/src/crypto.rs
+++ b/coins/bitcoin/src/crypto.rs
@@ -1,23 +1,14 @@
-use lazy_static::lazy_static;
-
-use sha2::{Digest, Sha256};
-
 use k256::{
-  elliptic_curve::{
-    ops::Reduce,
-    sec1::{Tag, ToEncodedPoint},
-  },
-  U256, Scalar, ProjectivePoint,
+  elliptic_curve::sec1::{Tag, ToEncodedPoint},
+  ProjectivePoint,
 };
 
 use bitcoin::XOnlyPublicKey;
 
-use frost::{algorithm::Hram, curve::Secp256k1};
-
 /// 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);
+  assert_eq!(encoded.tag(), Tag::CompressedEvenY, "x coordinate of odd key");
   (*encoded.x().expect("point at infinity")).into()
 }
 
@@ -26,7 +17,8 @@ pub fn x_only(key: &ProjectivePoint) -> XOnlyPublicKey {
   XOnlyPublicKey::from_slice(&x(key)).unwrap()
 }
 
-/// Make a point even, returning the even version and the offset required for it to be even.
+/// Make a point even by adding the generator until it is even. Returns the even point and the
+/// amount of additions required.
 pub fn make_even(mut key: ProjectivePoint) -> (ProjectivePoint, u64) {
   let mut c = 0;
   while key.to_encoded_point(true).tag() == Tag::CompressedOddY {
@@ -35,27 +27,3 @@ pub fn make_even(mut key: ProjectivePoint) -> (ProjectivePoint, u64) {
   }
   (key, c)
 }
-
-/// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm.
-#[derive(Clone, Copy, Debug)]
-pub struct BitcoinHram {}
-
-lazy_static! {
-  static ref TAG_HASH: [u8; 32] = Sha256::digest(b"BIP0340/challenge").into();
-}
-
-#[allow(non_snake_case)]
-impl Hram<Secp256k1> for BitcoinHram {
-  fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
-    let (R, _) = make_even(*R);
-
-    let mut data = Sha256::new();
-    data.update(*TAG_HASH);
-    data.update(*TAG_HASH);
-    data.update(x(&R));
-    data.update(x(A));
-    data.update(m);
-
-    Scalar::from_uint_reduced(U256::from_be_slice(&data.finalize()))
-  }
-}
diff --git a/coins/bitcoin/src/lib.rs b/coins/bitcoin/src/lib.rs
index cdd28b9c..2de4a062 100644
--- a/coins/bitcoin/src/lib.rs
+++ b/coins/bitcoin/src/lib.rs
@@ -1,5 +1,7 @@
 /// Cryptographic helpers.
 pub mod crypto;
+/// BIP-340 Schnorr signature algorithm.
+pub mod algorithm;
 /// Wallet functionality to create transactions.
 pub mod wallet;
 /// A minimal async RPC.
diff --git a/coins/bitcoin/src/tests/mod.rs b/coins/bitcoin/src/tests/mod.rs
index 7d003c6c..2b0afce4 100644
--- a/coins/bitcoin/src/tests/mod.rs
+++ b/coins/bitcoin/src/tests/mod.rs
@@ -2,21 +2,21 @@ use rand_core::OsRng;
 
 use sha2::{Digest, Sha256};
 
-use secp256k1::{SECP256K1, Message, schnorr::Signature};
+use secp256k1::{SECP256K1, Message};
 use bitcoin::hashes::{Hash as HashTrait, sha256::Hash};
 
 use k256::Scalar;
+use transcript::{Transcript, RecommendedTranscript};
 use frost::{
   curve::Secp256k1,
   Participant,
-  algorithm::IetfSchnorr,
   tests::{algorithm_machines, key_gen, sign},
 };
 
-use crate::crypto::{BitcoinHram, x_only, make_even};
+use crate::{crypto::{x_only, make_even}, algorithm::Schnorr};
 
 #[test]
-fn test_signing() {
+fn test_algorithm() {
   let mut keys = key_gen::<_, Secp256k1>(&mut OsRng);
   const MESSAGE: &[u8] = b"Hello, World!";
 
@@ -25,22 +25,18 @@ fn test_signing() {
     *keys = keys.offset(Scalar::from(offset));
   }
 
-  let algo = IetfSchnorr::<Secp256k1, BitcoinHram>::ietf();
-  let mut sig = sign(
+  let algo = Schnorr::<RecommendedTranscript>::new(RecommendedTranscript::new(b"bitcoin-serai sign test"));
+  let sig = sign(
     &mut OsRng,
-    algo,
+    algo.clone(),
     keys.clone(),
-    algorithm_machines(&mut OsRng, IetfSchnorr::ietf(), &keys),
+    algorithm_machines(&mut OsRng, algo, &keys),
     &Sha256::digest(MESSAGE),
   );
 
-  let offset;
-  (sig.R, offset) = make_even(sig.R);
-  sig.s += Scalar::from(offset);
-
   SECP256K1
     .verify_schnorr(
-      &Signature::from_slice(&sig.serialize()[1 .. 65]).unwrap(),
+      &sig,
       &Message::from(Hash::hash(MESSAGE)),
       &x_only(&keys[&Participant::new(1).unwrap()].group_key()),
     )
diff --git a/coins/bitcoin/src/wallet.rs b/coins/bitcoin/src/wallet.rs
index f7de1481..d1a16648 100644
--- a/coins/bitcoin/src/wallet.rs
+++ b/coins/bitcoin/src/wallet.rs
@@ -11,7 +11,6 @@ use k256::{elliptic_curve::sec1::ToEncodedPoint, Scalar};
 use frost::{
   curve::{Ciphersuite, Secp256k1},
   Participant, ThresholdKeys, FrostError,
-  algorithm::Schnorr,
   sign::*,
 };
 
@@ -22,7 +21,7 @@ use bitcoin::{
   OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Address,
 };
 
-use crate::crypto::{BitcoinHram, make_even};
+use crate::algorithm::Schnorr;
 
 /// A spendable output.
 #[derive(Clone, PartialEq, Eq, Debug)]
@@ -200,7 +199,7 @@ impl SignableTransaction {
 /// A FROST signing machine to produce a Bitcoin transaction.
 pub struct TransactionMachine {
   tx: SignableTransaction,
-  sigs: Vec<AlgorithmMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, BitcoinHram>>>,
+  sigs: Vec<AlgorithmMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
 }
 
 impl PreprocessMachine for TransactionMachine {
@@ -229,8 +228,7 @@ impl PreprocessMachine for TransactionMachine {
 
 pub struct TransactionSignMachine {
   tx: SignableTransaction,
-  sigs:
-    Vec<AlgorithmSignMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, BitcoinHram>>>,
+  sigs: Vec<AlgorithmSignMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
 }
 
 impl SignMachine<Transaction> for TransactionSignMachine {
@@ -307,9 +305,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
 
 pub struct TransactionSignatureMachine {
   tx: Transaction,
-  sigs: Vec<
-    AlgorithmSignatureMachine<Secp256k1, Schnorr<Secp256k1, RecommendedTranscript, BitcoinHram>>,
-  >,
+  sigs: Vec<AlgorithmSignatureMachine<Secp256k1, Schnorr<RecommendedTranscript>>>,
 }
 
 impl SignatureMachine<Transaction> for TransactionSignatureMachine {
@@ -324,17 +320,12 @@ impl SignatureMachine<Transaction> for TransactionSignatureMachine {
     mut shares: HashMap<Participant, Self::SignatureShare>,
   ) -> Result<Transaction, FrostError> {
     for (input, schnorr) in self.tx.input.iter_mut().zip(self.sigs.drain(..)) {
-      let mut sig = schnorr.complete(
+      let sig = schnorr.complete(
         shares.iter_mut().map(|(l, shares)| (*l, shares.remove(0))).collect::<HashMap<_, _>>(),
       )?;
 
-      // TODO: Implement BitcoinSchnorr Algorithm to handle this
-      let offset;
-      (sig.R, offset) = make_even(sig.R);
-      sig.s += Scalar::from(offset);
-
       let mut witness: Witness = Witness::new();
-      witness.push(&sig.serialize()[1 .. 65]);
+      witness.push(sig.as_ref());
       input.witness = witness;
     }