Cypher-Laboratory / OpenOasis

EVM verifier for Alice's Ring ring signature.
3 stars 2 forks source link

[Scroll - OpenOasis] Implement a ring signature verifier from TypeScript #3

Closed Elli610 closed 11 months ago

Elli610 commented 1 year ago

Write a Solidity function for the efficient on-chain verification of a Ring Signature.

Description

To mint a token which contains the solvency proof, the contract has to verify the Ring Signature. The description of what is expected is available here in the 'How to verify a ring signature' section

Skills needed

Hours needed

Expected working time: 4 or 6 hours

What is expected

In-depth technical explanation

A ring signature is: (c, r1, ..., rn)

Ring: all the public keys involved in the signing process n: ringSize - 1 Ki (0<i<n) (secp256k1 point) -> Ki = ring[i] = public key of signer i c (uint) -> the initial cee to start the verification from r0, ..., rn (uint) -> all the responses generated during the signing process (1 for each member of the ring) m (uint) -> the clear message

N: SECP256K1 order G: SECP256K1 generator point H: hashing function

Be careful, these are points. They do not have the same properties as integers Those functions are implemented here

The process to verify a ring signature

compute c1' = H(Ring, m, [r0*G + c*Ki]

For i in [1, n], compute: ci+1' = H(Ring, m, [ri*G + ci*Ki]

If c == H(Ring, m, [rn*G + cn*Kn], the signature is considered valid.

The TypeScript implementation


/*
G is a global constant: the generator point of SECP256K1
N is a global constant: the order of SECP256K1

.mult() is the multiplication of a point by a scalar (ex: G.mult(2n) = 2G)
.add() is the addition of two points (ex: G.add(G) = 2G)
*/

/**
   * Verify a RingSignature
   *
   * @param c - The message digest (= keccack256(clear message))
   * @param responses - The responses of the signers
   * @param ring - the ring of public keys
   * @param messageDigest - the hashed message
   * 
   * @returns True if the signature is valid, false otherwise
   */
function verify(c: bigint, responses: bigint[], ring: Secp256k1Point[], messageDigest: string): boolean {
  // we compute c1' = Hash(Ring, m, [r0G, c0K0])
  // then, for each ci (1 < i < n), compute ci' = Hash(Ring, message, [riG + ciKi])
  // (G = generator, K = ring public key)
  // finally, if we substitute lastC for lastC' and c0' == c0, the signature is valid
  if (ring.length === 0)
    throw new Error("Ring length must be greater than 0");

  if (ring.length !== responses.length) {
    throw new Error("ring and responses length mismatch");
  }

  if (ring.length === 1) throw new Error("Ring length must be greater than 1");

  // computes the cees
  let lastComputedCp = computeC(
    // c1'
    ring,
    messageDigest,
    G,
    N,
    responses[0],
    c,
    ring[0],
  );

  for (let i = 2; i < ring.length; i++) {
    // c2' -> cn'
    lastComputedCp = computeC(
      ring,
      messageDigest,
      G,
      N,
      responses[i - 1],
      lastComputedCp,
      ring[i - 1],
    );
  }

  // return true if c0 === c0'
  return (
    c ===
    computeC(
      ring,
      messageDigest,
      G,
      N,
      responses[responses.length - 1],
      lastComputedCp,
      ring[ring.length - 1],
    )
  );

}

function computeC(
  ring: Secp256k1Point[],
  message: string,
  G: Secp256k1Point,
  N: bigint,
  r: bigint,
  previousC: bigint,
  previousPubKey: Secp256k1Point,
): bigint {
  return modulo(
    BigInt(
      "0x" +
      keccak256(
        ring +
        message +
        G.mult(r).add(previousPubKey.mult(previousC)).toString(),
      ),
    ),
    N,
  );
}

export function modulo(n: bigint, p: bigint): bigint {
  const result = n % p;
  return result >= 0n ? result : result + p;
}

Here are two valid signatures to test the correctness of your implementation:

RingSignature {
  ring: [
    Point {

      x: 1274257144103255806972520868047049917801510458021039105859297476043033722598,
      y: 112743011644400363828396313077237312579697497741868742919803712880766076210426
    },
    Point {

      x: 36374258490126118609549712375482183197022594483766461470463873647596626301245,
      y: 20755993649323011104186872008428753594325520130284798450866607646494255124920
    },
    Point {

      x: 73972793577741382069207315942940483295399552194619206847781186062057454754823,
      y: 30949771869151713657932523234832019023027978544149552249443242678143567532717
    }
  ],
  message: 'test',
  c: 10236465106682650416484783756499729757647228123681163456968850142910258490778,
  responses: [
    113269311624735622262536955619548064208090516933002057842715628791238739086666,
    84743363108455212614987599968728463335301986988465605711977875419528968386445,
    22284797903673122402951113834043906732664235704662121688013808759734156906050
  ],
  curve: Curve {
    name: 'SECP256K1',
    G: [
      55066263022277343669578718895168534326250603453777594175500187360389116729240,
      32670510020758816978083085130507043184471273380659243275938904335757337482424
    ],
    N: 115792089237316195423570985008687907852837564279074904382605163141518161494337,
    P: 115792089237316195423570985008687907853269984665640564039457584007908834671663
  }
}
LeJamon commented 11 months ago

Done during the hackathon