Consensys / gnark

gnark is a fast zk-SNARK library that offers a high-level API to design circuits. The library is open source and developed under the Apache 2.0 license
https://hackmd.io/@gnark
Apache License 2.0
1.41k stars 361 forks source link

Recursive proof of signature on `".../std/recursion/groth16".VerifyingKey` #1215

Closed aayux closed 2 months ago

aayux commented 2 months ago

Starting from the recursion/groth16 example on pkg.go.dev I am trying to build a recursive 2-chain where the outer circuit verifies a signature on the inner proof system's VerifyingKey (among other things).

type OuterCircuit[
    FR emulated.FieldParams,
    G1El algebra.G1ElementT,
    G2El algebra.G2ElementT,
    GtEl algebra.GtElementT,
] struct {
    InnerProof   rec_groth16.Proof[G1El, G2El] // rec_groth16 ".../std/recursion/groth16"
    InnerVerKey  rec_groth16.VerifyingKey[G1El, G2El, GtEl]
    InnerWitness rec_groth16.Witness[FR]
    SigVerKey    eddsa.PublicKey
    Signature    eddsa.Signature
}

I have two questions on this:

  1. How can I obtain the byte string (for each element of) vk of type rec_groth16.VerifyingKey[G1El, G2El, GtEl] to sign over in the actual signing function?
  2. More importantly, how would Define method manipulate InnerVerKey to get a message of valid type for eddsa.Verify?
readygo67 commented 2 months ago

I think you can finish the above 2 task at once, please check https://github.com/lightec-xyz/gnark/blob/feat/vkey_fp/std/recursion/plonk/verifier.go.
FingerPrint write vk's element into a mimc buffer , then sum them. this function is for recursion plonk, but I think you can adopt it for groth16.

// FingerPrint() returns the MiMc hash of the VerifyingKey. It could be used to identify a VerifyingKey
// during recursive verification.
func (vk *VerifyingKey[FR, G1El, G2El]) FingerPrint(api frontend.API) (frontend.Variable, error) {
    var ret frontend.Variable
    mimc, err := mimc.NewMiMC(api)
    if err != nil {
        return ret, err
    }

    mimc.Write(vk.BaseVerifyingKey.NbPublicVariables)
    mimc.Write(vk.CircuitVerifyingKey.Size)
    mimc.Write(vk.CircuitVerifyingKey.Generator.Limbs[:]...)

    comms := make([]kzg.Commitment[G1El], 0)
    comms = append(comms, vk.CircuitVerifyingKey.S[:]...)
    comms = append(comms, vk.CircuitVerifyingKey.Ql)
    comms = append(comms, vk.CircuitVerifyingKey.Qr)
    comms = append(comms, vk.CircuitVerifyingKey.Qm)
    comms = append(comms, vk.CircuitVerifyingKey.Qo)
    comms = append(comms, vk.CircuitVerifyingKey.Qk)
    comms = append(comms, vk.CircuitVerifyingKey.Qcp[:]...)

    for _, comm := range comms {
        el := comm.G1El
        switch r := any(&el).(type) {
        case *sw_bls12377.G1Affine:
            mimc.Write(r.X)
            mimc.Write(r.Y)
        case *sw_bls12381.G1Affine:
            mimc.Write(r.X.Limbs[:]...)
            mimc.Write(r.Y.Limbs[:]...)
        case *sw_bls24315.G1Affine:
            mimc.Write(r.X)
            mimc.Write(r.Y)
        case *sw_bw6761.G1Affine:
            mimc.Write(r.X.Limbs[:]...)
            mimc.Write(r.Y.Limbs[:]...)
        case *sw_bn254.G1Affine:
            mimc.Write(r.X.Limbs[:]...)
            mimc.Write(r.Y.Limbs[:]...)
        default:
            return ret, fmt.Errorf("unknown parametric type")
        }
    }

    mimc.Write(vk.CircuitVerifyingKey.CommitmentConstraintIndexes[:]...)

    result := mimc.Sum()

    return result, nil
}
ivokub commented 2 months ago

In principle the above approach works - you just have to keep in mind that when computing the witness that you use the same representation for hashing, i.e. you have to split the coordinates at 64-bit intervals.

We have some helper tools which do this automatically, see https://pkg.go.dev/github.com/consensys/gnark@v0.10.0/std/recursion and the corresponding test file https://github.com/Consensys/gnark/blob/master/std/recursion/wrapped_hash_test.go. This wrapper is not very efficient though - we work with binary decomposition of non-native elements to map into native field for hashing and binary decomposition is slow. We're planning to migrate to the approach where we hash the limbs directly, but this requires also changes in gnark-crypto.

ivokub commented 2 months ago

Converted to discussion as seems to be question about gnark usage, not an issue.