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/ecdsaDKLS.go b/tss/ecdsa/dkls/ecdsaDKLS.go new file mode 100644 index 000000000..3a923fddb --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLS.go @@ -0,0 +1,73 @@ +// Reference: https://eprint.iacr.org/2018/499.pdf +// 2 out of 2 party threhsold signature scheme +// Figure 1 and Protocol 1 and 2 + +package dkls + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "math/big" + + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: sk, the real secret key +// Output: share1, share2 the multiplicative secret key shares for 2 parties. +func KeyShareGen(myGroup group.Group, sk group.Scalar) (group.Scalar, group.Scalar) { + share1 := myGroup.RandomNonZeroScalar(rand.Reader) + share1Inv := myGroup.NewScalar() + share1Inv.Inv(share1) + + share2 := myGroup.NewScalar() + share2.Mul(share1Inv, sk) + + return share1, share2 +} + +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.Params().N.BitLen() + orderBytes := (orderBits + 7) / 8 + + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +// ECDSA threshold signature verification +// Input: (r,s), the signature +// Input: hashMSG, the message +// Input: publicKey, the ECDSA public key +// Output: verification passed or not +func Verify(r, s group.Scalar, hashMSG []byte, publicKey *ecdsa.PublicKey) error { + rBig := new(big.Int) + sBig := new(big.Int) + + rByte, errByte := r.MarshalBinary() + if errByte != nil { + panic(errByte) + } + rBig.SetBytes(rByte) + + sByte, errByte := s.MarshalBinary() + if errByte != nil { + panic(errByte) + } + sBig.SetBytes(sByte) + + verify := ecdsa.Verify(publicKey, hashMSG, rBig, sBig) + if !verify { + return errors.New("ECDSA threshold verification failed") + } + return nil +} diff --git a/tss/ecdsa/dkls/ecdsaDKLSParty.go b/tss/ecdsa/dkls/ecdsaDKLSParty.go new file mode 100644 index 000000000..600fe183e --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLSParty.go @@ -0,0 +1,68 @@ +package dkls + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/tss/ecdsa/dkls/fmul" +) + +// The sender of Fmul +type AlicePre struct { + label []byte + kAPrime group.Scalar + RPrime group.Element // R' = [kA']G + kA group.Scalar // kA = H(R') + kA' + kAInv group.Scalar // 1/kA + DB group.Element // From bob + R group.Element // R = [kA]DB + Rx group.Scalar // x coordinate of point [kA][kB]G + + a group.Scalar // A random blinding for beaver's triple + ta group.Scalar // Additive share of a*b + receivera fmul.ReceiverFmul // Receiver of Fmul for a*b + + tkA group.Scalar // Additive share of 1/kA*1/kB + receiverkAInv fmul.ReceiverFmul // Receiver of Fmul for 1/kA*1/kB + myGroup group.Group // The elliptic curve we operate in +} + +// The receiver of Fmul +type BobPre struct { + label []byte + kB group.Scalar + kBInv group.Scalar // 1/kB + + DB group.Element // DB = [kB]G + R group.Element // R = [kA]DB + Rx group.Scalar // x coordinate of point [kA][kB]G + + b group.Scalar // A random blinding for beaver's triple + tb group.Scalar // Additive share of a*b + senderb fmul.SenderFmul // Sender of Fmul for a*b + + tkB group.Scalar // Additive share of 1/kA*1/kB + senderkBInv fmul.SenderFmul // Sender of Fmul for 1/kA*1/kB + myGroup group.Group // The elliptic curve we operate in +} + +// The final shares need to be saved +type Alice struct { + myGroup group.Group // The elliptic curve we operate in + keyShare group.Scalar + a group.Scalar // A random blinding for beaver's triple + kA group.Scalar // Multiplicative share of the instance key + ta group.Scalar // Additive share of a*b + tkA group.Scalar // Additive share of 1/kA*1/kB + Rx group.Scalar // x coordinate of point [kA][kB]G + beaver group.Scalar // skA/(kA*a) +} + +type Bob struct { + myGroup group.Group // The elliptic curve we operate in + keyShare group.Scalar + b group.Scalar // A random blinding for beaver's triple + kB group.Scalar // Multiplicative share of the instance key + tb group.Scalar // Additive share of a*b + tkB group.Scalar // Additive share of 1/kA*1/kB + Rx group.Scalar // x coordinate of point [kA][kB]G + beaver group.Scalar // skB/(kB*b) +} diff --git a/tss/ecdsa/dkls/ecdsaDKLS_test.go b/tss/ecdsa/dkls/ecdsaDKLS_test.go new file mode 100644 index 000000000..0f3eda18d --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLS_test.go @@ -0,0 +1,308 @@ +// Reference: https://eprint.iacr.org/2018/499.pdf +// 2 out of 2 party threhsold signature scheme +// Figure 1 and Protocol 1 and 2 + +package dkls + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testECDSAOTCount = 10 + +func genKey(myGroup group.Group, curve elliptic.Curve) (group.Scalar, *ecdsa.PublicKey) { + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + panic(err) + } + + if privateKey == nil { + panic(err) + } + + publicKey := &privateKey.PublicKey + + if publicKey == nil { + panic(err) + } + + secretByte := privateKey.D.Bytes() + secretScalar := myGroup.NewScalar() + err = secretScalar.UnmarshalBinary(secretByte) + if err != nil { + panic(err) + } + return secretScalar, publicKey +} + +// Input: myGroup, the group we operate in +// Output: precomputation information for signature generation +func precomputation(myGroup group.Group, alice *AlicePre, bob *BobPre, Alice *Alice, Bob *Bob) error { + // Initialization + DB, bAs, kBInvAs := bob.BobInit(myGroup) + + // Round 1 + // bob sends DB, bAs, kBInvAs, to alice + V, r, RPrime, aBs, kAInvBs := alice.AliceRound1(myGroup, DB, bAs, kBInvAs, alice.label, bob.label) + + // Round 2 + // alice sends a proof (V, r) of she knows the kA for R=[kA]DB as well as R' to bob + // alice sends aBs, kAInvBs, to bob + e0b, e1b, e0kBInv, e1kBInv, err := bob.BobRound2(V, r, RPrime, aBs, kAInvBs, alice.label, bob.label) + if err != nil { + return err + } + + // Round 3 + // bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + sigmaa, vsa, sigmakAInv, vskAInv, err := alice.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + return err + } + + // Round 4 + // alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + bob.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + + Alice.SetParamters(alice) + Bob.SetParamters(bob) + + return nil +} + +// Input: myGroup, the group we operate in +// Input: Alice and Bob +// Input: hash, the hash of the message we want to sign +// Input: curve, the curve we operate in +func sigGen(myGroup group.Group, Alice *Alice, Bob *Bob, hash []byte, curve elliptic.Curve) group.Scalar { + // Convert hash to scalar + hashBig := hashToInt(hash, curve) + hashByte := hashBig.Bytes() + + hashScalar := myGroup.NewScalar() + errByte := hashScalar.UnmarshalBinary(hashByte) + if errByte != nil { + panic(errByte) + } + beaverAlice := Alice.SigGenInit() + beaverBob := Bob.SigGenInit() + + // Round 1 + // Alice and Bob sends beaverAlice: skA/(kA*a), beaverBob: skB/(kB*b) to each other + sigAlice := Alice.SigGenRound1(beaverBob, hashScalar) + sigBob := Bob.SigGenRound1(beaverAlice, hashScalar) + + // Round 2 + // Either Alice or Bob can send the signature share to the other one and then combine + signature := SigGenRound2(myGroup, sigAlice, sigBob) + return signature +} + +func testECDSAOT(t *testing.T, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + errPre := precomputation(myGroup, &alicePre, &bobPre, &AliceSign, &BobSign) + if errPre != nil { + t.Error("Precomputation fail") + } + + // Generate key shares (precomputation is separate from key shares) + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := sigGen(myGroup, &AliceSign, &BobSign, hash, curve) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + t.Error("Signature verification fail") + } +} + +func TestECDSAOT(t *testing.T) { + t.Run("ECDSAOT", func(t *testing.T) { + for i := 0; i < testECDSAOTCount; i++ { + currGroup := group.P256 + currCurve := elliptic.P256() + testECDSAOT(t, currGroup, currCurve) + } + }) +} + +func benchECDSAOTPRE(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + + b.Run(curve.Params().Name+"-PreInit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + bobPre.BobInit(myGroup) + } + }) + + DB, bAs, kBInvAs := bobPre.BobInit(myGroup) + + b.Run(curve.Params().Name+"-PreRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + alicePre.AliceRound1(myGroup, DB, bAs, kBInvAs, alicePre.label, bobPre.label) + } + }) + + V, r, RPrime, aBs, kAInvBs := alicePre.AliceRound1(myGroup, DB, bAs, kBInvAs, alicePre.label, bobPre.label) + + b.Run(curve.Params().Name+"-PreRound2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _, _, err := bobPre.BobRound2(V, r, RPrime, aBs, kAInvBs, alicePre.label, bobPre.label) + if err != nil { + b.Error("PreRound2 zk verification fail") + } + } + }) + + e0b, e1b, e0kBInv, e1kBInv, err := bobPre.BobRound2(V, r, RPrime, aBs, kAInvBs, alicePre.label, bobPre.label) + if err != nil { + b.Error("PreRound2 zk verification fail") + } + + b.Run(curve.Params().Name+"-PreRound3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _, _, err = alicePre.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + b.Error("PreRound3 decryption fail") + } + } + }) + + sigmaa, vsa, sigmakAInv, vskAInv, err := alicePre.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + b.Error("PreRound3 decryption fail") + } + + b.Run(curve.Params().Name+"-PreRound4", func(b *testing.B) { + for i := 0; i < b.N; i++ { + bobPre.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + } + }) + + bobPre.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + + AliceSign.SetParamters(&alicePre) + BobSign.SetParamters(&bobPre) + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := sigGen(myGroup, &AliceSign, &BobSign, hash, curve) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } +} + +func benchECDSAOTSign(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + err := precomputation(myGroup, &alicePre, &bobPre, &AliceSign, &BobSign) + if err != nil { + b.Error("Precomputation fail") + } + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + hashBig := hashToInt(hash, curve) + hashByte := hashBig.Bytes() + + hashScalar := myGroup.NewScalar() + errByte := hashScalar.UnmarshalBinary(hashByte) + if errByte != nil { + panic(errByte) + } + + b.Run(curve.Params().Name+"-SignInit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AliceSign.SigGenInit() + BobSign.SigGenInit() + } + }) + + beaverAlice := AliceSign.SigGenInit() + beaverBob := BobSign.SigGenInit() + + b.Run(curve.Params().Name+"-SignRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AliceSign.SigGenRound1(beaverBob, hashScalar) + BobSign.SigGenRound1(beaverAlice, hashScalar) + } + }) + + sigAlice := AliceSign.SigGenRound1(beaverBob, hashScalar) + sigBob := BobSign.SigGenRound1(beaverAlice, hashScalar) + + b.Run(curve.Params().Name+"-SignRound2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + SigGenRound2(myGroup, sigAlice, sigBob) + } + }) + + signature := SigGenRound2(myGroup, sigAlice, sigBob) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } +} + +func BenchmarkECDSAOTPRE(b *testing.B) { + pubkeyCurve := elliptic.P256() + currGroup := group.P256 + benchECDSAOTPRE(b, currGroup, pubkeyCurve) +} + +func BenchmarkECDSAOTSign(b *testing.B) { + pubkeyCurve := elliptic.P256() + currGroup := group.P256 + benchECDSAOTSign(b, currGroup, pubkeyCurve) +} diff --git a/tss/ecdsa/dkls/ecdsalocalDKLS.go b/tss/ecdsa/dkls/ecdsalocalDKLS.go new file mode 100644 index 000000000..8dca6f9e0 --- /dev/null +++ b/tss/ecdsa/dkls/ecdsalocalDKLS.go @@ -0,0 +1,409 @@ +package dkls + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/tss/ecdsa/dkls/fmul" + zkRDL "github.com/cloudflare/circl/zk/dl" + "golang.org/x/crypto/sha3" +) + +// ---- Precomputation ---- + +// ---- Precomputation Initialization ---- + +// Input: myGroup, the group we operate in +// Output: DB for random nonce generation +// Output: bAs, kBInvAs for Fmul of a*b and 1/kA*1/kB +func (bob *BobPre) BobInit(myGroup group.Group) (group.Element, []group.Element, []group.Element) { + bob.myGroup = myGroup + // Generate multiplicative share of random nonce k + DB := bob.initRandomNonce(myGroup) + + // Initialize Fmul of a*b and 1/kA*1/kB + bAs, kBInvAs := bob.addShareGenInit(myGroup) + + return DB, bAs, kBInvAs +} + +// ---- Precomputation Round 1 ---- +// bob sends DB, bAs, kBInvAs, to alice + +// Input: myGroup, the group we operate in +// Input: DB, from bob for random nonce generation +// Input: bAs, kBInvAs from Bob for Fmul of a*b and 1/kA*1/kB +// Output: Proof (V, r) that alice knows kA, where R=[kA]DB, and RPrime +// Output: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +func (alice *AlicePre) AliceRound1(myGroup group.Group, DB group.Element, bAs, kBInvAs []group.Element, aliceLabel, bobLabel []byte) (group.Element, group.Scalar, group.Element, []group.Element, []group.Element) { + alice.myGroup = myGroup + // Generate multiplicative share of random nonce k + V, r, RPrime := alice.initRandomNonce(myGroup, DB, aliceLabel, bobLabel) + + // Round 1 Fmul of a*b and 1/kA*1/kB + aBs, kAInvBs := alice.addShareGenRound1(myGroup, bAs, kBInvAs) + return V, r, RPrime, aBs, kAInvBs +} + +// ---- Precomputation Round 2 ---- +// alice sends V, r, RPrime, aBs, kAInvBs, to bob + +// Input: Proof (V, r) that alice knows kA, where R=[kA]DB, and RPrime +// Input: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +// Output: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +func (bob *BobPre) BobRound2(V group.Element, r group.Scalar, RPrime group.Element, aBs, kAInvBs []group.Element, aliceLabel, bobLabel []byte) ([][]byte, [][]byte, [][]byte, [][]byte, error) { + // Generate R and verify proof from alice + err := bob.getRandomNonce(V, RPrime, r, aliceLabel, bobLabel) + if err != nil { + return nil, nil, nil, nil, err + } + + // Round 2 Fmul of a*b and 1/kA*1/kB + e0b, e1b, e0kBInv, e1kBInv := bob.addShareGenRound2(aBs, kAInvBs) + + return e0b, e1b, e0kBInv, e1kBInv, nil +} + +// ---- Precomputation Round 3 ---- +// bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + +// Input: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +// Output: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv) +func (alice *AlicePre) AliceRound3(e0b, e1b, e0kBInv, e1kBInv [][]byte) (group.Scalar, []group.Scalar, group.Scalar, []group.Scalar, error) { + sigmaa, vsa, sigmakAInv, vskAInv, errDec := alice.addShareGenRound3(e0b, e1b, e0kBInv, e1kBInv) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + alice.ta = alice.receivera.Returns2().Copy() + alice.tkA = alice.receiverkAInv.Returns2().Copy() + return sigmaa, vsa, sigmakAInv, vskAInv, nil +} + +// ---- Precomputation Round 4 ---- +// alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + +// Input: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv), from alice +func (bob *BobPre) BobRound4(sigmaa, sigmakAInv group.Scalar, vsa, vskAInv []group.Scalar) { + bob.addShareGenRound4(sigmaa, sigmakAInv, vsa, vskAInv) + bob.tb = bob.senderb.Returns1().Copy() + bob.tkB = bob.senderkBInv.Returns1().Copy() +} + +// ---- Helper functions for precomputation ---- + +// ---- Negotiate random nonce k ---- + +// Input: myGroup, the group we operate in +// Output: DB +func (bob *BobPre) initRandomNonce(myGroup group.Group) group.Element { + bob.kB = myGroup.RandomNonZeroScalar(rand.Reader) + bob.kBInv = myGroup.NewScalar() + bob.kBInv.Inv(bob.kB) + bob.DB = myGroup.NewElement() + bob.DB.MulGen(bob.kB) + return bob.DB.Copy() +} + +// bob sends DB to alice + +// Input: myGroup, the group we operate in +// Input: DB, from bob +// Output: Proof that alice knows kA, where R=[kA]DB, and RPrime +func (alice *AlicePre) initRandomNonce(myGroup group.Group, DB group.Element, aliceLabel, bobLabel []byte) (group.Element, group.Scalar, group.Element) { + alice.DB = DB.Copy() + alice.kAPrime = myGroup.RandomNonZeroScalar(rand.Reader) + alice.RPrime = myGroup.NewElement() + alice.RPrime.Mul(alice.DB, alice.kAPrime) + + RPrimeByte, errByte := alice.RPrime.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashResult := make([]byte, myGroup.Params().ScalarLength) + s := sha3.NewShake128() + _, errWrite := s.Write(RPrimeByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(hashResult) + if errRead != nil { + panic(errRead) + } + + hashRPrimeScalar := myGroup.NewScalar() + errByte = hashRPrimeScalar.UnmarshalBinary(hashResult) + if errByte != nil { + panic(errByte) + } + + alice.kA = myGroup.NewScalar() + alice.kA.Add(hashRPrimeScalar, alice.kAPrime) + + alice.kAInv = myGroup.NewScalar() + alice.kAInv.Inv(alice.kA) + + alice.R = myGroup.NewElement() + alice.R.Mul(alice.DB, alice.kA) + // get Rx as the x coordinate of R + RBinary, errByte := alice.R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + xCoor := RBinary[1 : myGroup.Params().ScalarLength+1] + alice.Rx = myGroup.NewScalar() + errByte = alice.Rx.UnmarshalBinary(xCoor) + if errByte != nil { + panic(errByte) + } + + // Construct zero knowledge proof that alice knows kA where R=[kA]DB + dst := "zeroknowledge" + rnd := rand.Reader + V, r := zkRDL.ProveGen(myGroup, alice.DB, alice.R, alice.kA, aliceLabel, bobLabel, []byte(dst), rnd) + + return V, r, alice.RPrime +} + +// alice sends a proof of she knows the kA for R=[kA]DB as well as R' to bob + +// Input: RPrime, from alice +// Input: V, r a proof from alice that she knows kA where R=[kA]DB +func (bob *BobPre) getRandomNonce(V, RPrime group.Element, r group.Scalar, aliceLabel, bobLabel []byte) error { + RPrimeByte, errByte := RPrime.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + hashResult := make([]byte, bob.myGroup.Params().ScalarLength) + s := sha3.NewShake128() + _, errWrite := s.Write(RPrimeByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(hashResult) + if errRead != nil { + panic(errRead) + } + + hashRPrimeScalar := bob.myGroup.NewScalar() + errByte = hashRPrimeScalar.UnmarshalBinary(hashResult) + if errByte != nil { + panic(errByte) + } + + bob.R = bob.myGroup.NewElement() + bob.R.Mul(bob.DB, hashRPrimeScalar) + bob.R.Add(bob.R, RPrime) + + // get Rx as the x coordinate of R + RBinary, errByte := bob.R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + xCoor := RBinary[1 : bob.myGroup.Params().ScalarLength+1] + bob.Rx = bob.myGroup.NewScalar() + errByte = bob.Rx.UnmarshalBinary(xCoor) + if errByte != nil { + panic(errByte) + } + + // Verify the proof + dst := "zeroknowledge" + + verify := zkRDL.Verify(bob.myGroup, bob.DB, bob.R, V, r, aliceLabel, bobLabel, []byte(dst)) + if !verify { + return errors.New("zero knowledge proof verification fails") + } + return nil +} + +// Now alice and bob have kA and kB +// Generate additive share of 1/kA*1/kB and random blinding a*b (For beaver's triple) +// t_kA + t_kB = 1/kA*1/kB = 1/k +// t_a + t_b = a*b +// Use Fmul subprotocol to realize this. +// bob as the sender of Fmul, alice as the receiver of Fmul + +// ---- Additive shares generation Initialization ---- + +// Input: myGroup, the group we operate in +// Output: bAs, kBInvAs for Fmul of a*b and 1/kA*1/kB +func (bob *BobPre) addShareGenInit(myGroup group.Group) ([]group.Element, []group.Element) { + bob.b = myGroup.RandomNonZeroScalar(rand.Reader) + + n := fmul.DecideNumOT(myGroup, 128) + bAs := bob.senderb.SenderInit(myGroup, bob.b, n) + + kBInvAs := bob.senderkBInv.SenderInit(myGroup, bob.kBInv, n) + + return bAs, kBInvAs +} + +// ---- Additive shares generation Round1 ---- +// bob sends bAs, kBInvAs to alice + +// Input: myGroup, the group we operate in +// Input: bAs, kBInvAs from bob +// Output: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +func (alice *AlicePre) addShareGenRound1(myGroup group.Group, bAs, kBInvAs []group.Element) ([]group.Element, []group.Element) { + alice.a = myGroup.RandomNonZeroScalar(rand.Reader) + + n := fmul.DecideNumOT(myGroup, 128) + + aBs := alice.receivera.ReceiverRound1(myGroup, bAs, alice.a, n) + kAInvBs := alice.receiverkAInv.ReceiverRound1(myGroup, kBInvAs, alice.kAInv, n) + + return aBs, kAInvBs +} + +// ---- Additive shares generation Round2 ---- +// alice sends aBs, kAInvBs to bob + +// Input: aBs, kAInvBs from alice +// Output: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +func (bob *BobPre) addShareGenRound2(aBs, kAInvBs []group.Element) ([][]byte, [][]byte, [][]byte, [][]byte) { + n := fmul.DecideNumOT(bob.myGroup, 128) + e0b, e1b := bob.senderb.SenderRound2(aBs, n) + e0kBInv, e1kBInv := bob.senderkBInv.SenderRound2(kAInvBs, n) + + return e0b, e1b, e0kBInv, e1kBInv +} + +// ---- Additive shares generation Round3 ---- +// bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + +// Input: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +// Output: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv) +func (alice *AlicePre) addShareGenRound3(e0b, e1b, e0kBInv, e1kBInv [][]byte) (group.Scalar, []group.Scalar, group.Scalar, []group.Scalar, error) { + n := fmul.DecideNumOT(alice.myGroup, 128) + + sigmaa, vsa, errDec := alice.receivera.ReceiverRound3(e0b, e1b, n) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + sigmakAInv, vskAInv, errDec := alice.receiverkAInv.ReceiverRound3(e0kBInv, e1kBInv, n) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + return sigmaa, vsa, sigmakAInv, vskAInv, nil +} + +// ---- Additive shares generation Round4 ---- +// alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + +// Input: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv), from alice +func (bob *BobPre) addShareGenRound4(sigmaa, sigmakAInv group.Scalar, vsa, vskAInv []group.Scalar) { + n := fmul.DecideNumOT(bob.myGroup, 128) + + bob.senderb.SenderRound4(vsa, sigmaa, n) + bob.senderkBInv.SenderRound4(vskAInv, sigmakAInv, n) +} + +// ---- Set useful parameter from AlicePre and BobPre to Alice and Bob ---- + +func (alice *Alice) SetParamters(alicePre *AlicePre) { + alice.myGroup = alicePre.myGroup + alice.a = alicePre.a.Copy() + alice.kA = alicePre.kA.Copy() + alice.ta = alicePre.ta.Copy() + alice.tkA = alicePre.tkA.Copy() + alice.Rx = alicePre.Rx.Copy() +} + +func (bob *Bob) SetParamters(bobPre *BobPre) { + bob.myGroup = bobPre.myGroup + bob.b = bobPre.b.Copy() + bob.kB = bobPre.kB.Copy() + bob.tb = bobPre.tb.Copy() + bob.tkB = bobPre.tkB.Copy() + bob.Rx = bobPre.Rx.Copy() +} + +// Receive key shares from the core + +func (bob *Bob) SetKeyShare(share group.Scalar) { + bob.keyShare = share +} + +func (alice *Alice) SetKeyShare(share group.Scalar) { + alice.keyShare = share +} + +// ---- Online phase ---- + +// Online Round 1 + +// Output: skA/(kA*a), skA/kA blinded by a +func (alice *Alice) SigGenInit() group.Scalar { + alice.beaver = alice.myGroup.NewScalar() // skA/(kA*a) + aInv := alice.myGroup.NewScalar() + aInv.Inv(alice.a) + kAInv := alice.myGroup.NewScalar() + kAInv.Inv(alice.kA) + + alice.beaver.Mul(alice.keyShare, aInv) + alice.beaver.Mul(alice.beaver, kAInv) + return alice.beaver.Copy() +} + +// Output: skB/(kB*b), skB/kB blinded by b +func (bob *Bob) SigGenInit() group.Scalar { + bob.beaver = bob.myGroup.NewScalar() // skB/(kB*b) + bInv := bob.myGroup.NewScalar() + bInv.Inv(bob.b) + kBInv := bob.myGroup.NewScalar() + kBInv.Inv(bob.kB) + + bob.beaver.Mul(bob.keyShare, bInv) + bob.beaver.Mul(bob.beaver, kBInv) + return bob.beaver.Copy() +} + +// Alice and Bob sends skA/(kA*a), skB/(kB*b) to each other + +// Online Round 2 +// Input: beaver, skB/(kB*b) +// Input: hashScalar, the hash message as a scalar +// Output: sigShare the additive share of the final signature +func (alice *Alice) SigGenRound1(beaver group.Scalar, hashScalar group.Scalar) group.Scalar { + askk := alice.myGroup.NewScalar() // Additive share of sk/k + askk.Mul(alice.ta, alice.beaver) + askk.Mul(askk, beaver) + + askk.Mul(askk, alice.Rx) // Rx * Additive share of sk/k + + sigShare := alice.myGroup.NewScalar() // Final signature share + sigShare.Mul(hashScalar, alice.tkA) + sigShare.Add(sigShare, askk) + return sigShare +} + +// Input: beaver, skA/(kA*a) +// Input: hashScalar, the hash message as a scalar +// Output: sigShare the additive share of the final signature +func (bob *Bob) SigGenRound1(beaver group.Scalar, hashScalar group.Scalar) group.Scalar { + askk := bob.myGroup.NewScalar() // Additive share of sk/k + askk.Mul(bob.tb, bob.beaver) + askk.Mul(askk, beaver) + + askk.Mul(askk, bob.Rx) // Rx * Additive share of sk/k + + sigShare := bob.myGroup.NewScalar() // Final signature share + sigShare.Mul(hashScalar, bob.tkB) + sigShare.Add(sigShare, askk) + return sigShare +} + +// Either Alice or Bob can send the signature share to the other one and then combine + +// Input: myGroup, the group we operate in +// Input: sigShare1, sigShare2 the 2 signature share from alice and bob +// Output: the final signature s +func SigGenRound2(myGroup group.Group, sigShare1, sigShare2 group.Scalar) group.Scalar { + signature := myGroup.NewScalar() // Additive share of sk/k + signature.Add(sigShare1, sigShare2) + return signature +} 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) + } + }) +}