Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/btc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ mod test {
script::{Builder, PushBytesBuf},
},
opcodes::all::*,
taproot::{LeafVersion, TaprootBuilder},
secp256k1::schnorr,
taproot::{self, LeafVersion, TaprootBuilder},
};
use rand_core::OsRng;

Expand Down Expand Up @@ -332,7 +333,7 @@ mod test {
let nums_x_data =
hex::decode("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
.unwrap();
let nums_public_key = bitcoin::XOnlyPublicKey::from_slice(&nums_x_data).unwrap();
let nums_public_key = XOnlyPublicKey::from_slice(&nums_x_data).unwrap();

let spend_info = TaprootBuilder::new()
.add_leaf(1, accept_script.clone())
Expand All @@ -357,7 +358,7 @@ mod test {
.expect("Failed to create message");

let schnorr_sig = secp.sign_schnorr(&message, &depositor_keypair);
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand Down Expand Up @@ -390,9 +391,9 @@ mod test {
Ok(proof) => proof,
};
let proof_bytes = proof.to_bytes();
let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof_bytes)
let schnorr_sig = schnorr::Signature::from_slice(&proof_bytes)
.expect("Failed to parse Signature from slice");
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand Down Expand Up @@ -448,7 +449,7 @@ mod test {

// [1] Verify the correct signature, which should succeed.
let schnorr_sig = secp.sign_schnorr(&message, &tweaked.to_keypair());
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand All @@ -458,7 +459,7 @@ mod test {

// [2] Verify the correct signature, but with a different sighash type,
// which should fail.
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::None,
};
Expand All @@ -470,7 +471,7 @@ mod test {
// which should fail. In this case we've created the signature using
// the untweaked keypair.
let schnorr_sig = secp.sign_schnorr(&message, &keypair);
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand All @@ -483,7 +484,7 @@ mod test {
let secret_key = secp256k1::SecretKey::new(&mut OsRng);
let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
let schnorr_sig = secp.sign_schnorr(&message, &keypair);
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand All @@ -494,7 +495,7 @@ mod test {
// [5] Same as [4], but using its tweaked key.
let tweaked = keypair.tap_tweak(&secp, merkle_root);
let schnorr_sig = secp.sign_schnorr(&message, &tweaked.to_keypair());
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand Down Expand Up @@ -573,9 +574,9 @@ mod test {
assert!(proof_deser.verify(&tweaked_public_key.x(), message));

// [1] Verify the correct signature, which should succeed.
let schnorr_sig = bitcoin::secp256k1::schnorr::Signature::from_slice(&proof_bytes)
let schnorr_sig = schnorr::Signature::from_slice(&proof_bytes)
.expect("Failed to parse Signature from slice");
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::All,
};
Expand All @@ -585,7 +586,7 @@ mod test {

// [2] Verify the correct signature, but with a different sighash type,
// which should fail.
let taproot_sig = bitcoin::taproot::Signature {
let taproot_sig = taproot::Signature {
signature: schnorr_sig,
sighash_type: TapSighashType::None,
};
Expand Down
108 changes: 77 additions & 31 deletions src/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,81 @@
point::{Compressed, Error as PointError, Point, G},
scalar::Scalar,
},
util::{expand_to_scalar, hash_to_scalar},

Check failure on line 12 in src/compute.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `expand_to_scalar`

Check failure on line 12 in src/compute.rs

View workflow job for this annotation

GitHub Actions / test-all

unused import: `expand_to_scalar`

Check failure on line 12 in src/compute.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `expand_to_scalar`
};

#[allow(non_snake_case)]
/// Compute a binding value from the party ID, public nonces, and signed message using XMD-based expansion.
pub fn binding(id: &Scalar, B: &[PublicNonce], msg: &[u8]) -> Scalar {
let prefix = b"WSTS/binding";

// Serialize all input into a buffer
let mut buf = Vec::new();
buf.extend_from_slice(&id.to_bytes());
/// Compute the group commitment from the list of PartyIDs and nonce commitments
pub fn group_commitment(commitment_list: &[(Scalar, PublicNonce)]) -> Scalar {
let mut hasher = Sha256::new();
let prefix = "WSTS/group_commitment";

for b in B {
buf.extend_from_slice(b.D.compress().as_bytes());
buf.extend_from_slice(b.E.compress().as_bytes());
hasher.update(prefix.as_bytes());
for (id, public_nonce) in commitment_list {
hasher.update(id.to_bytes());
hasher.update(public_nonce.D.compress().as_bytes());
hasher.update(public_nonce.E.compress().as_bytes());
}

buf.extend_from_slice(msg);
hash_to_scalar(&mut hasher)
}

#[allow(non_snake_case)]
/// Compute the group commitment from the list of PartyIDs and nonce commitments
pub fn group_commitment_compressed(commitment_list: &[(Scalar, Compressed, Compressed)]) -> Scalar {
let mut hasher = Sha256::new();
let prefix = "WSTS/group_commitment";

hasher.update(prefix.as_bytes());
for (id, hiding_commitment, binding_commitment) in commitment_list {
hasher.update(id.to_bytes());
hasher.update(hiding_commitment.as_bytes());
hasher.update(binding_commitment.as_bytes());
}

expand_to_scalar(&buf, prefix)
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
hash_to_scalar(&mut hasher)
}

#[allow(non_snake_case)]
/// Compute a binding value from the party ID, public nonces, and signed message using XMD-based expansion.
pub fn binding_compressed(id: &Scalar, B: &[(Compressed, Compressed)], msg: &[u8]) -> Scalar {
let prefix = b"WSTS/binding";
/// Compute a binding value from the party ID, public nonces, and signed message
pub fn binding(
id: &Scalar,
group_public_key: Point,
commitment_list: &[(Scalar, PublicNonce)],
msg: &[u8],
) -> Scalar {
let mut hasher = Sha256::new();
let prefix = "WSTS/binding";
let encoded_group_commitment = group_commitment(commitment_list);

// Serialize all input into a buffer
let mut buf = Vec::new();
buf.extend_from_slice(&id.to_bytes());
hasher.update(prefix.as_bytes());
hasher.update(group_public_key.compress().as_bytes());
hasher.update(msg);
hasher.update(encoded_group_commitment.to_bytes());
hasher.update(id.to_bytes());

for (D, E) in B {
buf.extend_from_slice(D.as_bytes());
buf.extend_from_slice(E.as_bytes());
}
hash_to_scalar(&mut hasher)
}

buf.extend_from_slice(msg);
#[allow(non_snake_case)]
/// Compute a binding value from the party ID, public nonces, and signed message
pub fn binding_compressed(
id: &Scalar,
group_public_key: Point,
commitment_list: &[(Scalar, Compressed, Compressed)],
msg: &[u8],
) -> Scalar {
let mut hasher = Sha256::new();
let prefix = "WSTS/binding";
let encoded_group_commitment = group_commitment_compressed(commitment_list);

hasher.update(prefix.as_bytes());
hasher.update(group_public_key.compress().as_bytes());
hasher.update(msg);
hasher.update(encoded_group_commitment.to_bytes());
hasher.update(id.to_bytes());

expand_to_scalar(&buf, prefix)
.expect("FATAL: DST is less than 256 bytes so operation should not fail")
hash_to_scalar(&mut hasher)
}

#[allow(non_snake_case)]
Expand Down Expand Up @@ -82,10 +116,20 @@
// Is this the best way to return these values?
#[allow(non_snake_case)]
/// Compute the intermediate values used in both the parties and the aggregator
pub fn intermediate(msg: &[u8], party_ids: &[u32], nonces: &[PublicNonce]) -> (Vec<Point>, Point) {
pub fn intermediate(
msg: &[u8],
group_key: Point,
party_ids: &[u32],
nonces: &[PublicNonce],
) -> (Vec<Point>, Point) {
let commitment_list: Vec<(Scalar, PublicNonce)> = party_ids
.iter()
.zip(nonces)
.map(|(i, nonce)| (Scalar::from(*i), nonce.clone()))
.collect();
let rhos: Vec<Scalar> = party_ids
.iter()
.map(|&i| binding(&id(i), nonces, msg))
.map(|i| binding(&id(*i), group_key, &commitment_list, msg))
.collect();
let R_vec: Vec<Point> = zip(nonces, rhos)
.map(|(nonce, rho)| nonce.D + rho * nonce.E)
Expand All @@ -99,19 +143,21 @@
/// Compute the aggregate nonce
pub fn aggregate_nonce(
msg: &[u8],
group_key: Point,
party_ids: &[u32],
nonces: &[PublicNonce],
) -> Result<Point, PointError> {
let compressed_nonces: Vec<(Compressed, Compressed)> = nonces
let commitment_list: Vec<(Scalar, Compressed, Compressed)> = party_ids
.iter()
.map(|nonce| (nonce.D.compress(), nonce.E.compress()))
.zip(nonces)
.map(|(id, nonce)| (Scalar::from(*id), nonce.D.compress(), nonce.E.compress()))
.collect();
let scalars: Vec<Scalar> = party_ids
.iter()
.flat_map(|&i| {
[
Scalar::from(1),
binding_compressed(&id(i), &compressed_nonces, msg),
binding_compressed(&id(i), group_key, &commitment_list, msg),
]
})
.collect();
Expand Down
35 changes: 26 additions & 9 deletions src/state_machine/coordinator/fire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
if nonce_info.nonce_recv_key_ids.len() >= self.config.threshold as usize {
// We have a winning message!
self.message.clone_from(&nonce_response.message);
let aggregate_nonce = self.compute_aggregate_nonce();
let aggregate_nonce = self.compute_aggregate_nonce()?;
info!("Aggregate nonce: {aggregate_nonce}");

self.move_to(State::SigShareRequest(signature_type))?;
Expand Down Expand Up @@ -1248,7 +1248,7 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
}

#[allow(non_snake_case)]
fn compute_aggregate_nonce(&self) -> Point {
fn compute_aggregate_nonce(&self) -> Result<Point, Error> {
// XXX this needs to be key_ids for v1 and signer_ids for v2
let public_nonces = self
.message_nonces
Expand All @@ -1266,9 +1266,14 @@ impl<Aggregator: AggregatorTrait> Coordinator<Aggregator> {
.cloned()
.flat_map(|pn| pn.nonces)
.collect::<Vec<PublicNonce>>();
let (_, R) = compute::intermediate(&self.message, &party_ids, &nonces);

R
let Some(group_key) = self.aggregate_public_key else {
return Err(Error::MissingAggregatePublicKey);
};
let (_, aggregate_nonce) =
compute::intermediate(&self.message, group_key, &party_ids, &nonces);

Ok(aggregate_nonce)
}

fn compute_num_key_ids<'a, I>(&self, signer_ids: I) -> Result<u32, Error>
Expand Down Expand Up @@ -1592,11 +1597,11 @@ pub mod test {
coordinator::{
fire::Coordinator as FireCoordinator,
test::{
bad_signature_share_request, check_signature_shares, coordinator_state_machine,
empty_private_shares, empty_public_shares, equal_after_save_load,
feedback_messages, feedback_mutated_messages, gen_nonces, invalid_nonce,
new_coordinator, run_dkg_sign, setup, setup_with_timeouts, start_dkg_round,
start_signing_round, verify_packet_sigs,
bad_signature_share_request, btc_sign_verify, check_signature_shares,
coordinator_state_machine, empty_private_shares, empty_public_shares,
equal_after_save_load, feedback_messages, feedback_mutated_messages,
gen_nonces, invalid_nonce, new_coordinator, run_dkg_sign, setup,
setup_with_timeouts, start_dkg_round, start_signing_round, verify_packet_sigs,
},
Config, Coordinator as CoordinatorTrait, State,
},
Expand Down Expand Up @@ -1828,6 +1833,7 @@ pub mod test {
let signature_type = SignatureType::Frost;
let message = vec![0u8];
coordinator.state = State::NonceGather(signature_type);
coordinator.aggregate_public_key = Some(Point::from(Scalar::random(&mut rng)));

let nonce_response = NonceResponse {
dkg_id: 0,
Expand Down Expand Up @@ -3732,4 +3738,15 @@ pub mod test {
fn verify_packet_sigs_v2() {
verify_packet_sigs::<FireCoordinator<v2::Aggregator>, v2::Signer>();
}

#[test]
#[cfg(feature = "with_v1")]
fn btc_sign_verify_v1() {
btc_sign_verify::<FireCoordinator<v1::Aggregator>, v1::Signer>(5, 2);
}

#[test]
fn btc_sign_verify_v2() {
btc_sign_verify::<FireCoordinator<v2::Aggregator>, v2::Signer>(5, 2);
}
}
Loading
Loading