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.43k stars 368 forks source link

bug: writer broken for hasher given by `recursion.NewShort`? #1249

Closed aayux closed 2 months ago

aayux commented 2 months ago

Description

I am using the recursive.NewShort method following this discussion, but I have an issue where, when I try to H.Write just one extra value, the constraint check fails with a segmentation violation.

I provide a minimal reproducible example below to explain what I mean. This toy example verifies a signature on a hash over a concatenated string, where part of the secret hash input is checked against a public value.

Circuit definition ```sighash_circuit.go``` ```golang package main import ( "fmt" "github.com/consensys/gnark-crypto/ecc" gc_tedwards "github.com/consensys/gnark-crypto/ecc/twistededwards" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/algebra/native/twistededwards" "github.com/consensys/gnark/std/hash/mimc" "github.com/consensys/gnark/std/recursion" "github.com/consensys/gnark/std/signature/eddsa" ) type HashCircuit struct { VerKey eddsa.PublicKey Signature eddsa.Signature Pad frontend.Variable `gnark:",public"` CurTime frontend.Variable `gnark:",public"` ExpTime frontend.Variable } func (c *HashCircuit) Define(api frontend.API) error { api.AssertIsLessOrEqual(c.CurTime, c.ExpTime) H, err := recursion.NewHash(api, ecc.BW6_761.ScalarField(), true) // mimc.NewMiMC(api) if err != nil { return fmt.Errorf("new hash: %w", err) } H.Write(c.ExpTime) H.Write(c.Pad) M := H.Sum() // construct the message signed curve, err := twistededwards.NewEdCurve(api, gc_tedwards.BW6_761) if err != nil { return err } mimc, err := mimc.NewMiMC(api) if err != nil { return err } // verify the signature in the cs return eddsa.Verify(curve, c.Signature, M, c.VerKey, &mimc) } ```
Driver code ```sighash.go``` ```golang package main import ( crand "crypto/rand" "encoding/binary" "fmt" "time" "github.com/consensys/gnark-crypto/ecc" gc_tedwards "github.com/consensys/gnark-crypto/ecc/twistededwards" "github.com/consensys/gnark-crypto/hash" "github.com/consensys/gnark-crypto/signature" gc_eddsa "github.com/consensys/gnark-crypto/signature/eddsa" "github.com/consensys/gnark/backend/groth16" "github.com/consensys/gnark/backend/witness" "github.com/consensys/gnark/constraint" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/std/recursion" ) const UnixTimeYear = 31536000 type WitnessStruct struct { VerKey signature.PublicKey Signature []byte Pad []byte CurTime []byte ExpTime []byte } func KeyGen_BW6_761() (signature.Signer, signature.PublicKey) { // create a eddsa key pair sigsk, _ := gc_eddsa.New(gc_tedwards.BW6_761, crand.Reader) sigvk := sigsk.Public() return sigsk, sigvk } func Sign_BW6_761(sigsk signature.Signer, sigvk signature.PublicKey, M []byte) []byte { // instantiate hash function H := hash.MIMC_BW6_761.New() // sign the M σ, _ := sigsk.Sign(M, H) b, _ := sigvk.Verify(σ, M, H) if !b { return nil } return σ } func Setup() (constraint.ConstraintSystem, groth16.ProvingKey, groth16.VerifyingKey) { // compile the signature verification circuit var circuit HashCircuit R, _ := frontend.Compile(ecc.BW6_761.ScalarField(), r1cs.NewBuilder, &circuit) // generating zkpk, zkvk zkpk, zkvk, _ := groth16.Setup(R) return R, zkpk, zkvk } func Prove(R constraint.ConstraintSystem, zkpk groth16.ProvingKey, witStruct WitnessStruct) (witness.Witness, groth16.Proof) { // declare the witness var circAssignment HashCircuit // signature verification key bytes sigvkBytes := witStruct.VerKey.Bytes() // assign signature verification key values circAssignment.VerKey.Assign(gc_tedwards.BW6_761, sigvkBytes[:48]) // assign signature values circAssignment.Signature.Assign(gc_tedwards.BW6_761, witStruct.Signature) // assign other values circAssignment.Pad = witStruct.Pad circAssignment.CurTime = witStruct.CurTime circAssignment.ExpTime = witStruct.ExpTime // full witness (instance + witness) W, _ := frontend.NewWitness(&circAssignment, ecc.BW6_761.ScalarField()) // generate the proof π, _ := groth16.Prove(R, zkpk, W) x, _ := W.Public() return x, π } func Verify(zkvk groth16.VerifyingKey, x witness.Witness, π groth16.Proof) error { // verify the proof return groth16.Verify(π, zkvk, x) } func main() { // set the inner proof expiration time curTime := time.Now() curUnixTime := curTime.Unix() curUnixTimeBytes := make([]byte, 8) binary.BigEndian.PutUint64(curUnixTimeBytes, uint64(curUnixTime)) expUnixTime := curUnixTime + UnixTimeYear expUnixTimeBytes := make([]byte, 8) binary.BigEndian.PutUint64(expUnixTimeBytes, uint64(expUnixTime)) // digest and sign H, _ := recursion.NewShort(ecc.BW6_761.ScalarField(), ecc.BW6_761.ScalarField()) // works when: hash.MIMC_BW6_761.New() H.Write(expUnixTimeBytes) pad := []byte("") H.Write(pad) M := H.Sum(nil) // the message to sign // generate signing keys // to sign the inner verification key fingerprint sigsk, sigvk := KeyGen_BW6_761() σ := Sign_BW6_761(sigsk, sigvk, M) // Prove R, pk, vk := Setup() // initialise the instance and witness pair var witStruct WitnessStruct witStruct.VerKey = sigvk witStruct.Signature = σ witStruct.Pad = pad witStruct.CurTime = curUnixTimeBytes witStruct.ExpTime = expUnixTimeBytes // run the outer NIZK prover x, π := Prove(R, pk, witStruct) err := Verify(vk, x, π) if err != nil { fmt.Println("Could not verify (x, π).") } else { fmt.Println("Verified (x, π).") } } ```

Expected Behavior

As I note in the comment near the "digest and sign" procedure in main(), the code above works when the hasher is instantiated using the standard MiMC instance (and similarly in the circuit). This is the behaviour I would expect.

Actual Behavior

I realise that in this toy example, I do not really need to use recursion.NewShort, but in my actual application, I do need it and the errors are identitcal. The output on running the code above is also provided:

❯ go run .
12:39:07 INF compiling circuit
12:39:07 INF parsed circuit inputs nbPublic=2 nbSecret=6
12:39:07 INF building constraint builder nbConstraints=14407
12:39:09 ERR error="constraint #2777 is not satisfied: 259583468606 ⋅ 258664426012969094010652733694893533536393512754914660539884262666720468348340822774968888139573360124180737989572 != 0" nbConstraints=14407
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1025f81fc]

goroutine 1 [running]:
github.com/consensys/gnark/backend/groth16/bw6-761.(*Proof).isValid(0x0)
        /Users/aayux/go/pkg/mod/github.com/consensys/gnark@v0.10.0/backend/groth16/bw6-761/prove.go:54 +0x1c
github.com/consensys/gnark/backend/groth16/bw6-761.Verify(0x0, 0x140002ba008, {0x140002b0240, 0x2, 0x2}, {0x0?, 0x1400000e0f0?, 0x14000112000?})
        /Users/aayux/go/pkg/mod/github.com/consensys/gnark@v0.10.0/backend/groth16/bw6-761/verify.go:60 +0x514
github.com/consensys/gnark/backend/groth16.Verify({0x1027865b0?, 0x0}, {0x1027898e0, 0x140002ba008}, {0x102789678?, 0x1400000e168?}, {0x0, 0x0, 0x0})
        /Users/aayux/go/pkg/mod/github.com/consensys/gnark@v0.10.0/backend/groth16/groth16.go:139 +0x414
main.Verify(...)
        /Users/aayux/Documents/software/private-authenticator/examples/alt_sighash/sighash.go:95
main.main()
        /Users/aayux/Documents/software/private-authenticator/examples/alt_sighash/sighash.go:136 +0x264
exit status 2

Possible Fix

Unclear. I do not understand the source of this issue.

Steps to Reproduce

  1. Simply go run . the provided code.

Context

Write a second value to the hash:

H, _ := recursion.NewShort(ecc.BW6_761.ScalarField(), ecc.BW6_761.ScalarField()) // works when: hash.MIMC_BW6_761.New()
H.Write(expUnixTimeBytes)
pad := []byte("<pad>")
H.Write(pad)
M := H.Sum(nil) // the message to sign

and verify in the circuit

H, err := recursion.NewHash(api, ecc.BW6_761.ScalarField(), true) // mimc.NewMiMC(api)
if err != nil {
    return fmt.Errorf("new hash: %w", err)
}

H.Write(c.ExpTime)
H.Write(c.Pad)
M := H.Sum() // construct the message signed

Your Environment

aayux commented 2 months ago

Closing because I now believe that this does not identify an actual issue, and the error above is just from incorrect usage. I explain my requirements more clearly in discussion #1250.

In short: I want to write some marshalled G1 elements (Groth16 verification key elements) as well as a byte string into a hash, and the wrapped hashes inside recursion don't seem to be able to handle that.