herumi / bls

288 stars 132 forks source link

Deserialize elements of G1, G2 represented in compressed form. #103

Closed vincenzoiovino closed 1 year ago

vincenzoiovino commented 1 year ago

I try to use mclBnG2_setStr to deserialize a group element published somewhere in hex format and representing a valid G2 point in compressed form but I am not able to make the deserialization successful. I used the format "1 A B" but it does not work. My guess is that it expects 4 fields not two because it expects points in uncompressed format, is it correct? So is there a way to deserialize a point in compressed form? Thank you in advance.

herumi commented 1 year ago

What is the hex string?

To deserialize a string with hex format, pass it to call mclBnG2_setSr with ioMode = MCLBN_IO_SERIALIZE_HEX_STR. https://github.com/herumi/mcl/blob/master/include/mcl/bn.h#L237-L247

vincenzoiovino commented 1 year ago

The hex string is: a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e In a Rust library I can use this string to get a valid G2 point.

In mcl I set: const char *G2Str="1 0xa0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d 0x0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"; mclBnG2 G2; but ASSERT(!mclBnG2_setStr(&G2, G2Str, strlen(G2Str), MCLBN_IO_SERIALIZE_HEX_STR)); fails. I guessed that the getStr is expecting a point in non-compressed form whereas that one is compressed or am I missing something? thank you a lot. (my guess is based on the fact that getStr works for G2 points consisting of tuples of "4 elements" rather than 2.)

herumi commented 1 year ago

The hex string is serialized (compressed) data, so you must call it the following.

mclBnG2 g;
const char *s = "a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e";
ret = mclBnG2_setStr(&g, s, strlen(s), MCLBN_IO_SERIALIZE_HEX_STR);
if (ret != 0) {
    puts("ERR");
    return 1;
}

You can see the deserialized (uncompressed) data as the following.

char buf[512];
ret = mclBnG2_getStr(buf, sizeof(buf), &g, 16);
if (ret == 0) {
    puts("ERR2");
}
printf("g=%s\n", buf);
buf=1 df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d 9518ba5deb9dcff223f3e24a70ac914947f300384b354a7c99f4b0cd208a75c5a938601a6afabb962e8a6c75aa67b82 fbf5a7ef15d600ac4f6d5c8aae3a6036e65e4f62e6ba07b143b8a510a74d28b3e065c95dda40902164e0dba3e695619
vincenzoiovino commented 1 year ago

I tiried your snippet but ret returns -1 so it is not able to deserialize the compressed string. Note: at the beginning of the code I initialize mclBn_init(MCL_BLS12_381, MCLBN_COMPILED_TIME_VAR); is it correct? (I do not get any error from this initialization)

herumi commented 1 year ago

You only need to call blsSetETHserialization(1); once after blsInit.

vincenzoiovino commented 1 year ago

Thanks. the deserialization works but the problem seems to be the signature or the hash function. The minimal example, adapted from your bls12_381_smp.cpp is the following:

include <bls/bls384_256.h>

include

include

int main() { blsPublicKey PK; blsSignature sig; const char sigStr="9544ddce2fdbe8688d6f5b4f98eed5d63eee3902e7e162050ac0f45905a55657714880adabe3c3096b92767d886567d0"; const char msg[8] = {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; const char PKStr="a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"; int ret = blsInit(MCL_BLS12_381, MCLBN_COMPILED_TIME_VAR); if (ret) { printf("err %d\n", ret); return 1; } mclBn_setETHserialization(1); ret=mclBnG2_setStr(&PK.v, PKStr, strlen(PKStr), MCLBN_IO_SERIALIZE_HEX_STR); printf("%d\n",ret); ret=mclBnG1_setStr(&sig.v,sigStr, strlen(sigStr), MCLBN_IO_SERIALIZE_HEX_STR); printf("%d\n",ret); printf("verify %d\n", blsVerify(&sig, &PK, msg, 8)); }

It prints: 0 0 verify 0

so the behavior is not as expected. Instead in a rust bls12_381 library, that signature is verified with respect to that public key. Maybe is there some issue with compatibility of hash functions? in that rust library they use the standardized hashing from a RFC. I also tried to set mclBn_setMapToMode(MCL_MAP_TO_MODE_HASH_TO_CURVE); soon after init but it does not work. Or maybe a problem with the generator of G2? The rust library uses the generator of Ethereum. I also tried with that one in a separate code rewriting the verificatio algorithm by myself but does not work as well.

Any clue?

herumi commented 1 year ago

Or maybe a problem with the generator of G2? The rust library uses the generator of Ethereum

You use G2 as PublicKey, meaning mcl/bls without BLS_ETH. The generator of Ethereum is an element of G1, not G2. So I think you must set up the generator of G2 according to the Rust library, and moreover, you may have to set up domain separation.

cf. Please read carefully initDFINITY.

vincenzoiovino commented 1 year ago

I did that in a separate code:

include

include

include

include <mcl/bn_c384_256.h>

int main(void){ const char g1Str = "1 0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb 0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"; const char g2Str = "1 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582"; const char PKStr="a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"; const char sigStr="9544ddce2fdbe8688d6f5b4f98eed5d63eee3902e7e162050ac0f45905a55657714880adabe3c3096b92767d886567d0"; mclBnGT e1, e2; mclBnG2 g2,PK; mclBnG1 g1,sig, HashedMsg; char msg[8]={ 0x01, 0x00,0x00,0x00,0x00,0x00,0x00,0x00}; const char *dst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RONUL";

int ret = mclBn_init(MCL_BLS12_381, MCLBN_COMPILED_TIME_VAR); printf("%d\n",ret); mclBn_setETHserialization(1); mclBn_setMapToMode(MCL_MAP_TO_MODE_HASH_TO_CURVE); mclBnG1_setDst(dst, strlen(dst)); ret=mclBnG1_setStr(&g1, g1Str, strlen(g1Str), 16); printf("%d\n",ret); ret=mclBnG2_setStr(&g2, g2Str, strlen(g2Str), 10); printf("%d\n",ret); ret=mclBnG2_setStr(&PK, PKStr, strlen(PKStr), MCLBN_IO_SERIALIZE_HEX_STR); printf("%d\n",ret); ret=mclBnG1_setStr(&sig, sigStr, strlen(sigStr), MCLBN_IO_SERIALIZE_HEX_STR); printf("%d\n",ret); mclBnG1_hashAndMapTo(&HashedMsg, (void *)&msg, 8); mclBn_pairing(&e1, &sig, &g2); mclBn_pairing(&e2, &HashedMsg, &PK); ret=mclBnGT_isEqual(&e1, &e2); printf("verify: %d\n",ret); return 0; }

but the verification fails. Note: the domain separation uses G2 instead of G1. Is it correct? It seemed weird to me because it is on G1 but this is what the rust library does both for hashing to G1 and G2. However, even if I change the domain string replacing G2 with G1 it does not work.

herumi commented 1 year ago

The domain separation affects a hash function to Signature, and Signature is G1 on your system, then using mclBnG1_setDst is correct. The value used in https://github.com/herumi/bls/blob/master/sample/dfinity.c#L38 is defined by DFINITY. Does your system use the same value? You have to find out which domain separation the Rust library uses.

vincenzoiovino commented 1 year ago

The rust library in line 15 here: https://github.com/noislabs/drand-verify/blob/main/src/verify.rs uses the same domain I used in the C code above but it does not work in mcl. Any clue on what is going on?

herumi commented 1 year ago

const DOMAIN: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; is okay.

But, https://github.com/noislabs/drand-verify/blob/main/src/verify.rs#L205-L210

The message function seems complex. char msg[8]={ 0x01, 0x00,0x00,0x00,0x00,0x00,0x00,0x00}; is simple.

https://github.com/noislabs/drand-verify/blob/main/src/verify.rs#L58-L60

        let msg = message(round, previous_signature);
        let msg_on_curve = Self::msg_to_curve(&msg);  // mclBnG1_hashAndMapTo
        self.verify_step2(signature, &msg_on_curve) // blsVerify

Wouldn't you have to pass the result of that message() function to mcl/bls verify?

vincenzoiovino commented 1 year ago

The message is the round number current_round that is an integer of 64 bits. I try to verify the first round that is the integer 1 of 64 bits. fn message(current_round: u64, prev_sig: &[u8]) -> Vec { let mut hasher = Sha256::default(); hasher.update(prev_sig); hasher.update(round_to_bytes(current_round)); hasher.finalize().to_vec() } This is how the message is converted. You can ignore prev_sig because they have two chains and for the chain I am verifying prev_sig is the empty string so only current_round of type u64 is hashed. In fact I call the verify algorithm on input: let round: u64 = 1; and it verifies. So in the C code that uses mcl I set the msg to be a string that represents the integer 1 of 64 bits. Am I wrong?

herumi commented 1 year ago

I only glanced at it during a break at work so I could be wrong, but the message function outputs the SHA-256 value; then you have to pass the SHA-256 results to mcl/verify, don't you?

I cannot reply any further until at least tomorrow.

vincenzoiovino commented 1 year ago

Thanks a lot, I was finally able to verify! IT was a combination of things. They incorrectly have a wrong domain string (they just acknowledged the bug in a private conversation to me). Because of this bug I was trying many combinations, like hashing or not hashing with SHA before verifying, with big or little endianess etc. The right combination is then: 1) wrong domain string for G2 instead of G1 2) big endianness and 3) sha256 hash before hash to point. thanks a lot for your help