Skip to content

Commit

Permalink
Add modProof in ECDSA-keygen
Browse files Browse the repository at this point in the history
  • Loading branch information
yycen committed Aug 11, 2023
1 parent 3d95e54 commit 4a3428b
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 10 deletions.
15 changes: 13 additions & 2 deletions common/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,22 @@ func IsNumberInMultiplicativeGroup(n, v *big.Int) bool {
gcd.GCD(nil, nil, v, n).Cmp(one) == 0
}

// Return a random generator of RQn with high probability.
// THIS METHOD ONLY WORKS IF N IS THE PRODUCT OF TWO SAFE PRIMES!
// Return a random generator of RQn with high probability.
// THIS METHOD ONLY WORKS IF N IS THE PRODUCT OF TWO SAFE PRIMES!
//
// https://github.com/didiercrunch/paillier/blob/d03e8850a8e4c53d04e8016a2ce8762af3278b71/utils.go#L39
func GetRandomGeneratorOfTheQuadraticResidue(n *big.Int) *big.Int {
f := GetRandomPositiveRelativelyPrimeInt(n)
fSq := new(big.Int).Mul(f, f)
return fSq.Mod(fSq, n)
}

// GetRandomQuadraticNonResidue returns a quadratic non residue of odd n.
func GetRandomQuadraticNonResidue(n *big.Int) *big.Int {
for {
w := GetRandomPositiveInt(n)
if big.Jacobi(w, n) == -1 {
return w
}
}
}
241 changes: 241 additions & 0 deletions crypto/modproof/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// Copyright © 2019-2023 Binance
//
// This file is part of Binance. The full Binance copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package modproof

import (
"fmt"
"math/big"

"github.com/bnb-chain/tss-lib/common"
)

const (
Iterations = 80
ProofModBytesParts = Iterations*2 + 3
)

var (
one = big.NewInt(1)
)

type (
ProofMod struct {
W *big.Int
X [Iterations]*big.Int
A *big.Int
B *big.Int
Z [Iterations]*big.Int
}
)

// isQuadraticResidue checks Euler criterion
func isQuadraticResidue(X, N *big.Int) bool {
return big.Jacobi(X, N) == 1
}

func NewProof(N, P, Q *big.Int) (*ProofMod, error) {
Phi := new(big.Int).Mul(new(big.Int).Sub(P, one), new(big.Int).Sub(Q, one))
// Fig 16.1
W := common.GetRandomQuadraticNonResidue(N)

// Fig 16.2
Y := [Iterations]*big.Int{}
for i := range Y {
ei := common.SHA512_256i(append([]*big.Int{W, N}, Y[:i]...)...)
Y[i] = common.RejectionSample(N, ei)
}

// Fig 16.3
modN, modPhi := common.ModInt(N), common.ModInt(Phi)
invN := new(big.Int).ModInverse(N, Phi)
X := [Iterations]*big.Int{}
var Abz, Bbz []byte
Abz = append(Abz, byte(255))
Bbz = append(Bbz, byte(255))
Z := [Iterations]*big.Int{}

// for fourth-root
expo := new(big.Int).Add(Phi, big.NewInt(4))
expo = new(big.Int).Rsh(expo, 3)
expo = modPhi.Mul(expo, expo)

for i := range Y {
for j := 0; j < 4; j++ {
a, b := j&1, j&2>>1
Yi := new(big.Int).SetBytes(Y[i].Bytes())
if a > 0 {
Yi = modN.Mul(big.NewInt(-1), Yi)
}
if b > 0 {
Yi = modN.Mul(W, Yi)
}
if isQuadraticResidue(Yi, P) && isQuadraticResidue(Yi, Q) {
Xi := modN.Exp(Yi, expo)
Zi := modN.Exp(Y[i], invN)
X[i], Z[i] = Xi, Zi
Abz = append(Abz, byte(a))
Bbz = append(Bbz, byte(b))
break
}
}
}
A := new(big.Int).SetBytes(Abz)
B := new(big.Int).SetBytes(Bbz)

pf := &ProofMod{W: W, X: X, A: A, B: B, Z: Z}
return pf, nil
}

func NewProofFromBytes(bzs [][]byte) (*ProofMod, error) {
if !common.NonEmptyMultiBytes(bzs, ProofModBytesParts) {
return nil, fmt.Errorf("expected %d byte parts to construct ProofMod", ProofModBytesParts)
}
bis := make([]*big.Int, len(bzs))
for i := range bis {
bis[i] = new(big.Int).SetBytes(bzs[i])
}

X := [Iterations]*big.Int{}
copy(X[:], bis[1:(Iterations+1)])

Z := [Iterations]*big.Int{}
copy(Z[:], bis[(Iterations+3):])

return &ProofMod{
W: bis[0],
X: X,
A: bis[Iterations+1],
B: bis[Iterations+2],
Z: Z,
}, nil
}

func (pf *ProofMod) Verify(N *big.Int) bool {
if pf == nil || !pf.ValidateBasic() {
return false
}
// TODO: add basic properties checker
if isQuadraticResidue(pf.W, N) {
return false
}
if pf.W.Sign() != 1 || pf.W.Cmp(N) != -1 {
return false
}
for i := range pf.Z {
if pf.Z[i].Sign() != 1 || pf.Z[i].Cmp(N) != -1 {
return false
}
}
for i := range pf.X {
if pf.X[i].Sign() != 1 || pf.X[i].Cmp(N) != -1 {
return false
}
}
if len(pf.A.Bytes()) != Iterations+1 {
return false
}
if len(pf.B.Bytes()) != Iterations+1 {
return false
}

modN := common.ModInt(N)
Y := [Iterations]*big.Int{}
for i := range Y {
ei := common.SHA512_256i(append([]*big.Int{pf.W, N}, Y[:i]...)...)
Y[i] = common.RejectionSample(N, ei)
}

// Fig 16. Verification
{
if N.Bit(0) == 0 || N.ProbablyPrime(30) {
return false
}
}

chs := make(chan bool, Iterations*2)
for i := 0; i < Iterations; i++ {
go func(i int) {
left := modN.Exp(pf.Z[i], N)
if left.Cmp(Y[i]) != 0 {
chs <- false
return
}
chs <- true
}(i)

go func(i int) {
a := int(pf.A.Bytes()[i+1])
b := int(pf.B.Bytes()[i+1])
if a != 0 && a != 1 {
chs <- false
return
}
if b != 0 && b != 1 {
chs <- false
return
}
left := modN.Exp(pf.X[i], big.NewInt(4))
right := Y[i]
if a > 0 {
right = modN.Mul(big.NewInt(-1), right)
}
if b > 0 {
right = modN.Mul(pf.W, right)
}
if left.Cmp(right) != 0 {
chs <- false
return
}
chs <- true
}(i)
}

for i := 0; i < Iterations*2; i++ {
if !<-chs {
return false
}
}

return true
}

func (pf *ProofMod) ValidateBasic() bool {
if pf.W == nil {
return false
}
for i := range pf.X {
if pf.X[i] == nil {
return false
}
}
if pf.A == nil {
return false
}
if pf.B == nil {
return false
}
for i := range pf.Z {
if pf.Z[i] == nil {
return false
}
}
return true
}

func (pf *ProofMod) Bytes() [ProofModBytesParts][]byte {
bzs := [ProofModBytesParts][]byte{}
bzs[0] = pf.W.Bytes()
for i := range pf.X {
bzs[1+i] = pf.X[i].Bytes()
}
bzs[Iterations+1] = pf.A.Bytes()
bzs[Iterations+2] = pf.B.Bytes()
for i := range pf.Z {
bzs[Iterations+3+i] = pf.Z[i].Bytes()
}
return bzs
}
33 changes: 33 additions & 0 deletions crypto/modproof/proof_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright © 2019-2023 Binance
//
// This file is part of Binance. The full Binance copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

package modproof_test

import (
"testing"
"time"

. "github.com/bnb-chain/tss-lib/crypto/modproof"
"github.com/bnb-chain/tss-lib/ecdsa/keygen"
"github.com/stretchr/testify/assert"
)

func TestMod(test *testing.T) {
preParams, err := keygen.GeneratePreParams(time.Minute*10, 8)
assert.NoError(test, err)

P, Q, N := preParams.PaillierSK.P, preParams.PaillierSK.Q, preParams.PaillierSK.N

proof, err := NewProof(N, P, Q)
assert.NoError(test, err)

proofBzs := proof.Bytes()
proof, err = NewProofFromBytes(proofBzs[:])
assert.NoError(test, err)

ok := proof.Verify(N)
assert.True(test, ok, "proof must verify")
}
23 changes: 16 additions & 7 deletions ecdsa/keygen/ecdsa-keygen.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions ecdsa/keygen/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package keygen

import (
"github.com/bnb-chain/tss-lib/crypto/facproof"
"github.com/bnb-chain/tss-lib/crypto/modproof"
"math/big"

"github.com/bnb-chain/tss-lib/common"
Expand Down Expand Up @@ -146,14 +147,17 @@ func (m *KGRound2Message1) UnmarshalFacProof() (*facproof.ProofFac, error) {
func NewKGRound2Message2(
from *tss.PartyID,
deCommitment cmt.HashDeCommitment,
proof *modproof.ProofMod,
) tss.ParsedMessage {
meta := tss.MessageRouting{
From: from,
IsBroadcast: true,
}
dcBzs := common.BigIntsToBytes(deCommitment)
proofBzs := proof.Bytes()
content := &KGRound2Message2{
DeCommitment: dcBzs,
ModProof: proofBzs[:],
}
msg := tss.NewMessageWrapper(meta, content)
return tss.NewMessage(meta, content, msg)
Expand All @@ -162,13 +166,19 @@ func NewKGRound2Message2(
func (m *KGRound2Message2) ValidateBasic() bool {
return m != nil &&
common.NonEmptyMultiBytes(m.GetDeCommitment())
// This is commented for backward compatibility, which msg has no proof
// && common.NonEmptyMultiBytes(m.GetModProof(), modproof.ProofModBytesParts)
}

func (m *KGRound2Message2) UnmarshalDeCommitment() []*big.Int {
deComBzs := m.GetDeCommitment()
return cmt.NewHashDeCommitmentFromBytes(deComBzs)
}

func (m *KGRound2Message2) UnmarshalModProof() (*modproof.ProofMod, error) {
return modproof.NewProofFromBytes(m.GetModProof())
}

// ----- //

func NewKGRound3Message(
Expand Down
Loading

0 comments on commit 4a3428b

Please sign in to comment.