diff --git a/go.mod b/go.mod index f9de51b5e..a8582894b 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.16 require ( github.com/bwesterb/go-ristretto v1.2.2 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 ) diff --git a/go.sum b/go.sum index c04d92360..b086edf9f 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/ot/simot/simot_test.go b/ot/simot/simot_test.go new file mode 100644 index 000000000..32e4aa550 --- /dev/null +++ b/ot/simot/simot_test.go @@ -0,0 +1,231 @@ +// Reference: https://eprint.iacr.org/2015/267.pdf (1 out of 2 OT case) +// Sender has 2 messages m0, m1 +// Receiver receives mc based on the choice bit c + +package simot + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testSimOTCount = 100 + +func simOT(myGroup group.Group, sender *SenderSimOT, receiver *ReceiverSimOT, m0, m1 []byte, choice, index int) error { + // Initialization + A := sender.InitSender(myGroup, m0, m1, index) + + // Round 1 + // Sender sends A to receiver + B := receiver.Round1Receiver(myGroup, choice, index, A) + + // Round 2 + // Receiver sends B to sender + e0, e1 := sender.Round2Sender(B) + + // Round 3 + // Sender sends e0 e1 to receiver + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + return errDec + } + + return nil +} + +func testNegativeSimOT(t *testing.T, myGroup group.Group, choice int) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + // Initialization + A := sender.InitSender(myGroup, m0, m1, 0) + + // Round 1 + B := receiver.Round1Receiver(myGroup, choice, 0, A) + + // Round 2 + e0, e1 := sender.Round2Sender(B) + // Round 3 + + // Here we pass in the flipped choice bit, to prove the decryption will fail + // The receiver will not learn anything about m_{1-c} + errDec := receiver.Round3Receiver(e0, e1, 1-choice) + if errDec == nil { + t.Error("SimOT decryption failed", errDec) + } + + if choice == 0 { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 == 0 { + t.Error("Receiver decryption should fail") + } + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 == 0 { + t.Error("Receiver decryption should fail") + } + } else { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 == 0 { + t.Error("Receiver decryption should fail") + } + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 == 0 { + t.Error("Receiver decryption should fail") + } + } +} + +// Input: myGroup, the group we operate in +func testSimOT(t *testing.T, myGroup group.Group, choice int) { + var sender SenderSimOT + var receiver ReceiverSimOT + + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + errDec := simOT(myGroup, &sender, &receiver, m0, m1, choice, 0) + if errDec != nil { + t.Error("AES GCM Decryption failed") + } + + if choice == 0 { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 != 0 { + t.Error("Receiver decryption failed") + } + } else { + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 != 0 { + t.Error("Receiver decryption failed") + } + } +} + +func benchmarSimOT(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + for iter := 0; iter < b.N; iter++ { + errDec := simOT(myGroup, &sender, &receiver, m0, m1, iter%2, 0) + if errDec != nil { + b.Error("AES GCM Decryption failed") + } + } +} + +func benchmarkSimOTRound(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + b.Run("Sender-Initialization", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.InitSender(myGroup, m0, m1, 0) + } + }) + + A := sender.InitSender(myGroup, m0, m1, 0) + + b.Run("Receiver-Round1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + receiver.Round1Receiver(myGroup, 0, 0, A) + } + }) + + B := receiver.Round1Receiver(myGroup, 0, 0, A) + + b.Run("Sender-Round2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.Round2Sender(B) + } + }) + + e0, e1 := sender.Round2Sender(B) + + b.Run("Receiver-Round3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + b.Error("Receiver-Round3 decryption failed") + } + } + }) + + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + b.Error("Receiver-Round3 decryption failed") + } + + // Confirm + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 != 0 { + b.Error("Receiver decryption failed") + } +} + +func TestSimOT(t *testing.T) { + t.Run("SimOT", func(t *testing.T) { + for i := 0; i < testSimOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testSimOT(t, currGroup, choice) + } + }) + t.Run("SimOTNegative", func(t *testing.T) { + for i := 0; i < testSimOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testNegativeSimOT(t, currGroup, choice) + } + }) +} + +func BenchmarkSimOT(b *testing.B) { + currGroup := group.P256 + benchmarSimOT(b, currGroup) +} + +func BenchmarkSimOTRound(b *testing.B) { + currGroup := group.P256 + benchmarkSimOTRound(b, currGroup) +} diff --git a/ot/simot/simotlocal.go b/ot/simot/simotlocal.go new file mode 100644 index 000000000..fabd3de56 --- /dev/null +++ b/ot/simot/simotlocal.go @@ -0,0 +1,240 @@ +package simot + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/subtle" + "errors" + "io" + + "github.com/cloudflare/circl/group" + "golang.org/x/crypto/sha3" +) + +const keyLength = 16 + +// AES GCM encryption, we don't need to pad because our input is fixed length +// Need to use authenticated encryption to defend against tampering on ciphertext +// Input: key, plaintext message +// Output: ciphertext +func aesEncGCM(key, plaintext []byte) []byte { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + panic(err.Error()) + } + + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + panic(err) + } + + ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil) + return ciphertext +} + +// AES GCM decryption +// Input: key, ciphertext message +// Output: plaintext +func aesDecGCM(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + panic(err.Error()) + } + nonceSize := aesgcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("ciphertext too short") + } + + nonce, encryptedMessage := ciphertext[:nonceSize], ciphertext[nonceSize:] + + plaintext, err := aesgcm.Open(nil, nonce, encryptedMessage, nil) + + return plaintext, err +} + +// Initialization + +// Input: myGroup, the group we operate in +// Input: m0, m1 the 2 message of the sender +// Input: index, the index of this SimOT +// Output: A = [a]G, a the sender randomness +func (sender *SenderSimOT) InitSender(myGroup group.Group, m0, m1 []byte, index int) group.Element { + sender.a = myGroup.RandomNonZeroScalar(rand.Reader) + sender.k0 = make([]byte, keyLength) + sender.k1 = make([]byte, keyLength) + sender.m0 = m0 + sender.m1 = m1 + sender.index = index + sender.A = myGroup.NewElement() + sender.A.MulGen(sender.a) + sender.myGroup = myGroup + return sender.A.Copy() +} + +// Round 1 + +// ---- sender should send A to receiver ---- + +// Input: myGroup, the group we operate in +// Input: choice, the receiver choice bit +// Input: index, the index of this SimOT +// Input: A, from sender +// Output: B = [b]G if c == 0, B = A+[b]G if c == 1 (Implementation in constant time). b, the receiver randomness +func (receiver *ReceiverSimOT) Round1Receiver(myGroup group.Group, choice int, index int, A group.Element) group.Element { + receiver.b = myGroup.RandomNonZeroScalar(rand.Reader) + receiver.c = choice + receiver.kR = make([]byte, keyLength) + receiver.index = index + receiver.A = A + receiver.myGroup = myGroup + + bG := myGroup.NewElement() + bG.MulGen(receiver.b) + cScalar := myGroup.NewScalar() + cScalar.SetUint64(uint64(receiver.c)) + add := receiver.A.Copy() + add.Mul(receiver.A, cScalar) + receiver.B = myGroup.NewElement() + receiver.B.Add(bG, add) + + return receiver.B.Copy() +} + +// Round 2 + +// ---- receiver should send B to sender ---- + +// Input: B from the receiver +// Output: e0, e1, encryption of m0 and m1 under key k0, k1 +func (sender *SenderSimOT) Round2Sender(B group.Element) ([]byte, []byte) { + sender.B = B + + aB := sender.myGroup.NewElement() + aB.Mul(sender.B, sender.a) + maA := sender.myGroup.NewElement() + maA.Mul(sender.A, sender.a) + maA.Neg(maA) + aBaA := sender.myGroup.NewElement() + aBaA.Add(aB, maA) + + // Hash the whole transcript A|B|... + AByte, errByte := sender.A.MarshalBinary() + if errByte != nil { + panic(errByte) + } + BByte, errByte := sender.B.MarshalBinary() + if errByte != nil { + panic(errByte) + } + aBByte, errByte := aB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte0 := append(AByte, BByte...) + hashByte0 = append(hashByte0, aBByte...) + + s := sha3.NewShake128() + _, errWrite := s.Write(hashByte0) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(sender.k0) + if errRead != nil { + panic(errRead) + } + + aBaAByte, errByte := aBaA.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte1 := append(AByte, BByte...) + hashByte1 = append(hashByte1, aBaAByte...) + s = sha3.NewShake128() + _, errWrite = s.Write(hashByte1) + if errWrite != nil { + panic(errWrite) + } + _, errRead = s.Read(sender.k1) + if errRead != nil { + panic(errRead) + } + + e0 := aesEncGCM(sender.k0, sender.m0) + sender.e0 = e0 + + e1 := aesEncGCM(sender.k1, sender.m1) + sender.e1 = e1 + + return sender.e0, sender.e1 +} + +// Round 3 + +// ---- sender should send e0, e1 to receiver ---- + +// Input: e0, e1: encryption of m0 and m1 from the sender +// Input: choice, choice bit of receiver +// Choose e0 or e1 based on choice bit in constant time +func (receiver *ReceiverSimOT) Round3Receiver(e0, e1 []byte, choice int) error { + receiver.ec = make([]byte, len(e1)) + // If c == 1, copy e1 + subtle.ConstantTimeCopy(choice, receiver.ec, e1) + // If c == 0, copy e0 + subtle.ConstantTimeCopy(1-choice, receiver.ec, e0) + + AByte, errByte := receiver.A.MarshalBinary() + if errByte != nil { + panic(errByte) + } + BByte, errByte := receiver.B.MarshalBinary() + if errByte != nil { + panic(errByte) + } + bA := receiver.myGroup.NewElement() + bA.Mul(receiver.A, receiver.b) + bAByte, errByte := bA.MarshalBinary() + if errByte != nil { + panic(errByte) + } + // Hash the whole transcript so far + hashByte := append(AByte, BByte...) + hashByte = append(hashByte, bAByte...) + + s := sha3.NewShake128() + _, errWrite := s.Write(hashByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(receiver.kR) // kR, decryption key of mc + if errRead != nil { + panic(errRead) + } + mc, errDec := aesDecGCM(receiver.kR, receiver.ec) + if errDec != nil { + return errDec + } + receiver.mc = mc + return nil +} + +func (receiver *ReceiverSimOT) Returnmc() []byte { + return receiver.mc +} + +func (sender *SenderSimOT) Returne0e1() ([]byte, []byte) { + return sender.e0, sender.e1 +} + +func (sender *SenderSimOT) Returnm0m1() ([]byte, []byte) { + return sender.m0, sender.m1 +} diff --git a/ot/simot/simotparty.go b/ot/simot/simotparty.go new file mode 100644 index 000000000..d4f677130 --- /dev/null +++ b/ot/simot/simotparty.go @@ -0,0 +1,29 @@ +package simot + +import "github.com/cloudflare/circl/group" + +type SenderSimOT struct { + index int // Indicate which OT + m0 []byte // The M0 message from sender + m1 []byte // The M1 message from sender + a group.Scalar // The randomness of the sender + A group.Element // [a]G + B group.Element // The random group element from the receiver + k0 []byte // The encryption key of M0 + k1 []byte // The encryption key of M1 + e0 []byte // The encryption of M0 under k0 + e1 []byte // The encryption of M1 under k1 + myGroup group.Group // The elliptic curve we operate in +} + +type ReceiverSimOT struct { + index int // Indicate which OT + c int // The choice bit of the receiver + A group.Element // The random group element from the sender + b group.Scalar // The randomness of the receiver + B group.Element // B = [b]G if c == 0, B = A+[b]G if c == 1 + kR []byte // The decryption key of receiver + ec []byte // The encryption of mc + mc []byte // The decrypted message from sender + myGroup group.Group // The elliptic curve we operate in +} diff --git a/tss/ecdsa/dkls/fmul/fmulLocal.go b/tss/ecdsa/dkls/fmul/fmulLocal.go new file mode 100644 index 000000000..0ad7d13ae --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmulLocal.go @@ -0,0 +1,242 @@ +package fmul + +import ( + "crypto/rand" + "math/big" + "sync" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simot" + "golang.org/x/sync/errgroup" +) + +// Input: myGroup, the group we operate in +// Input: securityParameter +// Output: The number of SimOT needed +func DecideNumOT(myGroup group.Group, sp int) int { + numSimOT := int(myGroup.Params().ScalarLength*8) + sp + return numSimOT +} + +// ---- Sender Initialization ---- + +// Input: myGroup, the group we operate in +// Input: a, the sender private input +// Input: n, the total number of SimOT +// Output: Array of A=[ai]G for n SimOT +func (sender *SenderFmul) SenderInit(myGroup group.Group, a group.Scalar, n int) []group.Element { + sender.myGroup = myGroup + sender.a = a.Copy() + sender.deltas = make([]group.Scalar, n) + sender.m0s = make([][]byte, n) + sender.m1s = make([][]byte, n) + sender.simOTsenders = make([]simot.SenderSimOT, n) + + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + sender.deltas[index] = myGroup.RandomNonZeroScalar(rand.Reader) + m0iScalar := myGroup.NewScalar() + m0iScalar.Sub(sender.deltas[index], sender.a) + + m0iByte, err := m0iScalar.MarshalBinary() + if err != nil { + panic(err) + } + sender.m0s[index] = m0iByte + + m1iScalar := myGroup.NewScalar() + m1iScalar.Add(sender.deltas[index], sender.a) + + m1iByte, err := m1iScalar.MarshalBinary() + if err != nil { + panic(err) + } + sender.m1s[index] = m1iByte + + // n Sim OT Sender Initialization + var simOTSender simot.SenderSimOT + simOTSender.InitSender(myGroup, sender.m0s[index], sender.m1s[index], index) + sender.simOTsenders[index] = simOTSender + }(i) + } + fmulWait.Wait() + + sender.s1 = myGroup.NewScalar() + sender.s1.SetUint64(0) + + As := make([]group.Element, n) + for i := 0; i < n; i++ { + As[i] = sender.simOTsenders[i].A.Copy() + } + return As +} + +// ---- Round1: Sender sends As to receiver ---- + +// Receiver randomly generates n choice bits, either 0 or 1 for SimOT, either -1(Scalar) or 1(Scalar) for Fmul +// Matching 0 or 1 to -1(Scalar) or 1(Scalar) in constant time +// Input: myGroup, the group we operate in +// Input: As, the n [ai]G received from sender +// Input: b, the receiver private input +// Input: n, the total number of SimOT +// Output: Array of B = [b]G if c == 0, B = A+[b]G if c == 1 +func (receiver *ReceiverFmul) ReceiverRound1(myGroup group.Group, As []group.Element, b group.Scalar, n int) []group.Element { + receiver.myGroup = myGroup + receiver.b = b.Copy() + receiver.ts = make([]int, n) + receiver.tsScalar = make([]group.Scalar, n) + receiver.zs = make([]group.Scalar, n) + receiver.vs = make([]group.Scalar, n) + + Scalar1 := myGroup.NewScalar() + Scalar1.SetUint64(1) + Scalar1.Neg(Scalar1) + + receiver.simOTreceivers = make([]simot.ReceiverSimOT, n) + + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + currScalar := myGroup.NewScalar() + binaryBig, err := rand.Int(rand.Reader, big.NewInt(2)) + if err != nil { + panic(err) + } + receiver.ts[index] = int(binaryBig.Int64()) + currScalar.SetUint64(uint64(2 * receiver.ts[index])) + currScalar.Neg(currScalar) + receiver.tsScalar[index] = Scalar1.Copy() + receiver.tsScalar[index].Sub(receiver.tsScalar[index], currScalar) + receiver.zs[index] = myGroup.NewScalar() + receiver.simOTreceivers[index].Round1Receiver(myGroup, receiver.ts[index], index, As[index]) + }(i) + } + fmulWait.Wait() + + receiver.s2 = myGroup.NewScalar() + receiver.s2.SetUint64(0) + + Bs := make([]group.Element, n) + for i := 0; i < n; i++ { + Bs[i] = receiver.simOTreceivers[i].B.Copy() + } + return Bs +} + +// ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + +// Input: Bs, the n [bi]G or Ai+[bi]G received from receiver +// Input: n, the total number of SimOT +// Output: Array of m0s encryptions and m1s encryptions +func (sender *SenderFmul) SenderRound2(Bs []group.Element, n int) ([][]byte, [][]byte) { + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + sender.simOTsenders[index].Round2Sender(Bs[index]) + }(i) + } + fmulWait.Wait() + + e0s := make([][]byte, n) + e1s := make([][]byte, n) + for i := 0; i < n; i++ { + e0s[i], e1s[i] = sender.simOTsenders[i].Returne0e1() + } + + return e0s, e1s +} + +// ---- Round 3: Sender sends e0s, e1s to receiver ---- + +// Input: e0s, e1s, the encryptions of m0s and m1s +// Input: n, the total number of SimOT +// Ouptut: Blinding sigma and Array of v +func (receiver *ReceiverFmul) ReceiverRound3(e0s, e1s [][]byte, n int) (group.Scalar, []group.Scalar, error) { + var errGroup errgroup.Group + receiver.s2.SetUint64(0) + + for i := 0; i < n; i++ { + func(index int) { + errGroup.Go(func() error { + errDec := receiver.simOTreceivers[index].Round3Receiver(e0s[index], e1s[index], receiver.ts[index]) + if errDec != nil { + return errDec + } + mc := receiver.simOTreceivers[index].Returnmc() + errByte := receiver.zs[index].UnmarshalBinary(mc) + if errByte != nil { + panic(errByte) + } + return nil + }) + }(i) + } + + if err := errGroup.Wait(); err != nil { + return nil, nil, err + } + + // v \times t = b + vn := receiver.b.Copy() + for i := 0; i < n-1; i++ { + receiver.vs[i] = receiver.myGroup.RandomNonZeroScalar(rand.Reader) + vt := receiver.myGroup.NewScalar() + vt.Mul(receiver.tsScalar[i], receiver.vs[i]) + vn.Sub(vn, vt) + } + tsnInv := receiver.myGroup.NewScalar() + tsnInv.Inv(receiver.tsScalar[n-1]) + vn.Mul(vn, tsnInv) + receiver.vs[n-1] = vn + receiver.sigma = receiver.myGroup.RandomNonZeroScalar(rand.Reader) + + for i := 0; i < n; i++ { + vzi := receiver.myGroup.NewScalar() + vzi.Mul(receiver.vs[i], receiver.zs[i]) + receiver.s2.Add(receiver.s2, vzi) + } + + // s2 = v \times z + sigma + receiver.s2.Add(receiver.s2, receiver.sigma) + + sigma := receiver.sigma.Copy() + vs := make([]group.Scalar, n) + for i := 0; i < n; i++ { + vs[i] = receiver.vs[i].Copy() + } + + return sigma, vs, nil +} + +// ---- Round 4: receiver sends sigma as well as vs to sender ---- + +// Input: vs, from receiver +// Input: sigma, blinding from receiver +// Input: n, the total number of SimOT +func (sender *SenderFmul) SenderRound4(vs []group.Scalar, sigma group.Scalar, n int) { + sender.s1.SetUint64(0) + + vdelta := sender.myGroup.NewScalar() + + // s1 = - v \times delta - sigma + for i := 0; i < n; i++ { + vdelta.Mul(vs[i], sender.deltas[i]) + sender.s1.Sub(sender.s1, vdelta) + } + sender.s1.Sub(sender.s1, sigma) +} + +func (sender *SenderFmul) Returns1() group.Scalar { + return sender.s1 +} + +func (receiver *ReceiverFmul) Returns2() group.Scalar { + return receiver.s2 +} diff --git a/tss/ecdsa/dkls/fmul/fmulParty.go b/tss/ecdsa/dkls/fmul/fmulParty.go new file mode 100644 index 000000000..44f312e5f --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmulParty.go @@ -0,0 +1,28 @@ +package fmul + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simot" +) + +type SenderFmul struct { + a group.Scalar // The input of the sender + deltas []group.Scalar // The n random of the sender + m0s [][]byte // The n m0 messages of the sender + m1s [][]byte // The n m1 messages of the sender + simOTsenders []simot.SenderSimOT // The n senders for n simOT + s1 group.Scalar // The final additive share + myGroup group.Group // The elliptic curve we operate in +} + +type ReceiverFmul struct { + b group.Scalar // The input of the receiver + ts []int // The n choice bits of the receiver, either 0 or 1 + tsScalar []group.Scalar // The scalar version of n choice bits, either -1 or 1 + zs []group.Scalar // The n OT transferred messages from the sender + vs []group.Scalar // The n random of the receiver such that v*t = b + sigma group.Scalar // The blinding scalar + simOTreceivers []simot.ReceiverSimOT // The n receivers for n simOT + s2 group.Scalar // The final additive share + myGroup group.Group // The elliptic curve we operate in +} diff --git a/tss/ecdsa/dkls/fmul/fmul_test.go b/tss/ecdsa/dkls/fmul/fmul_test.go new file mode 100644 index 000000000..ca8ebefd6 --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmul_test.go @@ -0,0 +1,214 @@ +// Reference: https://eprint.iacr.org/2021/1373.pdf +// Sender and receiver has private input a and b +// Sender and receiver get s1 and s2 such that a*b = s1+s2 from Fmul +// This scheme based on pure OT but not OT extension + +package fmul + +import ( + "crypto/rand" + "math/big" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testFmulCount = 50 + +// Input: aInput, bInput, the private input from both sender and receiver +// Input: myGroup, the group we operate in +// Input: n, the total number of SimOT +func fmul(sender *SenderFmul, receiver *ReceiverFmul, aInput, bInput group.Scalar, myGroup group.Group, n int) error { + // Sender Initialization + As := sender.SenderInit(myGroup, aInput, n) + + // ---- Round1: Sender sends As to receiver ---- + + Bs := receiver.ReceiverRound1(myGroup, As, bInput, n) + + // ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + + e0s, e1s := sender.SenderRound2(Bs, n) + + // ---- Round 3: Sender sends e0s, e1s to receiver ---- + + sigma, vs, errDec := receiver.ReceiverRound3(e0s, e1s, n) + if errDec != nil { + return errDec + } + + // ---- Round 4: receiver sends sigma as well as vs to sender ---- + + sender.SenderRound4(vs, sigma, n) + + return nil +} + +func testFmul(t *testing.T, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + err := fmul(&sender, &receiver, aSender, bReceiver, myGroup, n) + if err != nil { + t.Error("Fmul decryption fail", err) + } + + mul := myGroup.NewScalar() + add := myGroup.NewScalar() + + add.Add(sender.s1, receiver.s2) + mul.Mul(aSender, bReceiver) + + if add.IsEqual(mul) == false { + t.Error("Fmul reconstruction failed") + } +} + +// Note the receiver has no space to cheat in the protocol. +// The only way receiver can cheat is by making up incorrect vs which is the same as entering a different private input b +// So we will only test the case where sender deviate from the protocol +// Where sender exchanges one pair of e0 and e1. +func testFmulNegative(t *testing.T, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + // Sender Initialization + As := sender.SenderInit(myGroup, aSender, n) + + // ---- Round1: Sender sends As to receiver ---- + + Bs := receiver.ReceiverRound1(myGroup, As, bReceiver, n) + + // ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + + e0s, e1s := sender.SenderRound2(Bs, n) + + // exchange one pair of e0 and e1 + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(n))) + if err != nil { + panic(err) + } + randomIndex := int(nBig.Int64()) + savee0 := make([]byte, len(e0s[randomIndex])) + copy(savee0, e0s[randomIndex]) + e0s[randomIndex] = e1s[randomIndex] + e1s[randomIndex] = savee0 + + // ---- Round 3: Sender sends e0s, e1s to receiver ---- + + _, _, err = receiver.ReceiverRound3(e0s, e1s, n) + if err == nil { + t.Error("Fmul decryption should fail", err) + } +} + +func benchmarFmul(b *testing.B, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + for iter := 0; iter < b.N; iter++ { + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + err := fmul(&sender, &receiver, aSender, bReceiver, myGroup, n) + if err != nil { + b.Error("Fmul reconstruction failed") + } + } +} + +func benchmarFmulRound(b *testing.B, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + b.Run("Sender-Initialization", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderInit(myGroup, aSender, n) + } + }) + + As := sender.SenderInit(myGroup, aSender, n) + + b.Run("Receiver-Round1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + receiver.ReceiverRound1(myGroup, As, bReceiver, n) + } + }) + + Bs := receiver.ReceiverRound1(myGroup, As, bReceiver, n) + + b.Run("Sender-Round2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderRound2(Bs, n) + } + }) + + e0s, e1s := sender.SenderRound2(Bs, n) + + b.Run("Receiver-Round3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, err := receiver.ReceiverRound3(e0s, e1s, n) + if err != nil { + b.Error("Receiver-Round3 decryption failed") + } + } + }) + + sigma, vs, err := receiver.ReceiverRound3(e0s, e1s, n) + if err != nil { + b.Error("Receiver-Round3 decryption failed") + } + + b.Run("Sender-Round4", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderRound4(vs, sigma, n) + } + }) + + sender.SenderRound4(vs, sigma, n) + + add := myGroup.NewScalar() + mul := myGroup.NewScalar() + + add.Add(sender.s1, receiver.s2) + mul.Mul(aSender, bReceiver) + + if add.IsEqual(mul) == false { + b.Error("Fmul reconstruction failed") + } +} + +func TestFmul(t *testing.T) { + t.Run("fmul", func(t *testing.T) { + for i := 0; i < testFmulCount; i++ { + currGroup := group.P256 + testFmul(t, currGroup) + } + }) + t.Run("fmulNegative", func(t *testing.T) { + for i := 0; i < testFmulCount; i++ { + currGroup := group.P256 + testFmulNegative(t, currGroup) + } + }) +} + +func BenchmarkFmul(b *testing.B) { + currGroup := group.P256 + benchmarFmul(b, currGroup) +} + +func BenchmarkFmulRound(b *testing.B) { + currGroup := group.P256 + benchmarFmulRound(b, currGroup) +} diff --git a/zk/dl/dl.go b/zk/dl/dl.go new file mode 100644 index 000000000..98ac03d9a --- /dev/null +++ b/zk/dl/dl.go @@ -0,0 +1,86 @@ +// Reference: https://datatracker.ietf.org/doc/html/rfc8235#page-6 +// Prove the knowledge of [k] given [k]G, G and the curve where the points reside +package dl + +import ( + "io" + + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: R = [kA]DB +// Input: proverLabel, verifierLabel labels of prover and verifier +// Ouptput: (V,r), the prove such that we know kA without revealing kA +func ProveGen(myGroup group.Group, DB, R group.Element, kA group.Scalar, proverLabel, verifierLabel, dst []byte, rnd io.Reader) (group.Element, group.Scalar) { + v := myGroup.RandomNonZeroScalar(rnd) + V := myGroup.NewElement() + V.Mul(DB, v) + + // Hash transcript (D_B | V | R | proverLabel | verifierLabel) to get the random coin + DBByte, errByte := DB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + VByte, errByte := V.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + RByte, errByte := R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + hashByte := append(DBByte, VByte...) + hashByte = append(hashByte, RByte...) + hashByte = append(hashByte, proverLabel...) + hashByte = append(hashByte, verifierLabel...) + + c := myGroup.HashToScalar(hashByte, dst) + + kAc := myGroup.NewScalar() + kAc.Mul(c, kA) + r := v.Copy() + r.Sub(r, kAc) + + return V, r +} + +// Input: myGroup, the group we operate in +// Input: R = [kA]DB +// Input: (V,r), the prove such that the prover knows kA +// Input: proverLabel, verifierLabel labels of prover and verifier +// Output: V ?= [r]D_B +[c]R +func Verify(myGroup group.Group, DB, R group.Element, V group.Element, r group.Scalar, proverLabel, verifierLabel, dst []byte) bool { + // Hash the transcript (D_B | V | R | proverLabel | verifierLabel) to get the random coin + DBByte, errByte := DB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + VByte, errByte := V.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + RByte, errByte := R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte := append(DBByte, VByte...) + hashByte = append(hashByte, RByte...) + hashByte = append(hashByte, proverLabel...) + hashByte = append(hashByte, verifierLabel...) + + c := myGroup.HashToScalar(hashByte, dst) + + rDB := myGroup.NewElement() + rDB.Mul(DB, r) + + cR := myGroup.NewElement() + cR.Mul(R, c) + + rDB.Add(rDB, cR) + + return V.IsEqual(rDB) +} diff --git a/zk/dl/dl_test.go b/zk/dl/dl_test.go new file mode 100644 index 000000000..aaf306e43 --- /dev/null +++ b/zk/dl/dl_test.go @@ -0,0 +1,59 @@ +package dl + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testzkDLCount = 10 + +func testzkDL(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.NewElement() + R.Mul(DB, kA) + + dst := "zeroknowledge" + rnd := rand.Reader + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier"), []byte(dst), rnd) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier"), []byte(dst)) + if verify == false { + t.Error("zkRDL verification failed") + } +} + +func testzkDLNegative(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.RandomElement(rand.Reader) + + dst := "zeroknowledge" + rnd := rand.Reader + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier"), []byte(dst), rnd) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier"), []byte(dst)) + if verify == true { + t.Error("zkRDL verification should fail") + } +} + +func TestZKDL(t *testing.T) { + t.Run("zkDL", func(t *testing.T) { + for i := 0; i < testzkDLCount; i++ { + currGroup := group.P256 + testzkDL(t, currGroup) + } + }) + + t.Run("zkDLNegative", func(t *testing.T) { + for i := 0; i < testzkDLCount; i++ { + currGroup := group.P256 + testzkDLNegative(t, currGroup) + } + }) +}