Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to serialize/deserialize PublicKey? #40

Closed
namuyan opened this issue Mar 30, 2019 · 10 comments
Closed

How to serialize/deserialize PublicKey? #40

namuyan opened this issue Mar 30, 2019 · 10 comments

Comments

@namuyan
Copy link

namuyan commented Mar 30, 2019

I want to recover from bytes.

fn main() {
    let ec_point: GE = ECPoint::generator();
    let secret: FE = ECScalar::new_random();
    let public: GE = ec_point.scalar_mul(&secret.get_element());
    let compressed_key = public.get_element().serialize();
    let new_public: GE = ECPoint::from_bytes(&compressed_key).expect("THIS");
    // let uncompressed_key = public.get_element().serialize_uncompressed();
    // let new_public: GE = ECPoint::from_bytes(&uncompressed_key).expect("THIS");
    assert_eq!(public, new_public);
}

It looks work, but paniced 'THIS: InvalidPublicKey.
I cannot find correct way to serialize/deserialize except this.
What's wrong is this way?

@namuyan
Copy link
Author

namuyan commented Mar 30, 2019

I fixed /src/elliptic/curves/secp256_k1.rs#L317
public key length is only 33 or 65.
How about this? Secp256k1 require prefix 2 or 3 to decide odd or even.

    fn from_bytes(bytes: &[u8]) -> Result<Secp256k1Point, ErrorKey> {
        if bytes.len() < 1 {
            return Err(ErrorKey::InvalidPublicKey);
        }
        let prefix = bytes[0];
        let byte_len = bytes.len();

        if byte_len == 33 && (prefix == 2 || prefix == 3) {
            let result = PK::from_slice(bytes[..33]);
            let test = result.map(|pk| Secp256k1Point {
                purpose: "random",
                ge: pk,
            });
            test.map_err(|_err| ErrorKey::InvalidPublicKey)
        } else if byte_len == 65 && prefix == 4 {
            let result = PK::from_slice(bytes[..65]);
            let test = result.map(|pk| Secp256k1Point {
                purpose: "random",
                ge: pk,
            });
            test.map_err(|_err| ErrorKey::InvalidPublicKey)
        } else {
            Err(ErrorKey::InvalidPublicKey)
        }
    }

@omershlo
Copy link
Contributor

Hi, @namuyan. A few comments:

  1. you have a serialize and deserialize implemented for GE. please try to use this (in your example you use the serialize of PublicKey which is the secp256k1 lib serialize).
  2. There's one more way, see this comment : https://github.com/KZen-networks/curv/blob/master/src/elliptic/curves/secp256_k1.rs#L294. It will work but I do not encourage using it (or at least let me know what computation you want to do to make sure its fine)
  3. from_bytes can take any random number of bytes, and make a point out of it, not used as deserilizer. For other elliptic curves there are algorithms to generate random uniformly distributed point from random bytes. in secp256k1 this is the best we get.
  4. having said that, I tend to be inclined to move to something like you suggested. Will have to think about it more.

Please let me know if (1) or (2) works for your application

@namuyan
Copy link
Author

namuyan commented Mar 30, 2019

Thank you for comment!
I just want simple and short bytes formatted public key, like Bitcoin use.

pub fn public_from_bytes(bytes: &[u8]) -> Result<Secp256k1Point, ErrorKey> {
    if bytes.len() < 32 {
        return Err(ErrorKey::InvalidPublicKey);
    }
    let prefix = bytes[0];
    let byte_len = bytes.len();

    let public =
        if byte_len == 33 && (prefix == 2 || prefix == 3) {
            PK::from_slice(bytes).map_err(|_err| ErrorKey::InvalidPublicKey)
        } else if byte_len == 65 && prefix == 4 {
            PK::from_slice(bytes).map_err(|_err| ErrorKey::InvalidPublicKey)
        } else {
            Err(ErrorKey::InvalidPublicKey)
        }?;
    Secp256k1Point::from_bytes(&public.serialize_uncompressed()[1..])
}

@omershlo
Copy link
Contributor

If you use it for ecdsa than my option 2 should work for you with the compressed format:

  1. to serielize - call bytes_compressed_to_big_int to get a BigInt from your point (you can transform bigint to bytes and bytes to bigint easily)
  2. to deserialize -
    /// 1) convert BigInt::to_vec
    /// 2) remove first byte [1..33]
    /// 3) call from_bytes

let me know if you have any problem

@namuyan
Copy link
Author

namuyan commented Mar 31, 2019

Thank you for reply.
Prefix of compressed public key is important, I cannot recover same Y point (odd or even?

@omershlo
Copy link
Contributor

That's true in the general case, but in the case of ECDSA it doesn't matter. you can verify ecdsa even with public key and R with wrong parity.

@namuyan
Copy link
Author

namuyan commented Apr 1, 2019

I try to use pubic key without prefix, but often verification failed.
Next, I checked your aggregation signature test code by point2point.

use curv::{BigInt,GE};
use curv::elliptic::curves::traits::ECPoint;
use curv::arithmetic::traits::Converter;
fn point2point(p: &GE) -> GE {
    let tmp = p.bytes_compressed_to_big_int();
    let tmp = BigInt::to_vec(&tmp);
    GE::from_bytes(&tmp[1..]).unwrap()
}

point2point wrapper covert public key with prefix 2 or 3 to prefix 2.

#[cfg(test)]
mod tests {
    use super::point2point;
    use curv::BigInt;
    use curv::GE;
    use protocols::aggsig::{verify, verify_partial, EphemeralKey, KeyAgg, KeyPair};
    extern crate hex;
    use curv::elliptic::curves::traits::*;

    #[test]
    fn test_multiparty_signing_for_two_parties() {
        let is_musig = true;
        let message: [u8; 4] = [79, 77, 69, 82];

        // round 0: generate signing keys
        let party1_key = KeyPair::create();
        let party2_key = KeyPair::create();

        // round 1: send commitments to ephemeral public keys
        let party1_ephemeral_key = EphemeralKey::create();
        let party2_ephemeral_key = EphemeralKey::create();
        let party1_commitment = &party1_ephemeral_key.commitment;
        let party2_commitment = &party2_ephemeral_key.commitment;

        // round 2: send ephemeral public keys and check commitments
        // p1 release R1' and p2 test com(R1') = com(R1):
        assert!(EphemeralKey::test_com(
            &point2point(&party2_ephemeral_key.keypair.public_key),
            &party2_ephemeral_key.blind_factor,
            party2_commitment
        ));
        // p2 release R2' and p1 test com(R2') = com(R2):
        assert!(EphemeralKey::test_com(
            &point2point(&party1_ephemeral_key.keypair.public_key),
            &party1_ephemeral_key.blind_factor,
            party1_commitment
        ));

        // compute apk:
        let mut pks: Vec<GE> = Vec::new();
        pks.push(party1_key.public_key.clone());
        pks.push(party2_key.public_key.clone());
        let party1_key_agg = KeyAgg::key_aggregation_n(&pks, 0);
        let party2_key_agg = KeyAgg::key_aggregation_n(&pks, 1);
        assert_eq!(party1_key_agg.apk, party2_key_agg.apk);

        // compute R' = R1+R2:
        let party1_r_tag = EphemeralKey::add_ephemeral_pub_keys(
            &point2point(&party1_ephemeral_key.keypair.public_key),
            &point2point(&party2_ephemeral_key.keypair.public_key),
        );

        let party2_r_tag = EphemeralKey::add_ephemeral_pub_keys(
            &point2point(&party1_ephemeral_key.keypair.public_key),
            &point2point(&party2_ephemeral_key.keypair.public_key),
        );

        assert_eq!(party1_r_tag, party2_r_tag);

        // compute c = H0(Rtag || apk || message)
        let party1_h_0 =
            EphemeralKey::hash_0(&party1_r_tag, &party1_key_agg.apk, &message, is_musig);
        let party2_h_0 =
            EphemeralKey::hash_0(&party2_r_tag, &party2_key_agg.apk, &message, is_musig);
        assert_eq!(party1_h_0, party2_h_0);

        // compute partial signature s_i and send to the other party:
        let s1 = EphemeralKey::sign(
            &party1_ephemeral_key,
            &party1_h_0,
            &party1_key,
            &party1_key_agg.hash,
        );
        let s2 = EphemeralKey::sign(
            &party2_ephemeral_key,
            &party2_h_0,
            &party2_key,
            &party2_key_agg.hash,
        );

        let r = party1_ephemeral_key.keypair.public_key.x_coor().unwrap();

        assert!(verify_partial(
            &ECScalar::from(&s1),
            &r,
            &ECScalar::from(&party1_h_0),
            &ECScalar::from(&party1_key_agg.hash),
            &party1_key.public_key
        )
        .is_ok());

        // signature s:
        let (r, s) = EphemeralKey::add_signature_parts(s1, &s2, &party1_r_tag);

        // verify:
        assert!(verify(&s, &r, &point2point(&party1_key_agg.apk), &message, is_musig,).is_ok())
    }
}

Is this code use prefix on process? or is something wrong with my idea?

@omershlo
Copy link
Contributor

omershlo commented Apr 1, 2019

Hi @namuyan, if I understand correctly, you are trying point2point on Schnorr signature. Therefore it make sense that you will fail sometimes (with wrong public key). I wrote before that only if you use ECDSA it is safe to use point2point. In your case I suggest to use the serialize and deserialize methods (option 1) that are using non compressed points, or to add a serialize/deserialize option for compress - if you do, I think it might be a good addition to the library.

@namuyan
Copy link
Author

namuyan commented Apr 2, 2019

Thank you for advice.
I understand that Schnorr signature require correct Y point. I decide to use compressed public key with prefix. Addition to it, I will marge key meta info (is_musig, etc). code

@omershlo
Copy link
Contributor

omershlo commented Apr 2, 2019

I agree that Schnorr requires correct Y coordinate.
Thank you for the discussion.
feel free to open a PR with your changes

@namuyan namuyan closed this as completed Apr 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants