silversixpence-crypto / zk-proof-of-assets

MIT License
3 stars 0 forks source link

Hide balance in Pedersen commitment #38

Closed Stentonian closed 3 months ago

Stentonian commented 4 months ago

Layer 3 needs to be adjusted to hide the asset sum behind a Pedersen commitment

More details here https://hackmd.io/juvO0SxlSqWa2pTYDKCOcw?view=#Pedersen-commitments

Stentonian commented 4 months ago

We are going to try doing a scalar multiply in ed25519-circom and check that we get the same answer when doing it with curve25519_dalek_ng (used by Bulletproofs).

First thing to notice is that the ScalarMul circuit needs the curve points to be in extended coords (X, Y, Z, T): https://github.com/Electron-Labs/ed25519-circom/blob/main/circuits/scalarmul.circom#L81

dalek also stores points in extended coords (called an EdwardsPoint). The base point g for Pedersen commitments is defined here

We should pick some random scalar value, and use both the circuit & dalek code to multiply the base point above by the scalar, and check that the 2 results are equal. This will give us an indication whether the maths is the same or not.

Stentonian commented 4 months ago

Scalar multiply seems to be the same across the 2 repos. Next is to try full Pedersen commitment.

The dalek library doesn't make it easy to get the field elements in decimal format, but it was checked that this point in ed25519-circom

      const P = [
        15112221349535400772501151409588531511454012693041857206046113283949847762202n,
        46316835694926478169428394003475163141307993866256225615783033603165251855960n,
        1n,
        46827403850823179245072216630277197565144205554125654976674165829533817101731n,
      ];

is the same as the dalek base point using this unit test appended to field.rs:

    #[test]
    fn test_things() {
        use std::vec::Vec;

        let circuit_point_x_be: [u8; 32] = [
            0x21, 0x69, 0x36, 0xD3, 0xCD, 0x6E, 0x53, 0xFE, 0xC0, 0xA4, 0xE2, 0x31, 0xFD, 0xD6,
            0xDC, 0x5C, 0x69, 0x2C, 0xC7, 0x60, 0x95, 0x25, 0xA7, 0xB2, 0xC9, 0x56, 0x2D, 0x60,
            0x8F, 0x25, 0xD5, 0x1A,
        ];
        let mut circuit_point_x_le: [u8; 32] = circuit_point_x_be;
        circuit_point_x_le.reverse();
        let circuit_X = FieldElement::from_bytes(&circuit_point_x_le);

        let circuit_point_y_be: [u8; 32] = [
            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
            0x66, 0x66, 0x66, 0x58,
        ];
        let mut circuit_point_y_le: [u8; 32] = circuit_point_y_be;
        circuit_point_y_le.reverse();
        let circuit_Y = FieldElement::from_bytes(&circuit_point_y_le);

        let circuit_Z = FieldElement51::one();

        let circuit_point_t_be: [u8; 32] = [
            0x67, 0x87, 0x5F, 0x0F, 0xD7, 0x8B, 0x76, 0x65, 0x66, 0xEA, 0x4E, 0x8E, 0x64, 0xAB,
            0xE3, 0x7D, 0x20, 0xF0, 0x9F, 0x80, 0x77, 0x51, 0x52, 0xF5, 0x6D, 0xDE, 0x8A, 0xB3,
            0xA5, 0xB7, 0xDD, 0xA3,
        ];
        let mut circuit_point_t_le: [u8; 32] = circuit_point_t_be;
        circuit_point_t_le.reverse();
        let circuit_T = FieldElement::from_bytes(&circuit_point_t_le);

        let circuit_base_point = EdwardsPoint {
            X: circuit_X,
            Y: circuit_Y,
            Z: circuit_Z,
            T: circuit_T,
        };

        use crate::{
            constants::ED25519_BASEPOINT_POINT,
            constants::RISTRETTO_BASEPOINT_POINT,
            edwards::EdwardsPoint,
            ristretto::RistrettoPoint,
            scalar::Scalar,
            traits::{Identity, MultiscalarMul},
        };

        use core::ops::{Mul, MulAssign};

        let point_r = RISTRETTO_BASEPOINT_POINT;
        let point_e = ED25519_BASEPOINT_POINT;
        let point_id = EdwardsPoint::identity();
        let point_other = EdwardsPoint {
            X: FieldElement51::zero(),
            Y: FieldElement51([64, 0, 0, 0, 0]),
            Z: FieldElement51([64, 0, 0, 0, 0]),
            T: FieldElement51::zero(),
        };

        let scalar_two = Scalar::from(2u64);
        let scalar_one = Scalar::from(1u64);
        let s = scalar_one;

        let prod = point_id;
        let prod = EdwardsPoint::multiscalar_mul(&[s], &[point_id]);
        let prod = point_id * s;

        println!("ID = {:?}", point_id);
        println!("s = {:?}", s);
        println!("ID * s = {:?}", prod);
        println!("(ID * s).X = {:x?}", prod.X.to_bytes());
        println!("(ID * s).Y = {:x?}", prod.Y.to_bytes());
        println!("(ID * s).Z = {:x?}", prod.Z.to_bytes());
        println!("(ID * s).T = {:x?}", prod.T.to_bytes());
        println!("ID == ID * 1 {:?}", point_id == prod);
        println!("base points equal {:?}", ED25519_BASEPOINT_POINT == circuit_base_point);
        assert!(false);
    }
Stentonian commented 4 months ago

From a single test it seems that the Pedersen commitment generated by the Bulletproofs library is the same as that generated by the JS & circuit in ed25519-circom. See here for the code used to test ed25519-circom. The following test was added to field.rs to test equality with the rust code:

    #[test]
    fn thingthing() {
        use std::vec::Vec;

        let circuit_point_x_be: [u8; 32] = [
            0x21, 0x69, 0x36, 0xD3, 0xCD, 0x6E, 0x53, 0xFE, 0xC0, 0xA4, 0xE2, 0x31, 0xFD, 0xD6,
            0xDC, 0x5C, 0x69, 0x2C, 0xC7, 0x60, 0x95, 0x25, 0xA7, 0xB2, 0xC9, 0x56, 0x2D, 0x60,
            0x8F, 0x25, 0xD5, 0x1A,
        ];
        let mut circuit_point_x_le: [u8; 32] = circuit_point_x_be;
        circuit_point_x_le.reverse();
        let circuit_X = FieldElement::from_bytes(&circuit_point_x_le);

        let circuit_point_y_be: [u8; 32] = [
            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
            0x66, 0x66, 0x66, 0x58,
        ];
        let mut circuit_point_y_le: [u8; 32] = circuit_point_y_be;
        circuit_point_y_le.reverse();
        let circuit_Y = FieldElement::from_bytes(&circuit_point_y_le);

        let circuit_Z = FieldElement51::one();

        let circuit_point_t_be: [u8; 32] = [
            0x67, 0x87, 0x5F, 0x0F, 0xD7, 0x8B, 0x76, 0x65, 0x66, 0xEA, 0x4E, 0x8E, 0x64, 0xAB,
            0xE3, 0x7D, 0x20, 0xF0, 0x9F, 0x80, 0x77, 0x51, 0x52, 0xF5, 0x6D, 0xDE, 0x8A, 0xB3,
            0xA5, 0xB7, 0xDD, 0xA3,
        ];
        let mut circuit_point_t_le: [u8; 32] = circuit_point_t_be;
        circuit_point_t_le.reverse();
        let circuit_T = FieldElement::from_bytes(&circuit_point_t_le);

        let circuit_base_point = EdwardsPoint {
            X: circuit_X,
            Y: circuit_Y,
            Z: circuit_Z,
            T: circuit_T,
        };

        use crate::{
            constants::ED25519_BASEPOINT_POINT,
            constants::RISTRETTO_BASEPOINT_POINT,
            edwards::EdwardsPoint,
            ristretto::RistrettoPoint,
            scalar::Scalar,
            traits::{Identity, MultiscalarMul},
        };

        use core::ops::{Mul, MulAssign};

        let point_r = RISTRETTO_BASEPOINT_POINT;
        let point_e = ED25519_BASEPOINT_POINT;
        let point_id = EdwardsPoint::identity();
        let point_other = EdwardsPoint {
            X: FieldElement51::zero(),
            Y: FieldElement51([64, 0, 0, 0, 0]),
            Z: FieldElement51([64, 0, 0, 0, 0]),
            T: FieldElement51::zero(),
        };

        let scalar_two = Scalar::from(2u64);
        let scalar_one = Scalar::from(1u64);
        let s = scalar_one;

        let prod = point_id;
        let prod = EdwardsPoint::multiscalar_mul(&[s], &[point_id]);
        let prod = point_id * s;

        println!("ID = {:?}", point_id);
        println!("s = {:?}", s);
        println!("ID * s = {:?}", prod);
        println!("(ID * s).X = {:x?}", prod.X.to_bytes());
        println!("(ID * s).Y = {:x?}", prod.Y.to_bytes());
        println!("(ID * s).Z = {:x?}", prod.Z.to_bytes());
        println!("(ID * s).T = {:x?}", prod.T.to_bytes());
        println!("\nID == ID * 1 {:?}", point_id == prod);
        println!(
            "\nbase points equal {:?}",
            ED25519_BASEPOINT_POINT == circuit_base_point
        );

        use crate::constants::RISTRETTO_BASEPOINT_COMPRESSED;
        use digest::{ExtendableOutputDirty, Update, XofReader};
        use sha3::Sha3_512;

        let point_hash =
            RistrettoPoint::hash_from_bytes::<Sha3_512>(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());

        println!("\nH.X: {:x?}", point_hash.0.X.to_bytes());
        println!("H.Y: {:x?}", point_hash.0.Y.to_bytes());
        println!("H.Z: {:x?}", point_hash.0.Z.to_bytes());
        println!("H.T: {:x?}", point_hash.0.T.to_bytes());

        // H.X: [d4c23bc87b8c18fae7dbd48f33cf86bd1331a37d068da27c276f973cbd1d4f4a]
        // XXX a4f4d1dbc379f672c72ad860d73a1331db68fc33f84dbd7eaf81c8b78cb32c4d
        // XXX 74611866241262482218924848751621262905712641867768913466201722288867539889229
        // 33610936965734216034622052748864527785054979741013463956582067314415336407764

        // H.Y: [09c1179d0a976f8c4c1a524910a02b649f93c80cabd7a91f646eeb6cfcae4e56]
        // XXX 65e4eacfc6bee646f19a7dbac08c39f946b20a019425a1c4c8f679a0d9711c90
        // XXX 46088059447963851202877910652493995174271590680218357731117593782516752391312
        // 39037926758455103342491841394431773648115673280860795116462000885017926418697

        // H.Z: [ee7a7341d0b311220d77a8e82d725ceea08d5fe42b8b729df73598196d846d63]
        // XXX 36d648d69189537fd927b8b24ef5d80aeec527d28e8a77d022113b0d1437a7ee
        // XXX 24803501805851289910923208023974078174589554811367212262408246278036626319342
        // 44972472311651602601636560056538958210842501314939311016992875096561375476462

        // H.T: [9d62d3fa8e4379ab2c0e42b3cf6d95136eac832b63f7840d631ea62e7954e737]
        // XXX 737e4597e26ae136d0487f36b238cae63159d6fc3b24e0c2ba9734e8af3d26d9
        // XXX 52239080632532132070101193023593606034740220941411414931038259084165985937113
        // 25285931357802837959040485138497351343220742265312934020814563180777586254493

        /////////////////////////////////////////

        let g = RistrettoPoint(ED25519_BASEPOINT_POINT);
        let h = point_hash;

        let q = Scalar::from(5u64);
        let r = Scalar::from_bytes_mod_order([
            // 4869643893319708471955165214975585939793846505679808910535986866633137979160
            0x18, 0xFF, 0x9B, 0x53, 0x8D, 0x16, 0xF2, 0x90, 0xAE, 0x67, 0xF7, 0x60, 0x98, 0x4D,
            0xC6, 0x59, 0x4A, 0x7C, 0x15, 0xE9, 0x71, 0x6E, 0xD2, 0x8D, 0xC0, 0x27, 0xBE, 0xCE,
            0xEA, 0x1E, 0xC4, 0x0A,
        ]);

        let gq = q * g;
        let hr = r * h;
        let gqhr = gq + hr;

        let gqhr_js = EdwardsPoint {
            // 37265365969786020775905420635296558667377843363697089491268621263348662982968
            X: FieldElement::from_bytes(&[
                0x38, 0xD9, 0x96, 0xF3, 0xE5, 0xD9, 0x3D, 0xCD, 0x4F, 0x5E, 0x4C, 0x75, 0x1C, 0xE5,
                0x71, 0x44, 0xD4, 0x80, 0x0B, 0x93, 0x1E, 0x18, 0x3B, 0x99, 0xDB, 0x8C, 0xA2, 0xA0,
                0x1E, 0x73, 0x63, 0x52,
            ]),

            // 19918088020382620484898381577491593815929231710243946334400286158202769470154
            Y: FieldElement::from_bytes(&[
                0xCA, 0x72, 0xA3, 0x34, 0x6D, 0x70, 0x8C, 0x4C, 0xEA, 0xCF, 0x1C, 0x88, 0x8A, 0xF2,
                0x9F, 0x31, 0xC4, 0x51, 0x6E, 0x50, 0xCD, 0x70, 0xFF, 0x2E, 0x19, 0xC3, 0x8C, 0xF3,
                0x01, 0x3D, 0x09, 0x2C,
            ]),

            // 31159927823477228180874415041051045889480209716426176898820219283387381240684
            Z: FieldElement::from_bytes(&[
                0x6C, 0xD3, 0x90, 0x13, 0x2D, 0x70, 0x36, 0x0D, 0x05, 0xEF, 0x34, 0xAE, 0x4A, 0xAE,
                0x4F, 0xAE, 0xF9, 0xC9, 0x1B, 0xB1, 0xA7, 0x34, 0x24, 0xFE, 0x5D, 0x4E, 0xD0, 0x60,
                0xE7, 0xE4, 0xE3, 0x44,
            ]),

            // 47079230722737970294378002000658448476925929131576789322759010697112857187123
            T: FieldElement::from_bytes(&[
                0x33, 0x93, 0xB5, 0xEF, 0xE2, 0x54, 0x58, 0xE9, 0xB2, 0xCF, 0x14, 0x35, 0xB9, 0xFA,
                0x56, 0x3D, 0x44, 0x58, 0x62, 0x3C, 0xF8, 0x99, 0x47, 0xDA, 0xF3, 0xB2, 0x03, 0xAA,
                0x79, 0xE6, 0x15, 0x68,
            ]),
        };

        let gqhr_circuit = EdwardsPoint {
            // 48635943642156872448029152956705068449475690634291720428641991382245276795252
            X: FieldElement::from_bytes(&[
                0x74, 0x41, 0x3E, 0xA3, 0x63, 0xD9, 0xFC, 0x08, 0xEF, 0xF5, 0x7B, 0x9E, 0x07, 0xF2,
                0xF2, 0xEB, 0xD0, 0xEB, 0x3A, 0x67, 0x75, 0x56, 0x2B, 0x16, 0xCD, 0x5F, 0x0E, 0x05,
                0xF5, 0xF7, 0x86, 0x6B,
            ]),

            // 35355889176304605977521618576425045831228212122756410650668849545862790365375
            Y: FieldElement::from_bytes(&[
                0xBF, 0xC0, 0x35, 0x91, 0xC1, 0xFC, 0xA7, 0x9A, 0x32, 0x03, 0x5D, 0x5B, 0x83, 0x89,
                0xC0, 0xFD, 0xE5, 0xD5, 0xB4, 0x1D, 0x31, 0x9E, 0xEC, 0x42, 0x04, 0x0E, 0x2D, 0xF5,
                0x5F, 0xB9, 0x2A, 0x4E,
            ]),

            // 42591440083240086457862386893212933734720307988579781535698235417028191455850
            Z: FieldElement::from_bytes(&[
                0x6A, 0x0A, 0x76, 0xBF, 0xA6, 0x1C, 0x23, 0x8E, 0xBC, 0xB6, 0xAB, 0x93, 0x27, 0x78,
                0x35, 0x4D, 0xCE, 0x3A, 0xEB, 0xEF, 0xC9, 0x79, 0xE4, 0xCA, 0x82, 0x27, 0xE0, 0x3B,
                0x9B, 0xE6, 0x29, 0x5E,
            ]),

            // 23882048166316632707970310251995641678446217857323120501945051361819071271765
            T: FieldElement::from_bytes(&[
                0x55, 0xD3, 0x06, 0x6C, 0x7D, 0x70, 0xB7, 0x05, 0xEE, 0xDA, 0xE0, 0x37, 0x38, 0xCC,
                0xE8, 0x85, 0xEB, 0xAD, 0xEC, 0x3B, 0x0A, 0x2D, 0x21, 0x28, 0xFD, 0x16, 0xC2, 0x7E,
                0x9F, 0xC2, 0xCC, 0x34,
            ]),
        };

        println!(
            "\ng^q*h^r circuit == g^q*h^r js {:?}",
            gqhr_js == gqhr_circuit
        );
        println!(
            "g^q*h^r circuit == g^q*h^r rust {:?}",
            gqhr.0 == gqhr_circuit
        );
        println!("g^q*h^r rust == g^q*h^r js {:?}", gqhr_js == gqhr.0);

        use num_bigint::BigUint;

        println!("\nBigInt(r) {:?}", BigUint::from_bytes_le(&r.to_bytes()));

        println!("\ngq");
        println!(
            "BigInt(gq.X) {:?}",
            BigUint::from_bytes_le(&gq.0.X.to_bytes())
        );
        println!(
            "BigInt(gq.Y) {:?}",
            BigUint::from_bytes_le(&gq.0.Y.to_bytes())
        );
        println!(
            "BigInt(gq.Z) {:?}",
            BigUint::from_bytes_le(&gq.0.Z.to_bytes())
        );
        println!(
            "BigInt(gq.T) {:?}",
            BigUint::from_bytes_le(&gq.0.T.to_bytes())
        );

        println!("\nhr");
        println!(
            "BigInt(hr.X) {:?}",
            BigUint::from_bytes_le(&hr.0.X.to_bytes())
        );
        println!(
            "BigInt(hr.Y) {:?}",
            BigUint::from_bytes_le(&hr.0.Y.to_bytes())
        );
        println!(
            "BigInt(hr.Z) {:?}",
            BigUint::from_bytes_le(&hr.0.Z.to_bytes())
        );
        println!(
            "BigInt(hr.T) {:?}",
            BigUint::from_bytes_le(&hr.0.T.to_bytes())
        );

        assert!(false);
    }
Stentonian commented 3 months ago

https://github.com/silversixpence-crypto/zk-proof-of-assets/pull/40

Stentonian commented 3 months ago

A note on the binary representation of the scalar power values:

  1. The circuit requires the value as a 255 bit array:

    template ScalarMul(){
    signal input s[255];
    signal input P[4][3];
    signal output sP[4][3];

    https://github.com/Electron-Labs/ed25519-circom/blob/c9435c021384a74009c0b2ec2a5e863b2190e63b/circuits/scalarmul.circom#L80

  2. The JS test for the circuit first converts the number to a byte array, reverses it to get little endian, then turns it to bits and pads it:

      const s = 4869643893319708471955165214975585939793846505679808910535986866633137979160n;
      const buf = utils.bigIntToLEBuffer(s);
      const asBits = utils.pad(utils.buffer2bits(buf), 255);

    https://github.com/Electron-Labs/ed25519-circom/blob/c9435c021384a74009c0b2ec2a5e863b2190e63b/test/scalarmul.test.js#L18-L19

  3. buffer2bits reverses the bits which means it is not little endian anymore, but simply the reverse of the big endian bits:

    function buffer2bits(buff) {
    const res = [];
    for (let i = 0; i < buff.length; i++) {
        for (let j = 0; j < 8; j++) {
            if ((buff[i] >> j) & 1) {
                res.push(1n);
            } else {
                res.push(0n);
            }
        }
    }
    return res;
    }

    https://github.com/Electron-Labs/ed25519-circom/blob/c9435c021384a74009c0b2ec2a5e863b2190e63b/test/utils.js#L2

  4. This means the whole operation of converting to little endian is confusing and superfluous. The same result could have been gotten by just converting to bits first (which would default to BE) then reversing the bits.

Let's take a look at an example. First we add some logging to utils.bigIntToLEBuffer:

function bigIntToLEBuffer(x) {
  console.log("before ", x)
  console.log("BE ", Buffer.from(convertToEvenLength(x.toString(16)), 'hex'));
  console.log("LE ", Buffer.from(convertToEvenLength(x.toString(16)), 'hex').reverse());
  return Buffer.from(convertToEvenLength(x.toString(16)), 'hex').reverse()
}

https://github.com/Electron-Labs/ed25519-circom/blob/c9435c021384a74009c0b2ec2a5e863b2190e63b/test/utils.js#L34

If we use input s = 4869643893319708471955165214975585939793846505679808910535986866633137979160n then we get:

BE  <Buffer 0a c4 1e ea ce be 27 c0 8d d2 6e 71 e9 15 7c 4a 59 c6 4d 98 60 f7 67 ae 90 f2 16 8d 53 9b ff 18>
LE  <Buffer 18 ff 9b 53 8d 16 f2 90 ae 67 f7 60 98 4d c6 59 4a 7c 15 e9 71 6e d2 8d c0 27 be ce ea 1e c4 0a>

This is correct and can be compared to another tool like https://www.rapidtables.com/convert/number/decimal-to-hex.html

# BE bits (using tool)
00001010 11000100 00011110 11101010 11001110 10111110 00100111 11000000 10001101 11010010 01101110 01110001 11101001 00010101 01111100 01001010 01011001 11000110 01001101 10011000 01100000 11110111 01100111 10101110 10010000 11110010 00010110 10001101 01010011 10011011 11111111 00011000

# bits from JS after calling `buffer2bits`
00011000 11111111 11011001 11001010 10110001 01101000 01001111 00001001 01110101 11100110 11101111 00000110 00011001 10110010 01100011 10011010 01010010 00111110 10101000 10010111 10001110 01110110 01001011 10110001 00000011 11100100 01111101 01110011 01010111 01111000 00100011 01010000

We can see the last 2 bytes of BE are palindromes, so they match the first 2 bytes of the JS output. The 3rd last byte of BE is the reverse of the 3rd byte of the JS output, which it should not be if the JS value was to be LE.