Trust-Machines / wsts

Weighted Schnorr Threshold Signatures
Apache License 2.0
26 stars 12 forks source link

Intercompatibility with k256 #60

Open tbraun96 opened 5 months ago

tbraun96 commented 5 months ago

We are using the frost-secp256k1 crate for verification of WSTS-generated signatures: frost-secp256k1 { git = "https://github.com/LIT-Protocol/frost" }.

In theory, the math should be identical to this repo's use of Schnorr. Signatures in both crates use R and z values. This is how we convert from a WSTS signature to a ZCash FROST signature:

    let wsts_sig = sig_agg
        .sign(&msg, &party_nonces, &signature_shares, &party_key_ids)
        .map_err(|err| JobError {
            reason: err.to_string(),
        })?;

    // Convert the signature to a FROST-compatible format for pallet-verification
    let z_scalar_p = ScalarPrimitive::from_slice(&wsts_sig.z.to_bytes())
        .expect("Should be a valid 32-byte scalar");
    let z = k256::Scalar::from(&z_scalar_p);
    let x_generic_array = GenericArray::from(wsts_sig.R.x().to_bytes());
    let y_generic_array = GenericArray::from(wsts_sig.R.y().to_bytes());
    let R_encoded_point =
        EncodedPoint::from_affine_coordinates(&x_generic_array, &y_generic_array, true);
    let R = k256::ProjectivePoint::from_encoded_point(&R_encoded_point)
        .expect("Should be a valid encoded point");
    let frost_signature = frost_secp256k1::Signature::new(R, z);

And, this is how we convert from a WSTS group public key to a ZCash FROST group public key:

    let party: PartyState = party.save();

    // Convert the WSTS group key into a FROST-compatible format
    let group_point = party.group_key;
    let x_generic_array = GenericArray::from(group_point.x().to_bytes());
    let y_generic_array = GenericArray::from(group_point.y().to_bytes());
    let encoded_point =
        EncodedPoint::from_affine_coordinates(&x_generic_array, &y_generic_array, true);
    let projective_point = k256::ProjectivePoint::from_encoded_point(&encoded_point)
        .expect("Should be a valid encoded point");
    let verifying_key = VerifyingKey::new(projective_point);
    let public_key_frost_format = verifying_key.serialize().as_ref().to_vec();

Note: We are using wsts=3.0.0.

The to_bytes() function returns in big-endian. All functions that receive these big-endian bytes also expect a big-endian input. I have verified that the sizes are all correct, and, that none of this code panics.

However, when we attempt to verify using ZCash FROST, we get an invalid signature. I know this question may be outside of the scope of this repo, however, we do need to use wsts for our DKG and later use a no-std friendly crate for Schnorr signature verifications (specifically over secp256k1). Are there any ideas as to what we're doing wrong, or, if this is even possible? Thanks!

shekohex commented 5 months ago

Using frost_taproot instead of frost_secp256k1 works, since wsts and frsot_secp256k1 does use different challenge strings

xoloki commented 4 months ago

Using frost_taproot instead of frost_secp256k1 works, since wsts and frsot_secp256k1 does use different challenge strings

Yeah we use taproot compatible challenge strings in many (maybe all) contexts.

I always planned on offering a nostd build feature, would you like me to do that sooner rather than later? I actually did that for the dalek crates years ago, it was my first PRs for them.