Skip to content

Commit

Permalink
feature: homomorphic addition circuit (#1)
Browse files Browse the repository at this point in the history
* (reduced) twisted edwards golang helpers
* final circuit and tests
  • Loading branch information
lucasmenendez authored Nov 11, 2024
1 parent 61b7a65 commit 63fe9d3
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A set of custom circuits writted in [Gnark](https://github.com/ConsenSys/gnark)
2. SMT Verifier port from [@iden3/circomlib](https://github.com/iden3/circomlib/blob/master/circuits/smt/smtverifier.circom) ([source code](./smt)).
3. Arbo (by [@arnaucube](https://github.com/arnaucube)) proof checker from [@vocdoni/arbo](https://github.com/vocdoni/vocdoni-node/tree/main/tree/arbo) ([source code](./arbo))
- This is compatible with the SMT Verifier.
4. Homomorphic Addition (using point reduction of TwistedEdwards curve to transform circom BabyJubJub points into Gnark BabyJubJub points) ([source code](./hadd)).

**SMT Verifier vs. Arbo**

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.22 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dchest/blake512 v1.0.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/glendc/go-external-ip v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/blake512 v1.0.0 h1:oDFEQFIqFSeuA34xLtXZ/rWxCXdSjirjzPhey5EUvmA=
github.com/dchest/blake512 v1.0.0/go.mod h1:FV1x7xPPLWukZlpDpWQ88rF/SFwZ5qbskrzhLMB92JI=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
Expand Down
17 changes: 17 additions & 0 deletions hadd/homomorphic_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package hadd

import (
ecc_tweds "github.com/consensys/gnark-crypto/ecc/twistededwards"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/algebra/native/twistededwards"
)

func HomomorphicAdd(api frontend.API, a, b twistededwards.Point) (twistededwards.Point, error) {
curve, err := twistededwards.NewEdCurve(api, ecc_tweds.BN254)
if err != nil {
return twistededwards.Point{}, err
}
curve.AssertIsOnCurve(a)
curve.AssertIsOnCurve(b)
return curve.Add(a, b), nil
}
146 changes: 146 additions & 0 deletions hadd/homomorphic_add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package hadd

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

"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/profile"
"github.com/consensys/gnark/std/algebra/native/twistededwards"
"github.com/consensys/gnark/test"
"github.com/iden3/go-iden3-crypto/babyjub"
tw "github.com/vocdoni/gnark-crypto-primitives/internal/twistededwards"
)

type testHomomorphicAddCircuit struct {
A1 twistededwards.Point `gnark:"a1,public"`
A2 twistededwards.Point `gnark:"a2,public"`
B1 twistededwards.Point `gnark:"b1,public"`
B2 twistededwards.Point `gnark:"b2,public"`
C1 twistededwards.Point `gnark:"c1,public"`
C2 twistededwards.Point `gnark:"c2,public"`
}

func (c *testHomomorphicAddCircuit) Define(api frontend.API) error {
// calculate and check c1
c1, err := HomomorphicAdd(api, c.A1, c.B1)
if err != nil {
return err
}
api.AssertIsEqual(c.C1.X, c1.X)
api.AssertIsEqual(c.C1.Y, c1.Y)
// calculate and check c2
c2, err := HomomorphicAdd(api, c.A2, c.B2)
if err != nil {
return err
}
api.AssertIsEqual(c.C2.X, c2.X)
api.AssertIsEqual(c.C2.Y, c2.Y)
return nil
}

func TestHomomorphicAdd(t *testing.T) {
// generate a public mocked key and a random k to encrypt first message
_, pubKey := generateKeyPair()
k1, err := randomK()
if err != nil {
t.Errorf("Error generating random k: %v\n", err)
return
}
// encrypt a simple message
msg1 := big.NewInt(3)
a1, a2 := encrypt(msg1, pubKey, k1)
// reduce the points to reduced twisted edwards form
rteA1 := tw.NewPoint(a1.X, a1.Y).FromTEtoRTE()
rteA2 := tw.NewPoint(a2.X, a2.Y).FromTEtoRTE()
// generate a second random k to encrypt a second message
k2, err := randomK()
if err != nil {
t.Errorf("Error generating random k: %v\n", err)
return
}
// encrypt a second simple message
msg2 := big.NewInt(5)
b1, b2 := encrypt(msg2, pubKey, k2)
// reduce the points to reduced twisted edwards form
rteB1 := tw.NewPoint(b1.X, b1.Y).FromTEtoRTE()
rteB2 := tw.NewPoint(b2.X, b2.Y).FromTEtoRTE()
// calculate the sum of the encrypted messages to check the homomorphic property
c1 := new(babyjub.PointProjective).Add(a1.Projective(), b1.Projective()).Affine()
c2 := new(babyjub.PointProjective).Add(a2.Projective(), b2.Projective()).Affine()
// reduce the points to reduced twisted edwards form
rteC1 := tw.NewPoint(c1.X, c1.Y).FromTEtoRTE()
rteC2 := tw.NewPoint(c2.X, c2.Y).FromTEtoRTE()
// profiling the circuit compilation
p := profile.Start()
now := time.Now()
_, _ = frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &testHomomorphicAddCircuit{})
fmt.Println("elapsed", time.Since(now))
p.Stop()
fmt.Println("constrains", p.NbConstraints())
// run the test to prove the homomorphic property
assert := test.NewAssert(t)
inputs := &testHomomorphicAddCircuit{
A1: twistededwards.Point{
X: rteA1.X,
Y: rteA1.Y,
},
A2: twistededwards.Point{
X: rteA2.X,
Y: rteA2.Y,
},
B1: twistededwards.Point{
X: rteB1.X,
Y: rteB1.Y,
},
B2: twistededwards.Point{
X: rteB2.X,
Y: rteB2.Y,
},
C1: twistededwards.Point{
X: rteC1.X,
Y: rteC1.Y,
},
C2: twistededwards.Point{
X: rteC2.X,
Y: rteC2.Y,
},
}
assert.SolvingSucceeded(&testHomomorphicAddCircuit{}, inputs, test.WithCurves(ecc.BN254), test.WithBackends(backend.GROTH16))
}

func randomK() (*big.Int, error) {
// Generate random scalar k
kBytes := make([]byte, 32)
_, err := rand.Read(kBytes)
if err != nil {
return nil, fmt.Errorf("failed to generate random k: %v", err)
}

k := new(big.Int).SetBytes(kBytes)
k.Mod(k, babyjub.SubOrder)
return k, nil
}

func generateKeyPair() (babyjub.PrivateKey, *babyjub.PublicKey) {
privkey := babyjub.NewRandPrivKey()
return privkey, privkey.Public()
}

func encrypt(message *big.Int, publicKey *babyjub.PublicKey, k *big.Int) (*babyjub.Point, *babyjub.Point) {
// c1 = [k] * G
c1 := babyjub.NewPoint().Mul(k, babyjub.B8)
// s = [k] * publicKey
s := babyjub.NewPoint().Mul(k, publicKey.Point())
// m = [message] * G
m := babyjub.NewPoint().Mul(message, babyjub.B8)
// c2 = m + s
c2p := babyjub.NewPointProjective().Add(m.Projective(), s.Projective())
return c1, c2p.Affine()
}
84 changes: 84 additions & 0 deletions internal/twistededwards/twistededwards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// twistededwards package provides helper functions to transform points (x, y)
// from TwistedEdwards to Reduced TwistedEdwards and vice versa. These functions
// are required because Gnark uses the Reduced TwistedEdwards formula while
// Iden3 uses the standard TwistedEdwards formula.
// See https://github.com/bellesmarta/baby_jubjub for more information.
package twistededwards

import (
"math/big"

"github.com/consensys/gnark-crypto/ecc/bn254/fr"
)

var scalingFactor, _ = new(big.Int).SetString("6360561867910373094066688120553762416144456282423235903351243436111059670888", 10)

type Point struct {
X, Y *big.Int
}

func NewPoint(x, y *big.Int) *Point {
return &Point{
X: x,
Y: y,
}
}

// Convert Reduced TwistedEdwards x' to TwistedEdwards:
//
// x = x'/(-f)
// y' = y
func (p *Point) FromRTEtoTE() *Point {
// Step 1: Convert scalingFactor to fr.Element (mod p)
var f fr.Element
f.SetBigInt(scalingFactor) // f = scalingFactor mod p

// Step 2: Compute negF = -f mod p
var negF fr.Element
negF.Neg(&f) // negF = -f mod p

// Step 3: Compute the inverse of negF in the field
var negFInv fr.Element
negFInv.Inverse(&negF) // negFInv = (-f)^{-1} mod p

xTE := new(fr.Element)
xTE.SetBigInt(p.X)
// Step 4: Multiply g.inner.X by negFInv to get xTE
xRTE := new(fr.Element)
xRTE.Mul(xTE, &negFInv) // xTE = g.inner.X * negFInv mod p

// Step 5: Convert xTE and g.inner.Y to *big.Int
xRTEBigInt := new(big.Int)
xRTE.BigInt(xRTEBigInt)
return &Point{
X: xRTEBigInt, // x = x' / (-f)
Y: p.Y, // y' = y
}
}

// Convert TwistedEdwards to Reduced TwistedEdwards:
//
// x' = x*(-f)
// y = y'
func (p *Point) FromTEtoRTE() *Point {
// Step 1: Convert scalingFactor to fr.Element (mod p)
var f fr.Element
f.SetBigInt(scalingFactor) // f = scalingFactor mod p

// Step 2: Compute negF = -f mod p
var negF fr.Element
negF.Neg(&f) // negF = -f mod p

// Step 4: Multiply g.inner.X by negF to get xTE
xRTE := new(fr.Element).SetBigInt(p.X)
xTE := new(fr.Element)
xTE.Mul(xRTE, &negF) // xTE = g.inner.X * -f mod p

// Step 5: Convert xTE and g.inner.Y to *big.Int
xTEBigInt := new(big.Int)
xTE.BigInt(xTEBigInt)
return &Point{
X: xTEBigInt, // x' = x * (-f)
Y: p.Y, // y = y'
}
}
22 changes: 22 additions & 0 deletions internal/twistededwards/twistededwards_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package twistededwards

import (
"math/big"
"testing"
)

func TestTE2RTETransform(t *testing.T) {
p := new(Point)
p.X, _ = new(big.Int).SetString("20284931487578954787250358776722960153090567235942462656834196519767860852891", 10)
p.Y, _ = new(big.Int).SetString("21185575020764391300398134415668786804224896114060668011215204645513129497221", 10)

expectedRTE, _ := new(big.Int).SetString("5730906301301611931737915251485454905492689746504994962065413628158661689313", 10)
pPrime := p.FromTEtoRTE()
if pPrime.X.Cmp(expectedRTE) != 0 {
t.Errorf("Expected %v, got %v", expectedRTE, pPrime.X)
}
pPrimePrime := pPrime.FromRTEtoTE()
if pPrimePrime.X.Cmp(p.X) != 0 || pPrimePrime.Y.Cmp(p.Y) != 0 {
t.Errorf("Expected %v, got %v", p, pPrimePrime)
}
}

0 comments on commit 63fe9d3

Please sign in to comment.