diff --git a/cfd_protocol/src/lib.rs b/cfd_protocol/src/lib.rs index d908c38..bfac8db 100644 --- a/cfd_protocol/src/lib.rs +++ b/cfd_protocol/src/lib.rs @@ -3,7 +3,7 @@ pub use secp256k1_zkp; use anyhow::{bail, Context, Result}; use bdk::bitcoin::hashes::hex::ToHex; -use bdk::bitcoin::hashes::*; +use bdk::bitcoin::hashes::Hash; use bdk::bitcoin::util::bip143::SigHashCache; use bdk::bitcoin::util::psbt::{Global, PartiallySignedTransaction}; use bdk::bitcoin::{ @@ -16,12 +16,12 @@ use bdk::miniscript::DescriptorTrait; use bdk::wallet::AddressIndex; use bdk::FeeRate; use itertools::Itertools; -use secp256k1_zkp::bitcoin_hashes::sha256; use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; use std::collections::HashMap; use std::iter::FromIterator; pub mod interval; +pub mod oracle; /// In satoshi per vbyte. const SATS_PER_VBYTE: f64 = 1.0; @@ -530,20 +530,6 @@ impl Payout { } } -const BIP340_MIDSTATE: [u8; 32] = [ - 0x9c, 0xec, 0xba, 0x11, 0x23, 0x92, 0x53, 0x81, 0x11, 0x67, 0x91, 0x12, 0xd1, 0x62, 0x7e, 0x0f, - 0x97, 0xc8, 0x75, 0x50, 0x00, 0x3c, 0xc7, 0x65, 0x90, 0xf6, 0x11, 0x64, 0x33, 0xe9, 0xb6, 0x6a, -]; - -sha256t_hash_newtype!( - BIP340Hash, - BIP340HashTag, - BIP340_MIDSTATE, - 64, - doc = "bip340 hash", - true -); - /// Compute a signature point for the given oracle public key, announcement nonce public key and /// message. fn compute_signature_point( @@ -558,17 +544,7 @@ fn compute_signature_point( Ok(secp256k1_zkp::PublicKey::from_slice(&buf)?) } - let hash = { - let mut buf = Vec::::new(); - buf.extend(&nonce_pk.serialize()); - buf.extend(&oracle_pk.serialize()); - buf.extend( - secp256k1_zkp::Message::from_hashed_data::(msg) - .as_ref() - .to_vec(), - ); - BIP340Hash::hash(&buf).into_inner().to_vec() - }; + let hash = oracle::msg_hash(oracle_pk, nonce_pk, msg); let mut oracle_pk = schnorr_pubkey_to_pubkey(oracle_pk)?; oracle_pk.mul_assign(SECP256K1, &hash)?; let nonce_pk = schnorr_pubkey_to_pubkey(nonce_pk)?; diff --git a/cfd_protocol/src/oracle.rs b/cfd_protocol/src/oracle.rs new file mode 100644 index 0000000..89de01b --- /dev/null +++ b/cfd_protocol/src/oracle.rs @@ -0,0 +1,78 @@ +pub use secp256k1_zkp::*; + +use bdk::bitcoin::hashes::Hash; +use bip340_hash::Bip340Hash; +use rand::{CryptoRng, RngCore}; +use secp256k1_zkp::bitcoin_hashes::sha256; +use secp256k1_zkp::{schnorrsig, SecretKey}; + +mod bip340_hash; +mod secp_utils; + +/// Sign `msg` with the oracle's `key_pair` and a pre-computed `nonce` +/// whose corresponding public key was included in a previous +/// announcement. +pub fn attest( + key_pair: &schnorrsig::KeyPair, + nonce: &SecretKey, + msg: &[u8], +) -> schnorrsig::Signature { + let msg = secp256k1_zkp::Message::from_hashed_data::(msg); + secp_utils::schnorr_sign_with_nonce(&msg, key_pair, nonce) +} + +pub fn nonce(rng: &mut R) -> (SecretKey, schnorrsig::PublicKey) +where + R: RngCore + CryptoRng, +{ + let nonce = SecretKey::new(rng); + + let key_pair = schnorrsig::KeyPair::from_secret_key(SECP256K1, nonce); + let nonce_pk = schnorrsig::PublicKey::from_keypair(SECP256K1, &key_pair); + + (nonce, nonce_pk) +} + +pub fn msg_hash( + pk: &schnorrsig::PublicKey, + nonce_pk: &schnorrsig::PublicKey, + msg: &[u8], +) -> Vec { + let mut buf = Vec::::new(); + buf.extend(&nonce_pk.serialize()); + buf.extend(&pk.serialize()); + buf.extend( + secp256k1_zkp::Message::from_hashed_data::(msg) + .as_ref() + .to_vec(), + ); + + Bip340Hash::hash(&buf).into_inner().to_vec() +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::thread_rng; + + fn verify(sig: &schnorrsig::Signature, msg: &[u8], pk: &schnorrsig::PublicKey) -> bool { + let msg = secp256k1_zkp::Message::from_hashed_data::(msg); + SECP256K1.schnorrsig_verify(sig, &msg, pk).is_ok() + } + + #[test] + fn attest_and_verify() { + let mut rng = thread_rng(); + + let key_pair = schnorrsig::KeyPair::new(SECP256K1, &mut rng); + let pk = schnorrsig::PublicKey::from_keypair(SECP256K1, &key_pair); + + let (nonce, _nonce_pk) = nonce(&mut rng); + + let msg = b"hello world"; + + let sig = attest(&key_pair, &nonce, msg); + + assert!(verify(&sig, msg, &pk)); + } +} diff --git a/cfd_protocol/src/oracle/bip340_hash.rs b/cfd_protocol/src/oracle/bip340_hash.rs new file mode 100644 index 0000000..1c6f172 --- /dev/null +++ b/cfd_protocol/src/oracle/bip340_hash.rs @@ -0,0 +1,15 @@ +use bdk::bitcoin::hashes::*; + +sha256t_hash_newtype!( + Bip340Hash, + Bip340HashTag, + BIP340_MIDSTATE, + 64, + doc = "bip340 hash", + true +); + +const BIP340_MIDSTATE: [u8; 32] = [ + 0x9c, 0xec, 0xba, 0x11, 0x23, 0x92, 0x53, 0x81, 0x11, 0x67, 0x91, 0x12, 0xd1, 0x62, 0x7e, 0x0f, + 0x97, 0xc8, 0x75, 0x50, 0x00, 0x3c, 0xc7, 0x65, 0x90, 0xf6, 0x11, 0x64, 0x33, 0xe9, 0xb6, 0x6a, +]; diff --git a/cfd_protocol/src/oracle/secp_utils.rs b/cfd_protocol/src/oracle/secp_utils.rs new file mode 100644 index 0000000..9318937 --- /dev/null +++ b/cfd_protocol/src/oracle/secp_utils.rs @@ -0,0 +1,43 @@ +use secp256k1_zkp::secp256k1_zkp_sys::types::c_void; +use secp256k1_zkp::secp256k1_zkp_sys::CPtr; +use secp256k1_zkp::{schnorrsig, SecretKey, SECP256K1}; +use std::os::raw::{c_int, c_uchar}; +use std::ptr; + +/// Create a Schnorr signature using the provided nonce instead of generating one. +pub fn schnorr_sign_with_nonce( + msg: &secp256k1_zkp::Message, + keypair: &schnorrsig::KeyPair, + nonce: &SecretKey, +) -> schnorrsig::Signature { + unsafe { + let mut sig = [0u8; secp256k1_zkp::constants::SCHNORRSIG_SIGNATURE_SIZE]; + assert_eq!( + 1, + secp256k1_zkp::ffi::secp256k1_schnorrsig_sign( + *SECP256K1.ctx(), + sig.as_mut_c_ptr(), + msg.as_c_ptr(), + keypair.as_ptr(), + Some(constant_nonce_fn), + nonce.as_c_ptr() as *const c_void + ) + ); + + schnorrsig::Signature::from_slice(&sig).unwrap() + } +} + +extern "C" fn constant_nonce_fn( + nonce32: *mut c_uchar, + _msg32: *const c_uchar, + _key32: *const c_uchar, + _xonly_pk32: *const c_uchar, + _algo16: *const c_uchar, + data: *mut c_void, +) -> c_int { + unsafe { + ptr::copy_nonoverlapping(data as *const c_uchar, nonce32, 32); + } + 1 +} diff --git a/cfd_protocol/tests/cfds.rs b/cfd_protocol/tests/cfds.rs index 33aa373..30a8397 100644 --- a/cfd_protocol/tests/cfds.rs +++ b/cfd_protocol/tests/cfds.rs @@ -10,12 +10,11 @@ use bitcoin::Txid; use cfd_protocol::interval::Interval; use cfd_protocol::{ commit_descriptor, compute_adaptor_point, create_cfd_transactions, finalize_spend_transaction, - lock_descriptor, punish_transaction, renew_cfd_transactions, spending_tx_sighash, + lock_descriptor, oracle, punish_transaction, renew_cfd_transactions, spending_tx_sighash, CfdTransactions, Payout, PunishParams, TransactionExt, WalletExt, }; use rand::{CryptoRng, RngCore, SeedableRng}; use rand_chacha::ChaChaRng; -use secp256k1_zkp::bitcoin_hashes::sha256; use secp256k1_zkp::{schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1}; #[test] @@ -844,10 +843,7 @@ impl Oracle { fn attest(&self, event: &Event, msgs: &[&[u8]]) -> Vec { msgs.iter() .zip(&event.nonces) - .map(|(msg, nonce)| { - let msg = secp256k1_zkp::Message::from_hashed_data::(msg); - secp_utils::schnorr_sign_with_nonce(&msg, &self.key_pair, nonce) - }) + .map(|(msg, nonce)| oracle::attest(&self.key_pair, nonce, msg)) .collect() } } @@ -877,16 +873,7 @@ impl Event { where R: RngCore + CryptoRng, { - let (nonces, nonce_pks) = (0..20) - .map(|_| { - let nonce = SecretKey::new(rng); - - let key_pair = schnorrsig::KeyPair::from_secret_key(SECP256K1, nonce); - let nonce_pk = schnorrsig::PublicKey::from_keypair(SECP256K1, &key_pair); - - (nonce, nonce_pk) - }) - .unzip(); + let (nonces, nonce_pks) = (0..20).map(|_| oracle::nonce(rng)).unzip(); Self { nonces, nonce_pks } } @@ -935,50 +922,3 @@ fn schnorrsig_decompose(signature: &schnorrsig::Signature) -> (schnorrsig::Publi (nonce_pk, s) } - -mod secp_utils { - use super::*; - - use secp256k1_zkp::secp256k1_zkp_sys::types::c_void; - use secp256k1_zkp::secp256k1_zkp_sys::CPtr; - use std::os::raw::{c_int, c_uchar}; - use std::ptr; - - /// Create a Schnorr signature using the provided nonce instead of generating one. - pub fn schnorr_sign_with_nonce( - msg: &secp256k1_zkp::Message, - keypair: &schnorrsig::KeyPair, - nonce: &SecretKey, - ) -> schnorrsig::Signature { - unsafe { - let mut sig = [0u8; secp256k1_zkp::constants::SCHNORRSIG_SIGNATURE_SIZE]; - assert_eq!( - 1, - secp256k1_zkp::ffi::secp256k1_schnorrsig_sign( - *SECP256K1.ctx(), - sig.as_mut_c_ptr(), - msg.as_c_ptr(), - keypair.as_ptr(), - Some(constant_nonce_fn), - nonce.as_c_ptr() as *const c_void - ) - ); - - schnorrsig::Signature::from_slice(&sig).unwrap() - } - } - - extern "C" fn constant_nonce_fn( - nonce32: *mut c_uchar, - _msg32: *const c_uchar, - _key32: *const c_uchar, - _xonly_pk32: *const c_uchar, - _algo16: *const c_uchar, - data: *mut c_void, - ) -> c_int { - unsafe { - ptr::copy_nonoverlapping(data as *const c_uchar, nonce32, 32); - } - 1 - } -}