Skip to content

feat: Options for custom hash function and randomness source in MPC setup ceremonies #678

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

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions field/generator/generator_field.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func generateField(F *config.Field, outputDir, asmDirIncludePath, hashArm64, has
element.Sqrt,
element.Inverse,
element.BigNum,
element.CustomRandom,
}

// test file templates
Expand Down
36 changes: 36 additions & 0 deletions field/generator/internal/templates/element/element.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package element

const CustomRandom = `
// SetRandomWithSource sets z to a uniform random value in [0, q) using the provided random source.
//
// This might error only if reading from source errors,
// in which case, value of z is undefined.
func (z *{{.ElementName}}) SetRandomWithSource(source io.Reader, l, k int, b uint) (*{{.ElementName}}, error) {
// l is total number of bytes needed to reconstruct z
// k is the maximum byte length needed to encode a value < q
// b is the number of bits in the most significant byte of q-1

var bytes [{{mul 8 .NbWords}}]byte

for {
// note that bytes[k:l] is always 0
if _, err := io.ReadFull(source, bytes[:k]); err != nil {
return nil, err
}

// Clear unused bits in in the most significant byte to increase probability
// that the candidate is < q.
bytes[k-1] &= uint8(int(1<<b) - 1)
{{- range $i := .NbWordsIndexesFull}}
{{- $k := add $i 1}}
z[{{$i}}] = binary.LittleEndian.{{$.Word.TypeUpper}}(bytes[{{mul $i $.Word.ByteSize}}:{{mul $k $.Word.ByteSize}}])
{{- end}}

if !z.smallerThanModulus() {
continue // ignore the candidate and re-sample
}

return z, nil
}
}
`
15 changes: 15 additions & 0 deletions internal/generator/field/template/element/contents.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package element

const Contents = `
element.Base
element.Reduce
element.Exp
element.Conv
element.MulDoc
element.MulCIOS
element.MulNoCarry
element.Sqrt
element.Inverse
element.BigNum
element.CustomRandom
`
32 changes: 32 additions & 0 deletions internal/generator/field/template/element/custom_random.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SetRandomWithSource sets z to a uniform random value in [0, q) using the provided random source.
//
// This might error only if reading from source errors,
// in which case, value of z is undefined.
func (z *{{.ElementName}}) SetRandomWithSource(source io.Reader, l, k int, b uint) (*{{.ElementName}}, error) {
// l is total number of bytes needed to reconstruct z
// k is the maximum byte length needed to encode a value < q
// b is the number of bits in the most significant byte of q-1

var bytes [{{mul 8 .NbWords}}]byte
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use fieldPackage.Bytes instead of the multiplication here.


for {
// note that bytes[k:l] is always 0
if _, err := io.ReadFull(source, bytes[:k]); err != nil {
return nil, err
}

// Clear unused bits in in the most significant byte to increase probability
// that the candidate is < q.
bytes[k-1] &= uint8(int(1<<b) - 1)
{{- range $i := .NbWordsIndexesFull}}
{{- $k := add $i 1}}
z[{{$i}}] = binary.LittleEndian.{{$.Word.TypeUpper}}(bytes[{{mul $i $.Word.ByteSize}}:{{mul $k $.Word.ByteSize}}])
{{- end}}

if !z.smallerThanModulus() {
continue // ignore the candidate and re-sample
}

return z, nil
}
}
5 changes: 4 additions & 1 deletion internal/generator/mpcsetup/generate.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package mpcsetup

import (
"path/filepath"

"github.com/consensys/bavard"
"github.com/consensys/gnark-crypto/internal/generator/config"
"path/filepath"
)

func Generate(conf config.Curve, baseDir string, bgen *bavard.BatchGenerator) error {
entries := []bavard.Entry{
{File: filepath.Join(baseDir, "doc.go"), Templates: []string{"doc.go.tmpl"}},
{File: filepath.Join(baseDir, "mpcsetup.go"), Templates: []string{"mpcsetup.go.tmpl"}},
{File: filepath.Join(baseDir, "hash_interface.go"), Templates: []string{"hash_interface.go.tmpl"}},
{File: filepath.Join(baseDir, "mpcsetup_test.go"), Templates: []string{"tests/mpcsetup.go.tmpl"}},
{File: filepath.Join(baseDir, "custom_options_test.go"), Templates: []string{"tests/custom_options_test.go.tmpl"}},
}
return bgen.Generate(conf, "mpcsetup", "./mpcsetup/template", entries...)
}
14 changes: 13 additions & 1 deletion internal/generator/mpcsetup/template/doc.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
// Package mpcsetup provides tools for multiparty setup ceremonies for various protocols,
// based on the MMPORPG paper https://eprint.iacr.org/2017/1050.pdf
package mpcsetup
//
// The package supports customization of hash functions and randomness sources:
//
// To use a custom hash function:
// customHashFunc := func(msg, dst []byte) (curve.G2Affine, error) { ... }
// mpcsetup.ConfigureMPC(mpcsetup.WithHashToG2(customHashFunc))
//
// To use a custom randomness source:
// customRandomSource := func() (io.Reader, error) { ... }
// mpcsetup.ConfigureMPC(mpcsetup.WithRandomSource(customRandomSource))
//
// These options can be combined and are thread-safe.
package mpcsetup
111 changes: 111 additions & 0 deletions internal/generator/mpcsetup/template/hash_interface.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package mpcsetup

import (
"crypto/rand"
"io"
"sync"

curve "github.com/consensys/gnark-crypto/ecc/{{.Name}}"
"github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr"
)

// HashToG2Func defines a custom hash function interface for MPC setup ceremonies
type HashToG2Func func(msg, dst []byte) (curve.G2Affine, error)

// RandomSourceFunc defines a custom random source interface for generating random values
type RandomSourceFunc func() (io.Reader, error)

// MPCOptions holds configuration options for MPC operations
type MPCOptions struct {
hashToG2Func HashToG2Func
randomSourceFunc RandomSourceFunc
}

// defaultRandomSource returns crypto/rand.Reader as the default source of randomness
func defaultRandomSource() (io.Reader, error) {
return rand.Reader, nil
}

var (
// defaultHashToG2 uses the standard curve.HashToG2 function
defaultHashToG2 = curve.HashToG2

// thread-safe global option defaults
globalMPCOptions = MPCOptions{
hashToG2Func: defaultHashToG2,
randomSourceFunc: defaultRandomSource,
}

// mutex to protect global options during updates
optionsMutex sync.RWMutex
)

// MPCOption defines a function that can configure MPC options
type MPCOption func(*MPCOptions)

// WithHashToG2 configures a custom hash-to-G2 function for MPC operations
func WithHashToG2(hashFunc HashToG2Func) MPCOption {
return func(opts *MPCOptions) {
opts.hashToG2Func = hashFunc
}
}

// WithRandomSource configures a custom random source for MPC operations
func WithRandomSource(randFunc RandomSourceFunc) MPCOption {
return func(opts *MPCOptions) {
opts.randomSourceFunc = randFunc
}
}

// mpcHash implements HashToG2 using the configured hash function
func mpcHash(msg, dst []byte) (curve.G2Affine, error) {
optionsMutex.RLock()
hashFunc := globalMPCOptions.hashToG2Func
optionsMutex.RUnlock()

return hashFunc(msg, dst)
}

// getRandomSource returns the configured random source
func getRandomSource() (io.Reader, error) {
optionsMutex.RLock()
randFunc := globalMPCOptions.randomSourceFunc
optionsMutex.RUnlock()

return randFunc()
}

// ConfigureMPC applies the provided options to the global MPC configuration
func ConfigureMPC(opts ...MPCOption) {
optionsMutex.Lock()
defer optionsMutex.Unlock()

for _, opt := range opts {
opt(&globalMPCOptions)
}
}

// setElementRandom sets an fr.Element to a random value using the configured random source
func setElementRandom(e *fr.Element) (*fr.Element, error) {
reader, err := getRandomSource()
if err != nil {
return nil, err
}

// l is number of limbs * 8; the number of bytes needed to reconstruct the fr.Element
const l = {{ if eq .FrNbWords 6 }}48{{ else if eq .FrNbWords 5 }}40{{ else if eq .FrNbWords 4 }}32{{ else }}32{{ end }}

// bitLen is the maximum bit length needed to encode a value < q
const bitLen = {{ .Fr.NbBits }}

// k is the maximum byte length needed to encode a value < q
const k = (bitLen + 7) / 8

// b is the number of bits in the most significant byte of q-1
b := uint(bitLen % 8)
if b == 0 {
b = 8
}

return e.SetRandomWithSource(reader, l, k, b)
}
6 changes: 3 additions & 3 deletions internal/generator/mpcsetup/template/mpcsetup.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func pokBase(xG curve.G1Affine, challenge []byte, dst byte) curve.G2Affine {
buf.Grow(len(challenge) + curve.SizeOfG1AffineUncompressed)
buf.Write(xG.Marshal())
buf.Write(challenge)
xpG2, err := curve.HashToG2(buf.Bytes(), []byte{dst})
xpG2, err := mpcHash(buf.Bytes(), []byte{dst})
if err != nil {
panic(err)
}
Expand All @@ -42,7 +42,7 @@ func UpdateValues(contributionValue *fr.Element, challenge []byte, dst byte, rep
contributionValue = new(fr.Element)
}
if contributionValue.IsZero() {
if _, err := contributionValue.SetRandom(); err != nil {
if _, err := setElementRandom(contributionValue); err != nil {
panic(err)
}
}
Expand Down Expand Up @@ -215,7 +215,7 @@ func powersOfRandom(N int) []fr.Element {
x[0].SetOne()
}
if N > 1 {
if _, err := x[1].SetRandom(); err != nil {
if _, err := setElementRandom(&x[1]); err != nil {
panic(err)
}
}
Expand Down
Loading