mimblewimble / rust-secp256k1-zkp

ZKP fork for rust-secp256k1, adds wrappers for range proofs, pedersen commitments, etc
Creative Commons Zero v1.0 Universal
56 stars 51 forks source link

bulletproof requires nonce == blind #13

Closed mschneiderwng closed 6 years ago

mschneiderwng commented 6 years ago

If I understand it correctly, then the blind and nonce can be chosen independently (even if the rust api sets nonce = blind) when creating and unwinding bulletproofs. However, if nonce != blind, then only the first 32 bytes of the message can be unwinded as in the following test which can be inserted into perdersen.rs.

    #[test]
    fn test_bullet_nonce_neq_blind() {
        use std;
        use super::*;
        let secp = Secp256k1::with_caps(ContextFlag::Commit);
        let mut rng = OsRng::new().unwrap();
        let value = 1234567;
        let blind = SecretKey::new(&secp, &mut rng);
        let nonce = SecretKey::new(&secp, &mut rng);
        //let nonce = blind; // this works
        let mut msg = [0u8; 64];
        rng.fill_bytes(&mut msg);

        // ffi structures
        let n_bits = 64;
        let mut proof = [0; constants::MAX_PROOF_SIZE];
        let mut plen = constants::MAX_PROOF_SIZE as size_t;
        let extra_data = vec![];

        // create proof
        let success = unsafe {
            ffi::secp256k1_bulletproof_rangeproof_prove_single_w_scratch(
                secp.ctx,
                proof.as_mut_ptr(),
                &mut plen,
                value,
                blind.as_ptr(),
                constants::GENERATOR_H.as_ptr(),
                n_bits as size_t,
                nonce.as_ptr(),
                extra_data.as_ptr(),
                extra_data.len() as size_t,
                msg.as_ptr()
            ) == 1
        };
        assert!(success);

        // unwind proof
        let mut unwinded_msg = [0u8; 64];
        let commit = secp.commit(value, blind).unwrap();
        let success = unsafe {
            ffi::secp256k1_bulletproof_rangeproof_unwind_message(
                secp.ctx,
                proof.as_ptr(),
                plen as size_t,
                commit.as_ptr(),
                n_bits as size_t,
                constants::GENERATOR_H.as_ptr(),
                extra_data.as_ptr(),
                extra_data.len() as size_t,
                nonce.as_ptr(),
                unwinded_msg.as_mut_ptr(),
            ) == 1
        };
        assert!(success);

        println!("msg:     {:?}", msg.to_vec());
        println!("unwinded:{:?}", unwinded_msg.to_vec());

        for i in 0..msg.len() {
            assert_eq!(msg[i], unwinded_msg[i]);
        }
    }
yeastplume commented 6 years ago

Hi @mschneiderwng, thanks for pointing this out (and sorry it took a couple of days to get back to you, I wasn't watching the repository, am now). You're right because Grin is only using the case where the nonce==blind the message API was assuming they'd be the same on rewind. Although Grin doesn't need it, I've changed the API to require the caller to pass in both a blinding factor and a nonce, and it's up to the caller whether they want to keep it the same. I've also added your test into ours. Thanks for pointing this out!

mschneiderwng commented 6 years ago

@yeastplume thanks for the fast response. Just curious: Do bulletproofs require both, blind and nonce to unwind? This is somewhat different from vanilla range proofs, where the message is can be revealed with the nonce only.

yeastplume commented 6 years ago

No problem! The bullet proof message hiding isn't 'unwinding' in quite the same way rangeproofs are, it's using some internal data to XOR the message as sort of a side-product. It basically works by seeding an RNG with the same values, then using publicly derivable values to XOR back.. the nonce + commit is used for the RNG seeding, and the blinding factor is needed to re-calculate one of the XOR values.. I have a fuller description here: https://github.com/mimblewimble/grin/issues/734

Note grin uses blind == nonce just to avoid having to store an extra value (the blinding is available to the wallet-holder). Otherwise, you can use whatever values you want (after this fix).