Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat add random mask to groth16 commitment #1245

Merged
merged 7 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion constraint/solver/hint_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package solver

import (
"fmt"
"github.com/consensys/gnark/internal/hints"
"math/big"
"sync"

"github.com/consensys/gnark/logger"
)

func init() {
RegisterHint(InvZeroHint)
RegisterHint(InvZeroHint, hints.Randomize)
}

var (
Expand Down
14 changes: 14 additions & 0 deletions frontend/cs/r1cs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package r1cs
import (
"errors"
"fmt"
"github.com/consensys/gnark/internal/hints"
"path/filepath"
"reflect"
"runtime"
Expand Down Expand Up @@ -687,6 +688,19 @@ func (builder *builder) Compiler() frontend.Compiler {

func (builder *builder) Commit(v ...frontend.Variable) (frontend.Variable, error) {

// add a random mask to v
{
ivokub marked this conversation as resolved.
Show resolved Hide resolved
vCp := make([]frontend.Variable, len(v)+1)
copy(vCp, v)
mask, err := builder.NewHint(hints.Randomize, 1)
if err != nil {
return nil, err
}
vCp[len(v)] = mask[0]
builder.cs.AddR1C(builder.newR1C(mask[0], builder.eOne, mask[0]), builder.genericGate) // the variable needs to be involved in a constraint otherwise it will not affect the commitment
v = vCp
}

commitments := builder.cs.GetCommitments().(constraint.Groth16Commitments)
existingCommitmentIndexes := commitments.CommitmentIndexes()
privateCommittedSeeker := utils.MultiListSeeker(commitments.GetPrivateCommitted())
Expand Down
20 changes: 20 additions & 0 deletions internal/hints/hints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hints

import (
"crypto/rand"
"errors"
"math/big"
)

func Randomize(mod *big.Int, ins, outs []*big.Int) error {
if len(ins) != 0 {
return errors.New("randomize takes no input")
}
var err error
for i := range outs {
if outs[i], err = rand.Int(rand.Reader, mod); err != nil {
return err
}
}
return nil
}
89 changes: 89 additions & 0 deletions internal/security_tests/advisory-9xcg/advisory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Package advisory9xcg implements a test for advisory GHSA-9xcg-3q8v-7fq6.
package advisory9xcg

import (
"crypto/rand"
"fmt"
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/consensys/gnark/backend/groth16"
groth16_bn254 "github.com/consensys/gnark/backend/groth16/bn254"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/test"
)

type Circuit struct {
SecretWitness frontend.Variable `gnark:",private"`
}

func (circuit *Circuit) Define(api frontend.API) error {
// the goal of the test is to show that we are able to predict the private
// input solely from the stored commitment.
commitCompiler, ok := api.Compiler().(frontend.Committer)
if !ok {
return fmt.Errorf("compiler does not commit")
}

commit, err := commitCompiler.Commit(circuit.SecretWitness)
if err != nil {
return err
}

api.AssertIsDifferent(commit, 0)
api.AssertIsDifferent(circuit.SecretWitness, 0)
return nil
}

func TestAdvisory_ghsa_9xcg_3q8v_7fq6(t *testing.T) {
assert := test.NewAssert(t)
// the goal of the test is to show that we are able to predict the private
// input solely from the stored commitment

// Generating a random secret witness.
var bound int64 = 1024 // ten bits of entropy for testing
secretWitness, err := rand.Int(rand.Reader, big.NewInt(bound))
assert.NoError(err, "random generation failed")
assert.Log("random secret witness: ", secretWitness)

// Assigning some values.
assignment := Circuit{SecretWitness: secretWitness}
witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField())
assert.NoError(err, "witness creation failed")
witnessPublic, err := witness.Public()
assert.NoError(err, "witness public failed")

// Setup circuit
ccs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &Circuit{})
assert.NoError(err, "compilation failed")

// run the setup and prover
pk, vk, err := groth16.Setup(ccs)
assert.NoError(err, "setup failed")
proof, err := groth16.Prove(ccs, pk, witness)
assert.NoError(err, "proof failed")

// sanity check, check that the proof verifies
err = groth16.Verify(proof, vk, witnessPublic)
assert.NoError(err, "verification failed")

// we're ready to set up the attack. For that first we need to assert the
// exact types for being able to extract the proving key information.
pkConcrete, ok := pk.(*groth16_bn254.ProvingKey)
assert.True(ok, "unexpected type for proving key")
proofConcrete, ok := proof.(*groth16_bn254.Proof)
assert.True(ok, "unexpected type for proof")

var guessedCommitment bn254.G1Affine
for i := int64(0); i < bound; i++ {
// We check our guess for the secret witness.
guessedCommitment.ScalarMultiplication(&pkConcrete.CommitmentKeys[0].Basis[0], big.NewInt(int64(i)))
if guessedCommitment.Equal(&proofConcrete.Commitments[0]) {
assert.Fail("secret witness found: ", i)
return
}
}
}
Binary file modified internal/stats/latest.stats
Binary file not shown.
9 changes: 5 additions & 4 deletions std/multicommit/nativecommit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"testing"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/test"
Expand Down Expand Up @@ -52,7 +51,7 @@ func TestMultipleCommitments(t *testing.T) {
circuit := multipleCommitmentCircuit{}
assignment := multipleCommitmentCircuit{X: 10}
assert := test.NewAssert(t)
assert.ProverSucceeded(&circuit, &assignment, test.WithCurves(ecc.BN254), test.WithBackends(backend.GROTH16)) // right now PLONK doesn't implement commitment
assert.ProverSucceeded(&circuit, &assignment, test.WithCurves(ecc.BN254)) // right now PLONK doesn't implement commitment
}

type noCommitVariable struct {
Expand All @@ -64,9 +63,11 @@ func (c *noCommitVariable) Define(api frontend.API) error {
return nil
}

// TestNoCommitVariable checks that a circuit that doesn't use the commitment variable
// compiles and prover succeeds. This is due to the randomization of the commitment.
func TestNoCommitVariable(t *testing.T) {
circuit := noCommitVariable{}
assignment := noCommitVariable{X: 10}
assert := test.NewAssert(t)
_, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
assert.Error(err)
assert.ProverSucceeded(&circuit, &assignment, test.WithCurves(ecc.BN254))
}
Loading